diff --git a/src/main/java/com/fazecast/jSerialComm/SerialPort.java b/src/main/java/com/fazecast/jSerialComm/SerialPort.java index 92d77f2..0131fb3 100644 --- a/src/main/java/com/fazecast/jSerialComm/SerialPort.java +++ b/src/main/java/com/fazecast/jSerialComm/SerialPort.java @@ -2,7 +2,7 @@ * SerialPort.java * * Created on: Feb 25, 2012 - * Last Updated on: Mar 07, 2019 + * Last Updated on: Mar 15, 2019 * Author: Will Hedgecock * * Copyright (C) 2012-2019 Fazecast, Inc. @@ -26,6 +26,7 @@ package com.fazecast.jSerialComm; import java.lang.ProcessBuilder; +import java.nio.charset.Charset; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.File; @@ -37,12 +38,13 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.Date; +import java.util.regex.Pattern; /** * This class provides native access to serial ports and devices without requiring external libraries or tools. * * @author Will Hedgecock <will.hedgecock@fazecast.com> - * @version 2.4.2 + * @version 2.5.0 * @see java.io.InputStream * @see java.io.OutputStream */ @@ -679,25 +681,29 @@ public final class SerialPort *
* Calling this function enables event-based serial port callbacks to be used instead of, or in addition to, direct serial port read/write calls or the {@link java.io.InputStream}/{@link java.io.OutputStream} interface. *
- * The parameter passed into this method must be an implementation of either the {@link SerialPortDataListener} or the {@link SerialPortPacketListener}. - * The {@link SerialPortPacketListener} interface must be used if you plan to use event-based reading of full data packets over the serial port. + * The parameter passed into this method must be an implementation of either {@link SerialPortDataListener}, {@link SerialPortPacketListener}, or {@link SerialPortMessageListener}. + * The {@link SerialPortMessageListener} interface should be used if you plan to use event-based reading of delimited data messages over the serial port. + * The {@link SerialPortPacketListener} interface should be used if you plan to use event-based reading of full data packets over the serial port. * Otherwise, the simpler {@link SerialPortDataListener} may be used. *
* Only one listener can be registered at a time; however, that listener can be used to detect multiple types of serial port events.
- * Refer to {@link SerialPortDataListener} and {@link SerialPortPacketListener} for more information.
+ * Refer to {@link SerialPortDataListener}, {@link SerialPortPacketListener}, and {@link SerialPortMessageListener} for more information.
*
- * @param listener A {@link SerialPortDataListener} or {@link SerialPortPacketListener}implementation to be used for event-based serial port communications.
+ * @param listener A {@link SerialPortDataListener}, {@link SerialPortPacketListener}, or {@link SerialPortMessageListener} implementation to be used for event-based serial port communications.
* @return Whether the listener was successfully registered with the serial port.
* @see SerialPortDataListener
* @see SerialPortPacketListener
+ * @see SerialPortMessageListener
*/
public final boolean addDataListener(SerialPortDataListener listener)
{
if (userDataListener != null)
return false;
userDataListener = listener;
- serialEventListener = new SerialPortEventListener((userDataListener instanceof SerialPortPacketListener) ?
- ((SerialPortPacketListener)userDataListener).getPacketSize() : 0);
+ serialEventListener = ((userDataListener instanceof SerialPortPacketListener) ? new SerialPortEventListener(((SerialPortPacketListener)userDataListener).getPacketSize()) :
+ ((userDataListener instanceof SerialPortMessageListener) ?
+ new SerialPortEventListener(((SerialPortMessageListener)userDataListener).getMessageDelimiter(), ((SerialPortMessageListener)userDataListener).getCharacterEncoding()) :
+ new SerialPortEventListener()));
eventFlags = 0;
if ((listener.getListeningEvents() & LISTENING_EVENT_DATA_AVAILABLE) > 0)
@@ -1139,10 +1145,15 @@ public final class SerialPort
{
private volatile boolean isListening = false;
private final byte[] dataPacket;
+ private final String delimiter, delimiterRegex;
+ private final Charset messageCharset;
private volatile int dataPacketIndex = 0;
+ private String messages = "";
private Thread serialEventThread = null;
- public SerialPortEventListener(int packetSizeToReceive) { dataPacket = new byte[packetSizeToReceive]; }
+ public SerialPortEventListener() { dataPacket = new byte[0]; delimiter = delimiterRegex = ""; messageCharset = Charset.forName("UTF-8"); }
+ public SerialPortEventListener(int packetSizeToReceive) { dataPacket = new byte[packetSizeToReceive]; delimiter = delimiterRegex = ""; messageCharset = Charset.forName("UTF-8"); }
+ public SerialPortEventListener(String messageDelimiter, Charset charset) { dataPacket = new byte[0]; delimiter = messageDelimiter; delimiterRegex = Pattern.quote(messageDelimiter); messageCharset = charset; }
public final void startListening()
{
@@ -1192,7 +1203,17 @@ public final class SerialPort
byte[] newBytes = new byte[numBytesAvailable];
newBytesIndex = 0;
bytesRemaining = readBytes(portHandle, newBytes, newBytes.length, 0);
- if(dataPacket.length == 0)
+ if (!delimiter.isEmpty())
+ {
+ messages += new String(newBytes, messageCharset);
+ while (messages.contains(delimiter))
+ {
+ String[] message = messages.split(delimiterRegex, 2);
+ messages = (message.length > 1) ? message[1] : "";
+ userDataListener.serialEvent(new SerialPortEvent(SerialPort.this, LISTENING_EVENT_DATA_RECEIVED, message[0].getBytes(messageCharset)));
+ }
+ }
+ else if (dataPacket.length == 0)
userDataListener.serialEvent(new SerialPortEvent(SerialPort.this, LISTENING_EVENT_DATA_RECEIVED, newBytes.clone()));
else
{
diff --git a/src/main/java/com/fazecast/jSerialComm/SerialPortMessageListener.java b/src/main/java/com/fazecast/jSerialComm/SerialPortMessageListener.java
new file mode 100644
index 0000000..7ac2d65
--- /dev/null
+++ b/src/main/java/com/fazecast/jSerialComm/SerialPortMessageListener.java
@@ -0,0 +1,55 @@
+/*
+ * SerialPortMessageListener.java
+ *
+ * Created on: Mar 14, 2019
+ * Last Updated on: Mar 15, 2019
+ * Author: Will Hedgecock
+ *
+ * Copyright (C) 2012-2019 Fazecast, Inc.
+ *
+ * This file is part of jSerialComm.
+ *
+ * jSerialComm is free software: you can redistribute it and/or modify
+ * it under the terms of either the Apache Software License, version 2, or
+ * the GNU Lesser General Public License as published by the Free Software
+ * Foundation, version 3 or above.
+ *
+ * jSerialComm is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of both the GNU Lesser General Public
+ * License and the Apache Software License along with jSerialComm. If not,
+ * see
+ * Note: Using this interface will negate any serial port read timeout settings since they make no sense in an asynchronous context. + * + * @author Will Hedgecock <will.hedgecock@fazecast.com> + * @version 2.5.0 + * @see com.fazecast.jSerialComm.SerialPortDataListener + * @see java.util.EventListener + */ +public interface SerialPortMessageListener extends SerialPortDataListener +{ + /** + * Must be overridden to return the expected message delimiter that must be encountered before the {@link #serialEvent(SerialPortEvent)} callback is triggered. + * + * @return A string indicating the expected message delimiter that must be encountered before the {@link #serialEvent(SerialPortEvent)} callback is triggered. + */ + public abstract String getMessageDelimiter(); + + /** + * Must be overridden to return the expected character encoding used by your message transfer protocol. + * + * @return A {@link java.nio.charset.Charset} indicating the expected character encoding used by your serial messages. + */ + public abstract Charset getCharacterEncoding(); +} diff --git a/src/test/java/com/fazecast/jSerialComm/SerialPortTest.java b/src/test/java/com/fazecast/jSerialComm/SerialPortTest.java index 53d0de0..52da5d2 100644 --- a/src/test/java/com/fazecast/jSerialComm/SerialPortTest.java +++ b/src/test/java/com/fazecast/jSerialComm/SerialPortTest.java @@ -2,10 +2,10 @@ * SerialPortTest.java * * Created on: Feb 27, 2015 - * Last Updated on: Jan 10, 2018 + * Last Updated on: Mar 14, 2019 * Author: Will Hedgecock * - * Copyright (C) 2012-2018 Fazecast, Inc. + * Copyright (C) 2012-2019 Fazecast, Inc. * * This file is part of jSerialComm. * @@ -26,13 +26,14 @@ package com.fazecast.jSerialComm; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Scanner; /** * This class provides a test case for the jSerialComm library. * * @author Will Hedgecock <will.hedgecock@gmail.com> - * @version 2.4.1 + * @version 2.5.0 * @see java.io.InputStream * @see java.io.OutputStream */ @@ -55,6 +56,22 @@ public class SerialPortTest public int getPacketSize() { return 100; } } + private static final class MessageListener implements SerialPortMessageListener + { + @Override + public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_RECEIVED; } + @Override + public void serialEvent(SerialPortEvent event) + { + String newMessage = new String(event.getReceivedData()); + System.out.println("Received the following message: " + newMessage); + } + @Override + public String getMessageDelimiter() { return "\n"; } + @Override + public Charset getCharacterEncoding() { return Charset.forName("UTF-8"); } + } + static public void main(String[] args) { SerialPort[] ports = SerialPort.getCommPorts(); @@ -169,8 +186,13 @@ public class SerialPortTest PacketListener listener = new PacketListener(); ubxPort.addDataListener(listener); try { Thread.sleep(5000); } catch (Exception e) {} - System.out.println("\n\nClosing " + ubxPort.getDescriptivePortName() + ": " + ubxPort.closePort()); ubxPort.removeDataListener(); + System.out.println("\nNow listening for newline-delimited string messages\n"); + MessageListener messageListener = new MessageListener(); + ubxPort.addDataListener(messageListener); + try { Thread.sleep(5000); } catch (Exception e) {} + ubxPort.removeDataListener(); + System.out.println("\n\nClosing " + ubxPort.getDescriptivePortName() + ": " + ubxPort.closePort()); try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("Reopening " + ubxPort.getDescriptivePortName() + ": " + ubxPort.openPort() + "\n"); ubxPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 1000, 0);