From a5b249aab0704a50635c6697c6210bff01e80dd3 Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Fri, 28 Jun 2024 18:25:24 +0200 Subject: [PATCH] Removed JSSC connection driver and added an event to detect if a controller has been disconnected. --- pom.xml | 3 +- ugs-core/pom.xml | 5 - .../AbstractController.java | 10 ++ .../communicator/AbstractCommunicator.java | 8 +- .../communicator/ICommunicator.java | 5 +- .../communicator/ICommunicatorListener.java | 7 +- .../event/CommunicatorEventDispatcher.java | 5 + .../connection/AbstractConnection.java | 26 ++-- .../connection/Connection.java | 11 +- .../connection/ConnectionDriver.java | 3 +- .../connection/ConnectionFactory.java | 36 ++--- ...er.java => ConnectionListenerManager.java} | 20 ++- .../connection/IConnectionListener.java | 23 +++ ...r.java => IConnectionListenerManager.java} | 7 +- .../connection/JSSCConnection.java | 140 ------------------ .../connection/JSSCConnectionDevice.java | 51 ------- .../connection/JSerialCommConnection.java | 47 +++--- .../connection/LoopBackConnection.java | 7 +- .../connection/TCPConnection.java | 50 +++---- .../connection/WSConnection.java | 19 ++- ...a => XModemConnectionListenerHandler.java} | 24 ++- .../firmware/fluidnc/FluidNCController.java | 9 ++ .../firmware/grbl/GrblFirmwareSettings.java | 4 + ...lFirmwareSettingsCommunicatorListener.java | 4 + .../firmware/tinyg/TinyGFirmwareSettings.java | 4 + ...GFirmwareSettingsCommunicatorListener.java | 5 + .../services/JogService.java | 2 +- .../AbstractControllerTest.java | 16 +- .../GrblControllerTest.java | 50 ++++++- .../BufferedCommunicatorTest.java | 2 +- .../communicator/GrblCommunicatorTest.java | 40 +++-- .../connection/JSerialCommConnectionTest.java | 54 +++++++ .../ResponseMessageHandlerTest.java | 4 +- .../fluidnc/FluidNCControllerTest.java | 37 ++++- .../commands/GetErrorCodesCommandTest.java | 6 +- .../mockobjects/MockConnection.java | 7 +- .../v1/resources/MachineResource.java | 5 +- .../ugs/nbp/core/ui/PortComboBox.java | 4 +- 38 files changed, 369 insertions(+), 391 deletions(-) rename ugs-core/src/com/willwinder/universalgcodesender/connection/{ResponseMessageHandler.java => ConnectionListenerManager.java} (76%) rename ugs-core/src/com/willwinder/universalgcodesender/connection/{IResponseMessageHandler.java => IConnectionListenerManager.java} (90%) delete mode 100644 ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnection.java delete mode 100644 ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnectionDevice.java rename ugs-core/src/com/willwinder/universalgcodesender/connection/xmodem/{XModemResponseMessageHandler.java => XModemConnectionListenerHandler.java} (75%) create mode 100644 ugs-core/test/com/willwinder/universalgcodesender/connection/JSerialCommConnectionTest.java diff --git a/pom.xml b/pom.xml index 145797c7d..e67fe004b 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,6 @@ 2.5.0 3.7.4 33.0.0-jre - 2.8.0 2.10.4 3.12.0 2.14.0 @@ -67,7 +66,7 @@ 1.0.3 3.0.0-M5 1.3.0 - 3.2.4 + 5.12.0 1.17 1.19.0 3.1.0 diff --git a/ugs-core/pom.xml b/ugs-core/pom.xml index bcf77eed9..f31de50ca 100644 --- a/ugs-core/pom.xml +++ b/ugs-core/pom.xml @@ -64,11 +64,6 @@ commons-io ${commons-io.version} - - org.scream3r - jssc - ${jssc.version} - com.fazecast jSerialComm diff --git a/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java b/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java index c11b72ee6..83528663f 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java @@ -391,6 +391,16 @@ public Boolean closeCommPort() throws Exception { return true; } + @Override + public void onConnectionClosed() { + try { + closeCommPort(); + } catch (Exception e) { + // Ignore + } + } + + @Override public Boolean isCommOpen() { return comm != null && comm.isConnected(); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/communicator/AbstractCommunicator.java b/ugs-core/src/com/willwinder/universalgcodesender/communicator/AbstractCommunicator.java index d9be80f24..8672b0f1a 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/communicator/AbstractCommunicator.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/communicator/AbstractCommunicator.java @@ -23,6 +23,7 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.connection.Connection; import com.willwinder.universalgcodesender.connection.ConnectionDriver; import com.willwinder.universalgcodesender.connection.ConnectionFactory; +import com.willwinder.universalgcodesender.connection.IConnectionListener; import com.willwinder.universalgcodesender.i18n.Localization; import java.io.IOException; @@ -33,7 +34,7 @@ This file is part of Universal Gcode Sender (UGS). * * @author wwinder */ -public abstract class AbstractCommunicator implements ICommunicator { +public abstract class AbstractCommunicator implements ICommunicator, IConnectionListener { private static final Logger logger = Logger.getLogger(AbstractCommunicator.class.getName()); private final ICommunicatorEventDispatcher eventDispatcher; @@ -124,4 +125,9 @@ public byte[] xmodemReceive() throws IOException { public void xmodemSend(byte[] data) throws IOException { connection.xmodemSend(data); } + + @Override + public void onConnectionClosed() { + eventDispatcher.onConnectionClosed(); + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicator.java b/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicator.java index 89f69c98f..e680e2dcd 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicator.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicator.java @@ -1,5 +1,5 @@ /* - Copyright 2019 Will Winder + Copyright 2019-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -20,7 +20,6 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.connection.Connection; import com.willwinder.universalgcodesender.connection.ConnectionDriver; -import com.willwinder.universalgcodesender.connection.IConnectionListener; import com.willwinder.universalgcodesender.types.GcodeCommand; import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; @@ -36,7 +35,7 @@ This file is part of Universal Gcode Sender (UGS). * * @author Joacim Breiler */ -public interface ICommunicator extends IConnectionListener { +public interface ICommunicator { /** * Add command to the command buffer outside file mode. These commands will be sent diff --git a/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicatorListener.java b/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicatorListener.java index c892959c5..a4a3921eb 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicatorListener.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/communicator/ICommunicatorListener.java @@ -1,5 +1,5 @@ /* - Copyright 2012-2018 Will Winder + Copyright 2012-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -55,4 +55,9 @@ public interface ICommunicatorListener { * processing of commands. */ void communicatorPausedOnError(); + + /** + * Called when the connection was closed + */ + void onConnectionClosed(); } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/communicator/event/CommunicatorEventDispatcher.java b/ugs-core/src/com/willwinder/universalgcodesender/communicator/event/CommunicatorEventDispatcher.java index 0f278ccce..451555663 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/communicator/event/CommunicatorEventDispatcher.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/communicator/event/CommunicatorEventDispatcher.java @@ -74,4 +74,9 @@ public void commandSkipped(GcodeCommand command) { public void communicatorPausedOnError() { communicatorListeners.forEach(ICommunicatorListener::communicatorPausedOnError); } + + @Override + public void onConnectionClosed() { + communicatorListeners.forEach(ICommunicatorListener::onConnectionClosed); + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/AbstractConnection.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/AbstractConnection.java index 9de5bc3eb..64860807c 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/AbstractConnection.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/AbstractConnection.java @@ -1,5 +1,5 @@ /* - Copyright 2013-2022 Will Winder + Copyright 2013-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -18,7 +18,7 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.universalgcodesender.connection; -import com.willwinder.universalgcodesender.connection.xmodem.XModemResponseMessageHandler; +import com.willwinder.universalgcodesender.connection.xmodem.XModemConnectionListenerHandler; import java.io.IOException; import java.util.Arrays; @@ -31,26 +31,25 @@ This file is part of Universal Gcode Sender (UGS). */ public abstract class AbstractConnection implements Connection { - protected IResponseMessageHandler responseMessageHandler = new ResponseMessageHandler(); + protected IConnectionListenerManager connectionListenerManager = new ConnectionListenerManager(); @Override public void addListener(IConnectionListener connectionListener) { - responseMessageHandler.addListener(connectionListener); + connectionListenerManager.addListener(connectionListener); } @Override public byte[] xmodemReceive() throws IOException { // Switch to a special XModem response handler - IResponseMessageHandler previousResponseMessageHandler = responseMessageHandler; + XModemConnectionListenerHandler reader = new XModemConnectionListenerHandler(this, connectionListenerManager); try { - XModemResponseMessageHandler reader = new XModemResponseMessageHandler(this); - responseMessageHandler = reader; + connectionListenerManager = reader; byte[] result = reader.xmodemReceive(); return trimEOF(result); } finally { // Restore the old response message handler - responseMessageHandler = previousResponseMessageHandler; + connectionListenerManager = reader.unwrap(); } } @@ -67,18 +66,17 @@ protected static byte[] trimEOF(byte[] result) { @Override public void xmodemSend(byte[] data) throws IOException { // Switch to a special XModem response handler - IResponseMessageHandler previousResponseMessageHandler = responseMessageHandler; + XModemConnectionListenerHandler reader = new XModemConnectionListenerHandler(this, connectionListenerManager); try { - XModemResponseMessageHandler reader = new XModemResponseMessageHandler(this); - responseMessageHandler = reader; + connectionListenerManager = reader; reader.xmodemSend(data); } finally { // Restore the old response message handler - responseMessageHandler = previousResponseMessageHandler; + connectionListenerManager = reader.unwrap(); } } - public IResponseMessageHandler getResponseMessageHandler() { - return responseMessageHandler; + public IConnectionListenerManager getConnectionListenerManager() { + return connectionListenerManager; } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/Connection.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/Connection.java index e1186d5b0..61c3c2496 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/Connection.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/Connection.java @@ -1,5 +1,5 @@ /* - Copyright 2013-2018 Will Winder + Copyright 2013-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -89,19 +89,12 @@ public interface Connection { */ boolean isOpen(); - /** - * Returns a list of all port names available - * - * @return a list of available port names - */ - List getPortNames(); - /** * Returns a list of all available connection devices * * @return a list of available connection devices */ - List getDevices(); + List getDevices(); /** * Enters a mode for receiving using the xmodem protocol and return the file stream as an byte array. diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionDriver.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionDriver.java index 1c63124b5..df57a106c 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionDriver.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionDriver.java @@ -1,5 +1,5 @@ /* - Copyright 2018 Will Winder + Copyright 2018-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -28,7 +28,6 @@ This file is part of Universal Gcode Sender (UGS). */ public enum ConnectionDriver { JSERIALCOMM("JSerialComm", "jserialcomm://"), - JSSC("JSSC", "jssc://"), TCP("TCP", "tcp://"), WS("WebSocket", "ws://"); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionFactory.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionFactory.java index c503fc86a..6187b73a3 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionFactory.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionFactory.java @@ -1,5 +1,5 @@ /* - Copyright 2015-2023 Will Winder + Copyright 2015-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -40,7 +40,7 @@ public class ConnectionFactory { * @return a connection * @throws ConnectionException if something went wron while creating the connection */ - static public Connection getConnection(String uri) throws ConnectionException { + public static Connection getConnection(String uri) throws ConnectionException { for (ConnectionDriver connectionDriver : ConnectionDriver.values()) { if (StringUtils.startsWithIgnoreCase(uri, connectionDriver.getProtocol())) { Connection connection = getConnection(connectionDriver).orElseThrow(() -> new ConnectionException("Couldn't load connection driver " + connectionDriver + " for uri: " + uri)); @@ -52,42 +52,24 @@ static public Connection getConnection(String uri) throws ConnectionException { throw new ConnectionException("Couldn't find connection driver for uri: " + uri); } - /** - * Returns available ports for this connection driver - * - * @param connectionDriver the connection driver to use for querying ports - * @return a list of port names - * @deprecated use {@link #getDevices(ConnectionDriver)} instead - */ - public static List getPortNames(ConnectionDriver connectionDriver) { - return getConnection(connectionDriver) - .map(Connection::getPortNames) - .orElseGet(Collections::emptyList); - } - /** * Lists found devices for the given connection driver * * @param connectionDriver the connection driver to use for querying devices * @return a list of connection devices */ - public static List getDevices(ConnectionDriver connectionDriver) { + public static List getDevices(ConnectionDriver connectionDriver) { return getConnection(connectionDriver) .map(Connection::getDevices) .orElseGet(Collections::emptyList); } public static Optional getConnection(ConnectionDriver connectionDriver) { - switch (connectionDriver) { - case JSERIALCOMM: - return Optional.of(new JSerialCommConnection()); - case JSSC: - return Optional.of(new JSSCConnection()); - case TCP: - return Optional.of(new TCPConnection()); - case WS: - return Optional.of(new WSConnection()); - } - return Optional.empty(); + return switch (connectionDriver) { + case JSERIALCOMM -> Optional.of(new JSerialCommConnection()); + case TCP -> Optional.of(new TCPConnection()); + case WS -> Optional.of(new WSConnection()); + default -> Optional.empty(); + }; } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/ResponseMessageHandler.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionListenerManager.java similarity index 76% rename from ugs-core/src/com/willwinder/universalgcodesender/connection/ResponseMessageHandler.java rename to ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionListenerManager.java index 17a2231af..2dc153c22 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/ResponseMessageHandler.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/ConnectionListenerManager.java @@ -1,5 +1,5 @@ /* - Copyright 2018 Will Winder + Copyright 2018-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -28,19 +28,19 @@ This file is part of Universal Gcode Sender (UGS). /** * Handles response messages from the serial connection buffering the data * until we have a complete line. It will then attempt to dispatch that - * data to a communicator. + * data to a communicator via a {@link IConnectionListener} * * @author wwinder * @author Joacim Breiler */ -public class ResponseMessageHandler implements IResponseMessageHandler { +public class ConnectionListenerManager implements IConnectionListenerManager { - private final static Logger LOGGER = Logger.getLogger(ResponseMessageHandler.class.getSimpleName()); + private static final Logger LOGGER = Logger.getLogger(ConnectionListenerManager.class.getSimpleName()); private final StringBuilder inputBuffer; private final Set listeners = new HashSet<>(); - public ResponseMessageHandler() { + public ConnectionListenerManager() { inputBuffer = new StringBuilder(); } @@ -76,13 +76,19 @@ public void notifyListeners(String message) { try { listener.handleResponseMessage(message); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "The response message could not be handled: \"" + message + "\", unsafe to proceed, shutting down connection.", e); - throw e; + LOGGER.log(Level.SEVERE, e, () -> "The response message could not be handled: \"" + message + "\", unsafe to proceed, shutting down connection."); + throw new ConnectionException("The response message could not be handled: \"" + message + "\", unsafe to proceed, shutting down connection.", e); } }); } + @Override public void addListener(IConnectionListener connectionListener) { listeners.add(connectionListener); } + + @Override + public void onConnectionClosed() { + listeners.forEach(IConnectionListener::onConnectionClosed); + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/IConnectionListener.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/IConnectionListener.java index 2ddc96f65..abbc3f557 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/IConnectionListener.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/IConnectionListener.java @@ -1,3 +1,21 @@ +/* + Copyright 2019-2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS 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. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ package com.willwinder.universalgcodesender.connection; /** @@ -12,4 +30,9 @@ public interface IConnectionListener { * @param response a response message */ void handleResponseMessage(String response); + + /** + * This method will be executed if the connection is closed + */ + void onConnectionClosed(); } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/IResponseMessageHandler.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/IConnectionListenerManager.java similarity index 90% rename from ugs-core/src/com/willwinder/universalgcodesender/connection/IResponseMessageHandler.java rename to ugs-core/src/com/willwinder/universalgcodesender/connection/IConnectionListenerManager.java index a947d2569..555252298 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/IResponseMessageHandler.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/IConnectionListenerManager.java @@ -24,7 +24,7 @@ This file is part of Universal Gcode Sender (UGS). * @author wwinder * @author Joacim Breiler */ -public interface IResponseMessageHandler { +public interface IConnectionListenerManager { /** * Receives byte data from the serial connection to be processed. * @@ -37,4 +37,9 @@ public interface IResponseMessageHandler { void addListener(IConnectionListener connectionListener); void notifyListeners(String message); + + /** + * Is triggered when a connection was closed + */ + void onConnectionClosed(); } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnection.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnection.java deleted file mode 100644 index cc060c57d..000000000 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnection.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - Copyright 2015-2018 Will Winder - - This file is part of Universal Gcode Sender (UGS). - - UGS is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - UGS 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. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with UGS. If not, see . -*/ -package com.willwinder.universalgcodesender.connection; - -import jssc.SerialPort; -import jssc.SerialPortEvent; -import jssc.SerialPortEventListener; -import jssc.SerialPortList; -import org.apache.commons.lang3.StringUtils; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -/** - * A serial connection object implementing the connection API. - * - * @author wwinder - */ -public class JSSCConnection extends AbstractConnection implements SerialPortEventListener { - - private int baudRate; - private String portName; - - // General variables - private SerialPort serialPort; - - @Override - public void setUri(String uri) { - try { - portName = StringUtils.substringBetween(uri, ConnectionDriver.JSSC.getProtocol(), ":"); - baudRate = Integer.parseInt(StringUtils.substringAfterLast(uri, ":")); - } catch (Exception e) { - throw new ConnectionException("Couldn't parse connection string " + uri, e); - } - } - - @Override - public boolean openPort() throws Exception { - if (StringUtils.isEmpty(portName) || baudRate == 0) { - throw new ConnectionException("Couldn't open port " + portName + " using baud rate " + baudRate); - } - this.serialPort = new SerialPort(portName); - this.serialPort.openPort(); - this.serialPort.setParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, true, true); - this.serialPort.addEventListener(this); - - if (this.serialPort == null) { - throw new ConnectionException("Serial port not found."); - } - return serialPort.isOpened(); - } - - @Override - public void closePort() throws Exception { - if (this.serialPort != null) { - try { - this.serialPort.removeEventListener(); - - if (this.serialPort.isOpened()) { - this.serialPort.closePort(); - } - } finally { - this.serialPort = null; - } - } - } - - @Override - public boolean isOpen() { - return serialPort != null && serialPort.isOpened(); - } - - @Override - public List getPortNames() { - return Arrays.asList(SerialPortList.getPortNames()); - } - - @Override - public List getDevices() { - return Arrays.stream(SerialPortList.getPortNames()) - .map(port -> new JSSCConnectionDevice(new SerialPort(port))) - .collect(Collectors.toList()); - - } - - /** - * Sends a command to the serial device. This actually streams the bits to - * the comm port. - * - * @param command Command to be sent to serial device. - */ - @Override - public void sendStringToComm(String command) throws Exception { - this.serialPort.writeString(command); - } - - /** - * Immediately sends a byte, used for real-time commands. - */ - @Override - public void sendByteImmediately(byte b) throws Exception { - this.serialPort.writeByte(b); - } - - /** - * Reads data from the serial port. RXTX SerialPortEventListener method. - */ - @Override - public void serialEvent(SerialPortEvent evt) { - try { - byte[] buf = this.serialPort.readBytes(); - if (buf == null || buf.length <= 0) { - return; - } - - responseMessageHandler.handleResponse(buf, 0, buf.length); - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } -} - diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnectionDevice.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnectionDevice.java deleted file mode 100644 index 9f6111ec8..000000000 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/JSSCConnectionDevice.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright 2023 Will Winder - - This file is part of Universal Gcode Sender (UGS). - - UGS is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - UGS 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. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with UGS. If not, see . - */ -package com.willwinder.universalgcodesender.connection; - -import jssc.SerialPort; - -import java.util.Optional; - -/** - * A connection device based on a JSSC serial port. - * - * @author Joacim Breiler - */ -public class JSSCConnectionDevice extends AbstractConnectionDevice implements IConnectionDevice { - private final SerialPort serialPort; - - public JSSCConnectionDevice(SerialPort serialPort) { - this.serialPort = serialPort; - } - - @Override - public String getAddress() { - return serialPort.getPortName(); - } - - @Override - public Optional getDescription() { - return Optional.empty(); - } - - @Override - public Optional getPort() { - return Optional.empty(); - } -} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/JSerialCommConnection.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/JSerialCommConnection.java index 8f2aee5ef..643ec3089 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/JSerialCommConnection.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/JSerialCommConnection.java @@ -24,7 +24,6 @@ This file is part of Universal Gcode Sender (UGS). import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; /** * Serial connection using JSerialComm @@ -35,6 +34,14 @@ public class JSerialCommConnection extends AbstractConnection implements SerialP private SerialPort serialPort; + public JSerialCommConnection() { + // Empty implementation + } + + public JSerialCommConnection(SerialPort serialPort) { + this.serialPort = serialPort; + } + @Override public void setUri(String uri) { try { @@ -77,6 +84,7 @@ public void closePort() throws Exception { if (serialPort != null) { serialPort.removeDataListener(); serialPort.closePort(); + serialPort = null; } } @@ -92,36 +100,39 @@ public void sendStringToComm(String command) throws Exception { @Override public boolean isOpen() { - return serialPort.isOpen(); + return serialPort != null && serialPort.isOpen(); } @Override - public List getPortNames() { - return Arrays.stream(SerialPort.getCommPorts()) - .map(SerialPort::getSystemPortName) - .collect(Collectors.toList()); - } - - @Override - public List getDevices() { + public List getDevices() { return Arrays.stream(SerialPort.getCommPorts()) .map(JSerialCommConnectionDevice::new) - .collect(Collectors.toList()); + .toList(); } @Override public int getListeningEvents() { - return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; + return SerialPort.LISTENING_EVENT_DATA_AVAILABLE | SerialPort.LISTENING_EVENT_PORT_DISCONNECTED; } @Override public void serialEvent(com.fazecast.jSerialComm.SerialPortEvent event) { - if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { - return; + switch (event.getEventType()) { + case SerialPort.LISTENING_EVENT_PORT_DISCONNECTED -> { + try { + connectionListenerManager.onConnectionClosed(); + } catch (Exception e) { + // Never mind + } + } + case SerialPort.LISTENING_EVENT_DATA_AVAILABLE -> { + byte[] newData = new byte[serialPort.bytesAvailable()]; + int numRead = serialPort.readBytes(newData, newData.length); + getConnectionListenerManager().handleResponse(newData, 0, numRead); + } + default -> { + // Never mind + } } - - byte[] newData = new byte[serialPort.bytesAvailable()]; - int numRead = serialPort.readBytes(newData, newData.length); - getResponseMessageHandler().handleResponse(newData, 0, numRead); } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/LoopBackConnection.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/LoopBackConnection.java index 69c6becfb..7b79f32d8 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/LoopBackConnection.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/LoopBackConnection.java @@ -101,7 +101,7 @@ private void initialize() { } private void handleResponse(String response) { - responseMessageHandler.handleResponse(response.getBytes(), 0, response.length()); + connectionListenerManager.handleResponse(response.getBytes(), 0, response.length()); } @Override @@ -123,11 +123,6 @@ public boolean isOpen() { return open; } - @Override - public List getPortNames() { - return Arrays.asList("loopback"); - } - @Override public List getDevices() { return Arrays.asList(new DefaultConnectionDevice("loopback")); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/TCPConnection.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/TCPConnection.java index 96839011b..25542dd12 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/TCPConnection.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/TCPConnection.java @@ -31,12 +31,12 @@ This file is part of Universal Gcode Sender (UGS). import java.net.SocketException; import java.util.List; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * A TCP connection object implementing the connection API. * * @author Adam Carmicahel + * @author Joacim Breiler */ public class TCPConnection extends AbstractConnection implements Runnable, Connection { @@ -93,27 +93,25 @@ public boolean openPort() throws Exception { return client.isConnected(); } - /** - * TODO: toggle the disconnect/connect icon; investigate how... - * UGS correctly goes into offline state when called, potentially a bug elsewhere? - */ @Override public void closePort() throws Exception { - if (client != null) { - try { - replyThread.interrupt(); - client.close(); - } catch (SocketException e) { - // ignore socketexception if connection was broken early - } finally { - client = null; - } + if (client == null) { + return; + } + + try { + replyThread.interrupt(); + client.close(); + } catch (SocketException e) { + // ignore socketexception if connection was broken early + } finally { + client = null; } } @Override public boolean isOpen() { - return (client != null) && (!client.isClosed()); + return client != null && !client.isClosed(); } /** @@ -126,7 +124,8 @@ public void sendStringToComm(String command) throws Exception { bufOut.write(command.getBytes()); bufOut.flush(); } catch (IOException e) { - closePort(); // very likely we got disconnected, attempt to disconnect gracefully + // very likely we got disconnected, attempt to disconnect gracefully + connectionListenerManager.onConnectionClosed(); throw e; } } @@ -139,7 +138,8 @@ public void sendByteImmediately(byte b) throws Exception { bufOut.write(b); bufOut.flush(); } catch (IOException e) { - closePort(); // very likely we got disconnected, attempt to disconnect gracefully + // very likely we got disconnected, attempt to disconnect gracefully + connectionListenerManager.onConnectionClosed(); throw e; } } @@ -153,26 +153,22 @@ public void run() { try { int readBytes = inStream.read(buffer); if (readBytes > 0) { - responseMessageHandler.handleResponse(buffer, 0, readBytes); + connectionListenerManager.handleResponse(buffer, 0, readBytes); } } catch (IOException e) { LOGGER.info("Got a socket exception: " + e.getMessage()); - return; // terminate thread if disconnected + break; } } - } - @Override - public List getPortNames() { - return getDevices().stream() - .map(IConnectionDevice::getAddress) - .collect(Collectors.toList()); + // Notify listeners that we are disconnected + connectionListenerManager.onConnectionClosed(); } @Override - public List getDevices() { + public List getDevices() { return MdnsService.getInstance().getServices(MDNS_SERVICE).stream() .map(service -> new DefaultConnectionDevice(service.getHost(), service.getPort(), service.getName())) - .collect(Collectors.toList()); + .toList(); } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/WSConnection.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/WSConnection.java index 91d2ece4b..35bbbde47 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/WSConnection.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/WSConnection.java @@ -20,13 +20,20 @@ This file is part of Universal Gcode Sender (UGS). import org.apache.commons.lang3.StringUtils; -import java.io.*; +import javax.websocket.ClientEndpoint; +import javax.websocket.CloseReason; +import javax.websocket.ContainerProvider; +import javax.websocket.OnClose; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import java.io.OutputStream; import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import javax.websocket.*; @ClientEndpoint public class WSConnection extends AbstractConnection implements Connection { @@ -65,11 +72,12 @@ public void onOpen(Session userSession) { @OnClose public void onClose(Session userSession, CloseReason reason) { this.userSession = null; + connectionListenerManager.onConnectionClosed(); } @OnMessage public void onMessage(String message) { - responseMessageHandler.handleResponse(message.getBytes(), 0, message.length()); + connectionListenerManager.handleResponse(message.getBytes(), 0, message.length()); } @Override @@ -105,11 +113,6 @@ public boolean isOpen() { return this.userSession != null && this.userSession.isOpen(); } - @Override - public List getPortNames() { - return new ArrayList<>(); - } - @Override public List getDevices() { return new ArrayList<>(); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/connection/xmodem/XModemResponseMessageHandler.java b/ugs-core/src/com/willwinder/universalgcodesender/connection/xmodem/XModemConnectionListenerHandler.java similarity index 75% rename from ugs-core/src/com/willwinder/universalgcodesender/connection/xmodem/XModemResponseMessageHandler.java rename to ugs-core/src/com/willwinder/universalgcodesender/connection/xmodem/XModemConnectionListenerHandler.java index 1959f6f2a..d8af59167 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/connection/xmodem/XModemResponseMessageHandler.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/connection/xmodem/XModemConnectionListenerHandler.java @@ -1,5 +1,5 @@ /* - Copyright 2022 Will Winder + Copyright 2022-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -20,26 +20,27 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.connection.Connection; import com.willwinder.universalgcodesender.connection.IConnectionListener; -import com.willwinder.universalgcodesender.connection.IResponseMessageHandler; +import com.willwinder.universalgcodesender.connection.IConnectionListenerManager; +import static com.willwinder.universalgcodesender.connection.xmodem.XModemUtils.trimEOF; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -import static com.willwinder.universalgcodesender.connection.xmodem.XModemUtils.trimEOF; - /** * A response message handler for handling XModem communication to upload and download files from the controller. * * @author Joacim Breiler */ -public class XModemResponseMessageHandler implements IResponseMessageHandler { +public class XModemConnectionListenerHandler implements IConnectionListenerManager { private final RingBuffer buffer = new RingBuffer(4096); private final XModem modem; + private final IConnectionListenerManager previousConnectionListenerManager; - public XModemResponseMessageHandler(Connection connection) { - modem = new XModem(buffer, new OutputStream() { + public XModemConnectionListenerHandler(Connection connection, IConnectionListenerManager previousConnectionListenerManager) { + this.previousConnectionListenerManager = previousConnectionListenerManager; + this.modem = new XModem(buffer, new OutputStream() { @Override public void write(int b) throws IOException { try { @@ -61,6 +62,11 @@ public void notifyListeners(String message) { // Not implemented } + @Override + public void onConnectionClosed() { + previousConnectionListenerManager.onConnectionClosed(); + } + @Override public void handleResponse(byte[] buffer, int offset, int length) { this.buffer.write(buffer, offset, length); @@ -75,4 +81,8 @@ public byte[] xmodemReceive() throws IOException { public void xmodemSend(byte[] data) throws IOException { modem.send(new ByteArrayInputStream(data), false); } + + public IConnectionListenerManager unwrap() { + return previousConnectionListenerManager; + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java index 1fa3b873b..1088db0e5 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java @@ -831,6 +831,15 @@ public void communicatorPausedOnError() { } } + @Override + public void onConnectionClosed() { + try { + closeCommPort(); + } catch (Exception e) { + // Never mind + } + } + @Override public IFileService getFileService() { return fileService; diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettings.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettings.java index 8f8fb0d9c..fdd5e9d37 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettings.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettings.java @@ -442,6 +442,10 @@ public void communicatorPausedOnError() { serialCommunicatorDelegate.communicatorPausedOnError(); } + @Override + public void onConnectionClosed() { + } + /* * IFirmwareSettingsListener */ diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettingsCommunicatorListener.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettingsCommunicatorListener.java index 5e28e9545..d081d54b0 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettingsCommunicatorListener.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblFirmwareSettingsCommunicatorListener.java @@ -268,4 +268,8 @@ public void commandSkipped(GcodeCommand command) { public void communicatorPausedOnError() { } + + @Override + public void onConnectionClosed() { + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettings.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettings.java index 925778bf8..74e3d1e18 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettings.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettings.java @@ -228,4 +228,8 @@ public void communicatorPausedOnError() { serialCommunicatorDelegate.communicatorPausedOnError(); } + @Override + public void onConnectionClosed() { + + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettingsCommunicatorListener.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettingsCommunicatorListener.java index 4ebdb47a7..1d7628917 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettingsCommunicatorListener.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/tinyg/TinyGFirmwareSettingsCommunicatorListener.java @@ -152,6 +152,11 @@ public void communicatorPausedOnError() { // Not used } + @Override + public void onConnectionClosed() { + + } + /** * Returns if the controller is ready to receive setting commands. * diff --git a/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java b/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java index 7b3467efa..66cb82d23 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java @@ -328,7 +328,7 @@ public void jogTo(PartialPosition position) { try { backend.getController().jogMachineTo(position, getFeedRate()); } catch (Exception e) { - logger.log(Level.WARNING, "Couldn't jog to position " + position, e); + logger.log(Level.WARNING, e, () -> String.format("Couldn't jog to position %s", position)); } } } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/AbstractControllerTest.java b/ugs-core/test/com/willwinder/universalgcodesender/AbstractControllerTest.java index 924cec28d..78e786410 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/AbstractControllerTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/AbstractControllerTest.java @@ -38,7 +38,6 @@ This file is part of Universal Gcode Sender (UGS). import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; -import static org.easymock.EasyMock.or; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.verify; @@ -126,7 +125,7 @@ private void openInstanceExpectUtility(String port, int portRate) throws Excepti instance.setControllerState(eq(ControllerState.CONNECTING)); expect(expectLastCall()).once(); expect(mockCommunicator.isConnected()).andReturn(true).anyTimes(); - mockCommunicator.connect(or(eq(ConnectionDriver.JSERIALCOMM), eq(ConnectionDriver.JSSC)), eq(port), eq(portRate)); + mockCommunicator.connect(eq(ConnectionDriver.JSERIALCOMM), eq(port), eq(portRate)); expect(instance.isCommOpen()).andReturn(false).once(); expect(instance.isCommOpen()).andReturn(true).anyTimes(); } @@ -137,20 +136,7 @@ private void streamInstanceExpectUtility() throws Exception { mockCommunicator.streamCommands(); expect(expectLastCall()).once(); } - private void startStreamExpectation(String port, int rate) throws Exception { - openInstanceExpectUtility(port, rate); - streamInstanceExpectUtility(); - // Making sure the commands get queued. - mockCommunicator.queueStreamForComm(anyObject(IGcodeStreamReader.class)); - expect(expectLastCall()).times(1); - } - private void startStream(String port, int rate, String command) throws Exception { - // Open port, send some commands, make sure they are streamed. - instance.openCommPort(getSettings().getConnectionDriver(), port, rate); - instance.queueStream(new SimpleGcodeStreamReader(command, command)); - instance.beginStreaming(); - } private Settings getSettings() { return settings; } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/GrblControllerTest.java b/ugs-core/test/com/willwinder/universalgcodesender/GrblControllerTest.java index 4aec5b9ef..2b7e3dd7b 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/GrblControllerTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/GrblControllerTest.java @@ -19,6 +19,9 @@ This file is part of Universal Gcode Sender (UGS). package com.willwinder.universalgcodesender; import com.willwinder.universalgcodesender.AbstractController.UnexpectedCommand; +import static com.willwinder.universalgcodesender.GrblUtils.GRBL_PAUSE_COMMAND; +import static com.willwinder.universalgcodesender.GrblUtils.GRBL_RESET_COMMAND; +import static com.willwinder.universalgcodesender.GrblUtils.GRBL_RESUME_COMMAND; import com.willwinder.universalgcodesender.firmware.grbl.GrblVersion; import com.willwinder.universalgcodesender.gcode.DefaultCommandCreator; import com.willwinder.universalgcodesender.gcode.util.Code; @@ -27,13 +30,40 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.MessageType; import com.willwinder.universalgcodesender.mockobjects.MockGrblCommunicator; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_CHECK; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_SENDING; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.services.MessageService; import com.willwinder.universalgcodesender.types.GcodeCommand; -import com.willwinder.universalgcodesender.utils.*; +import com.willwinder.universalgcodesender.utils.GUIHelpers; +import com.willwinder.universalgcodesender.utils.GcodeStreamReader; +import com.willwinder.universalgcodesender.utils.GcodeStreamTest; +import com.willwinder.universalgcodesender.utils.GcodeStreamWriter; +import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; +import com.willwinder.universalgcodesender.utils.Settings; +import com.willwinder.universalgcodesender.utils.SimpleGcodeStreamReader; import org.apache.commons.io.FileUtils; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import java.io.File; import java.io.IOException; @@ -43,12 +73,6 @@ This file is part of Universal Gcode Sender (UGS). import java.util.Collection; import java.util.List; -import static com.willwinder.universalgcodesender.GrblUtils.*; -import static com.willwinder.universalgcodesender.model.CommunicatorState.*; -import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.*; - /** * @author wwinder */ @@ -1340,6 +1364,16 @@ public void commandCompleteWithNoSentCommandsShouldThrowError() throws Exception instance.commandComplete("done"); } + @Test + public void onConnectionClosedShouldDisconnectController() throws Exception { + GrblController instance = initializeAndConnectController(VERSION_GRBL_0_8); + assertEquals(ControllerState.CONNECTING, instance.getControllerStatus().getState()); + + instance.onConnectionClosed(); + + assertEquals(ControllerState.DISCONNECTED, instance.getControllerStatus().getState()); + } + /** * Helper function for initializing a grbl controller for testing * diff --git a/ugs-core/test/com/willwinder/universalgcodesender/communicator/BufferedCommunicatorTest.java b/ugs-core/test/com/willwinder/universalgcodesender/communicator/BufferedCommunicatorTest.java index 2fad45087..ad96fe1a6 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/communicator/BufferedCommunicatorTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/communicator/BufferedCommunicatorTest.java @@ -323,7 +323,7 @@ public void testOpenCommPort() throws Exception { EasyMock.expect(mockConnection.openPort()).andReturn(true).once(); EasyMock.replay(mockConnection); - instance.connect(ConnectionDriver.JSSC, name, baud); + instance.connect(ConnectionDriver.JSERIALCOMM, name, baud); EasyMock.verify(mockConnection); } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/communicator/GrblCommunicatorTest.java b/ugs-core/test/com/willwinder/universalgcodesender/communicator/GrblCommunicatorTest.java index 59491477a..432397652 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/communicator/GrblCommunicatorTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/communicator/GrblCommunicatorTest.java @@ -20,17 +20,25 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.GrblUtils; import com.willwinder.universalgcodesender.communicator.event.CommunicatorEventDispatcher; -import com.willwinder.universalgcodesender.utils.CommUtils; +import com.willwinder.universalgcodesender.communicator.event.ICommunicatorEventDispatcher; import com.willwinder.universalgcodesender.mockobjects.MockConnection; import com.willwinder.universalgcodesender.mockobjects.MockGrbl; import com.willwinder.universalgcodesender.types.GcodeCommand; -import java.util.LinkedList; -import java.util.concurrent.LinkedBlockingDeque; -import static org.junit.Assert.*; - +import com.willwinder.universalgcodesender.utils.CommUtils; import org.junit.Assert; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingDeque; /** * @@ -56,8 +64,6 @@ public void setUp() { */ @Test public void testQueueStringForComm() throws Exception { - - System.out.println("queueStringForComm"); String input = "someCommand"; MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); @@ -95,7 +101,6 @@ public void testQueueStringForComm() throws Exception { */ @Test public void testSendByteImmediately() { - System.out.println("sendByteImmediately"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); @@ -138,7 +143,6 @@ public void testSendByteImmediately() { */ @Test public void testAreActiveCommands() { - System.out.println("areActiveCommands"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); @@ -176,7 +180,6 @@ public void testAreActiveCommands() { */ @Test public void testStreamCommands() { - System.out.println("streamCommands"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); String term = "\n"; @@ -244,7 +247,6 @@ public void testStreamCommands() { */ @Test public void testPauseSendAndResumeSend() { - System.out.println("pauseSend"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); String twentyCharString = "twenty characters..."; @@ -293,7 +295,6 @@ public void testPauseSendAndResumeSend() { */ @Test public void testCancelSend() { - System.out.println("cancelSend"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); String twentyCharString = "twenty characters..."; @@ -348,7 +349,6 @@ public void testCancelSend() { */ @Test public void testSoftReset() { - System.out.println("softReset"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); String twentyCharString = "twenty characters..."; @@ -368,7 +368,6 @@ public void testSoftReset() { @Test public void errorResponseShouldPauseTheCommunicator() { - System.out.println("streamCommands"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); instance.setSingleStepMode(true); @@ -425,7 +424,6 @@ public void errorResponseShouldPauseTheCommunicator() { @Test public void errorResponseOnLastCommandInStreamShouldNotPauseTheCommunicator() { - System.out.println("streamCommands"); MockConnection mc = new MockConnection(mg.in, mg.out); GrblCommunicator instance = new GrblCommunicator(cb, asl, new CommunicatorEventDispatcher(), mc); instance.setSingleStepMode(true); @@ -458,4 +456,16 @@ public void errorResponseOnLastCommandInStreamShouldNotPauseTheCommunicator() { assertEquals(0, cb.size()); assertFalse("The communicator should not be paused when last command has an error", instance.isPaused()); } + + @Test + public void onConnectionClosedShouldDispatchEventUpstream() { + MockConnection mc = new MockConnection(mg.in, mg.out); + ICommunicatorEventDispatcher eventDispatcher = mock(ICommunicatorEventDispatcher.class); + GrblCommunicator instance = new GrblCommunicator(cb, asl, eventDispatcher, mc); + + instance.onConnectionClosed(); + + verify(eventDispatcher, times(1)).onConnectionClosed(); + verifyNoMoreInteractions(eventDispatcher); + } } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/connection/JSerialCommConnectionTest.java b/ugs-core/test/com/willwinder/universalgcodesender/connection/JSerialCommConnectionTest.java new file mode 100644 index 000000000..ecf38578e --- /dev/null +++ b/ugs-core/test/com/willwinder/universalgcodesender/connection/JSerialCommConnectionTest.java @@ -0,0 +1,54 @@ +package com.willwinder.universalgcodesender.connection; + +import com.fazecast.jSerialComm.SerialPort; +import com.fazecast.jSerialComm.SerialPortEvent; +import org.junit.Test; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class JSerialCommConnectionTest { + + @Test + public void serialEvent_shouldDispatchDisconnectEventsWhenDeviceIsClosed() { + SerialPort serialPort = mock(SerialPort.class); + JSerialCommConnection connection = new JSerialCommConnection(serialPort); + + IConnectionListener listener = mock(IConnectionListener.class); + connection.addListener(listener); + + SerialPortEvent event = new SerialPortEvent(serialPort, SerialPort.LISTENING_EVENT_PORT_DISCONNECTED); + connection.serialEvent(event); + + verify(listener, times(1)).onConnectionClosed(); + verifyNoMoreInteractions(listener); + + // This to ensure that not double disconnect commands are dispatched + verify(serialPort, times(0)).removeDataListener(); + verify(serialPort, times(0)).closePort(); + verifyNoMoreInteractions(serialPort); + } + + @Test + public void serialEvent_shouldDispatchDisconnectEventsWhenDisconnected() throws Exception { + SerialPort serialPort = mock(SerialPort.class); + doNothing().when(serialPort).removeDataListener(); + JSerialCommConnection connection = new JSerialCommConnection(serialPort); + + IConnectionListener listener = mock(IConnectionListener.class); + connection.addListener(listener); + + connection.closePort(); + + verify(serialPort, times(1)).removeDataListener(); + verify(serialPort, times(1)).closePort(); + verifyNoMoreInteractions(serialPort); + + // This to ensure that not double disconnect commands are dispatched + verify(listener, times(0)).onConnectionClosed(); + verifyNoMoreInteractions(listener); + } + +} \ No newline at end of file diff --git a/ugs-core/test/com/willwinder/universalgcodesender/connection/ResponseMessageHandlerTest.java b/ugs-core/test/com/willwinder/universalgcodesender/connection/ResponseMessageHandlerTest.java index 60ad83487..454399764 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/connection/ResponseMessageHandlerTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/connection/ResponseMessageHandlerTest.java @@ -29,11 +29,11 @@ This file is part of Universal Gcode Sender (UGS). public class ResponseMessageHandlerTest { - private ResponseMessageHandler responseMessageHandler; + private ConnectionListenerManager responseMessageHandler; @Before public void setUp() { - responseMessageHandler = new ResponseMessageHandler(); + responseMessageHandler = new ConnectionListenerManager(); } @Test diff --git a/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCControllerTest.java b/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCControllerTest.java index 40cbab732..f7ea6e379 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCControllerTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCControllerTest.java @@ -4,6 +4,7 @@ import com.willwinder.universalgcodesender.gcode.GcodeState; import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.listeners.ControllerListener; +import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; import com.willwinder.universalgcodesender.listeners.ControllerStatusBuilder; import com.willwinder.universalgcodesender.model.Position; @@ -11,19 +12,25 @@ import com.willwinder.universalgcodesender.types.GcodeCommand; import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; import com.willwinder.universalgcodesender.utils.SimpleGcodeStreamReader; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import static org.mockito.ArgumentMatchers.any; import org.mockito.InOrder; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import java.io.IOException; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - public class FluidNCControllerTest { private FluidNCController target; @@ -39,7 +46,7 @@ public void setUp() { public void executeReturnToHomeShouldAddSafetyHeightWhenBelow() throws Exception { when(target.isIdle()).thenReturn(true); mockGcodeState(); - mockControllerStatus(new Position(0, 0, 5, UnitUtils.Units.MM)); + mockControllerStatus(ControllerState.IDLE, new Position(0, 0, 5, UnitUtils.Units.MM)); ArgumentCaptor commandArgumentCaptor = ArgumentCaptor.forClass(GcodeCommand.class); doNothing().when(target).sendCommandImmediately(commandArgumentCaptor.capture()); @@ -57,7 +64,7 @@ public void executeReturnToHomeShouldAddSafetyHeightWhenBelow() throws Exception public void executeReturnToHomeShouldNotAddSafetyHeightWhenOver() throws Exception { when(target.isIdle()).thenReturn(true); mockGcodeState(); - mockControllerStatus(new Position(0, 0, 11, UnitUtils.Units.MM)); + mockControllerStatus(ControllerState.IDLE, new Position(0, 0, 11, UnitUtils.Units.MM)); ArgumentCaptor commandArgumentCaptor = ArgumentCaptor.forClass(GcodeCommand.class); doNothing().when(target).sendCommandImmediately(commandArgumentCaptor.capture()); @@ -161,8 +168,22 @@ public void restoreParserModalStateShouldRestoreRelativeMode() { assertEquals("G91", commandArgumentCaptor.getValue().getCommandString()); } - private void mockControllerStatus(Position workPosition) { + @Test + public void onConnectionClosedShouldDisconnectController() throws Exception { + when(target.isIdle()).thenReturn(true); + target.rawResponseListener(""); + + assertEquals(ControllerState.IDLE, target.getControllerStatus().getState()); + + target.onConnectionClosed(); + + verify(communicator, times(1)).disconnect(); + assertEquals(ControllerState.DISCONNECTED, target.getControllerStatus().getState()); + } + + private void mockControllerStatus(ControllerState state, Position workPosition) { ControllerStatus controllerStatus = ControllerStatusBuilder.newInstance() + .setState(state) .setWorkCoord(workPosition) .build(); when(target.getControllerStatus()).thenReturn(controllerStatus); diff --git a/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/commands/GetErrorCodesCommandTest.java b/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/commands/GetErrorCodesCommandTest.java index 1f946fb46..7ed280e32 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/commands/GetErrorCodesCommandTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/firmware/fluidnc/commands/GetErrorCodesCommandTest.java @@ -1,11 +1,11 @@ package com.willwinder.universalgcodesender.firmware.fluidnc.commands; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Test; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.Assert.*; - public class GetErrorCodesCommandTest { @Test public void appendResponseWithErrorCodes() throws InterruptedException { @@ -85,7 +85,7 @@ public void appendResponseWithErrorCodes() throws InterruptedException { command.appendResponse("ok"); // Wait for the listener to complete - Thread.sleep(200); + Thread.sleep(500); assertTrue(command.isOk()); assertEquals(1, eventsCounter.get()); diff --git a/ugs-core/test/com/willwinder/universalgcodesender/mockobjects/MockConnection.java b/ugs-core/test/com/willwinder/universalgcodesender/mockobjects/MockConnection.java index f0e121275..9a202cc82 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/mockobjects/MockConnection.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/mockobjects/MockConnection.java @@ -54,7 +54,7 @@ public MockConnection(final InputStream in, final OutputStream out) { } public void sendResponse(String str) { - this.responseMessageHandler.notifyListeners(str); + this.connectionListenerManager.notifyListeners(str); } @Override @@ -76,11 +76,6 @@ public boolean isOpen() { return true; } - @Override - public List getPortNames() { - return Arrays.asList("port"); - } - @Override public List getDevices() { return Arrays.asList(new DefaultConnectionDevice("port")); diff --git a/ugs-pendant/src/main/java/com/willwinder/universalgcodesender/pendantui/v1/resources/MachineResource.java b/ugs-pendant/src/main/java/com/willwinder/universalgcodesender/pendantui/v1/resources/MachineResource.java index 0288fd47f..729344083 100644 --- a/ugs-pendant/src/main/java/com/willwinder/universalgcodesender/pendantui/v1/resources/MachineResource.java +++ b/ugs-pendant/src/main/java/com/willwinder/universalgcodesender/pendantui/v1/resources/MachineResource.java @@ -23,6 +23,7 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.IController; import com.willwinder.universalgcodesender.connection.ConnectionDriver; import com.willwinder.universalgcodesender.connection.ConnectionFactory; +import com.willwinder.universalgcodesender.connection.IConnectionDevice; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.BackendAPI; import com.willwinder.universalgcodesender.model.BaudRateEnum; @@ -81,7 +82,9 @@ public void disconnect() throws Exception { @Produces(MediaType.APPLICATION_JSON) public List getPortList() { ConnectionDriver connectionDriver = SettingsFactory.loadSettings().getConnectionDriver(); - return ConnectionFactory.getPortNames(connectionDriver); + return ConnectionFactory.getDevices(connectionDriver).stream() + .map(IConnectionDevice::getAddress) + .toList(); } @GET diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/ui/PortComboBox.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/ui/PortComboBox.java index 4cde8f091..df505f448 100644 --- a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/ui/PortComboBox.java +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/ui/PortComboBox.java @@ -66,9 +66,9 @@ private void refreshPorts() { return; } - List availablePorts = ConnectionFactory.getDevices(backend.getSettings().getConnectionDriver()); + List availablePorts = ConnectionFactory.getDevices(backend.getSettings().getConnectionDriver()); PortComboBoxModel model = (PortComboBoxModel) getModel(); - model.setElements(availablePorts); + model.setElements(availablePorts.stream().map(s -> (IConnectionDevice)(s)).toList()); } @Override