From 6ec19f472f36c88dec538045d2c89747bd75f71f Mon Sep 17 00:00:00 2001 From: hedgecrw85 Date: Fri, 15 Mar 2019 11:35:44 -0500 Subject: [PATCH] Initial implementation of SerialPortMessageListener --- .../com/fazecast/jSerialComm/SerialPort.java | 41 ++++++++++---- .../SerialPortMessageListener.java | 55 +++++++++++++++++++ .../fazecast/jSerialComm/SerialPortTest.java | 30 ++++++++-- 3 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/fazecast/jSerialComm/SerialPortMessageListener.java 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 and . + */ + +package com.fazecast.jSerialComm; + +import java.nio.charset.Charset; + +/** + * This interface must be implemented to enable delimited string-based message reads using event-based serial port I/O. + *

+ * 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);