/* * An Abstract communicator interface which implements listeners. */ /* Copywrite 2013-2016 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 <http://www.gnu.org/licenses/>. */ package com.willwinder.universalgcodesender; import static com.willwinder.universalgcodesender.AbstractCommunicator.SerialCommunicatorEvent.*; import com.willwinder.universalgcodesender.connection.Connection; import com.willwinder.universalgcodesender.connection.ConnectionFactory; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.listeners.SerialCommunicatorListener; import com.willwinder.universalgcodesender.types.GcodeCommand; import com.willwinder.universalgcodesender.utils.GcodeStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.LinkedBlockingDeque; /** * * @author wwinder */ public abstract class AbstractCommunicator { public static String DEFAULT_TERMINATOR = "\r\n"; protected Connection conn; private int commandCounter = 0; // Allow events to be sent from same thread for unit tests. private boolean launchEventsInDispatchThread = true; // Serial Communicator Listener Events enum SerialCommunicatorEvent { COMMAND_SENT, COMMAND_SKIPPED, RAW_RESPONSE, CONSOLE_MESSAGE, VERBOSE_CONSOLE_MESSAGE } // Callback interfaces private ArrayList<SerialCommunicatorListener> commandEventListeners; private ArrayList<SerialCommunicatorListener> commConsoleListeners; private ArrayList<SerialCommunicatorListener> commVerboseConsoleListeners; private ArrayList<SerialCommunicatorListener> commRawResponseListener; private HashMap<SerialCommunicatorEvent, ArrayList<SerialCommunicatorListener>> eventMap; public AbstractCommunicator() { this.commandEventListeners = new ArrayList<>(); this.commConsoleListeners = new ArrayList<>(); this.commVerboseConsoleListeners = new ArrayList<>(); this.commRawResponseListener = new ArrayList<>(); this.eventMap = new HashMap<>(); eventMap.put(SerialCommunicatorEvent.COMMAND_SENT, commandEventListeners); eventMap.put(SerialCommunicatorEvent.COMMAND_SKIPPED, commandEventListeners); eventMap.put(SerialCommunicatorEvent.CONSOLE_MESSAGE, commConsoleListeners); eventMap.put(SerialCommunicatorEvent.VERBOSE_CONSOLE_MESSAGE, commVerboseConsoleListeners); eventMap.put(SerialCommunicatorEvent.RAW_RESPONSE, commRawResponseListener); } /*********************/ /* Serial Layer API. */ /*********************/ abstract public void setSingleStepMode(boolean enable); abstract public boolean getSingleStepMode(); abstract public void queueStringForComm(final String input); /** * Use GcodeStreamReader to allow GUIs to display better execution progress. */ @Deprecated abstract public void queueRawStreamForComm(final Reader input); abstract public void queueStreamForComm(final GcodeStreamReader input); abstract public void sendByteImmediately(byte b) throws Exception; abstract public String activeCommandSummary(); abstract public boolean areActiveCommands(); abstract public void streamCommands(); abstract public void pauseSend(); abstract public void resumeSend(); abstract public boolean isPaused(); abstract public void cancelSend(); abstract public void softReset(); abstract public void responseMessage(String response); abstract public int numActiveCommands(); /** * Reset any internal buffers. In case a controller reset was detected call * this. */ abstract public void resetBuffersInternal(); final public void resetBuffers() { if (eventQueue != null) { eventQueue.clear(); } resetBuffersInternal(); } public void setConnection(Connection c) { conn = c; } //do common operations (related to the connection, that is shared by all communicators) protected boolean openCommPort(String name, int baud) throws Exception { if (conn == null) { conn = ConnectionFactory.getConnectionFor(name, baud); } if (conn != null) { conn.setCommunicator(this); } if (conn==null) { throw new Exception(Localization.getString("communicator.exception.port") + ": "+name); } // Handle all events in a single thread. this.eventThread.start(); //open it return conn.openPort(name, baud); } public boolean isCommOpen() { return conn != null && conn.isOpen(); } //do common things (related to the connection, that is shared by all communicators) protected void closeCommPort() throws Exception { this.stop = true; this.eventThread.interrupt(); conn.closePort(); } protected int getNextCommandId() { return this.commandCounter++; } /** Getters & Setters. */ abstract public String getLineTerminator(); /* ****************** */ /** Listener helpers. */ /* ****************** */ void setListenAll(SerialCommunicatorListener scl) { this.addCommandEventListener(scl); this.addCommConsoleListener(scl); this.addCommVerboseConsoleListener(scl); this.addCommRawResponseListener(scl); } void addCommandEventListener(SerialCommunicatorListener scl) { this.commandEventListeners.add(scl); } void addCommConsoleListener(SerialCommunicatorListener scl) { this.commConsoleListeners.add(scl); } void addCommVerboseConsoleListener(SerialCommunicatorListener scl) { this.commVerboseConsoleListeners.add(scl); } void addCommRawResponseListener(SerialCommunicatorListener scl) { this.commRawResponseListener.add(scl); } // Helper for the console listener. protected void sendMessageToConsoleListener(String msg) { this.sendMessageToConsoleListener(msg, false); } protected void sendMessageToConsoleListener(String msg, boolean verbose) { // Exit early if there are no listeners. if (this.commConsoleListeners == null) { return; } SerialCommunicatorEvent verbosity; if (!verbose) { verbosity = SerialCommunicatorEvent.CONSOLE_MESSAGE; } else { verbosity = SerialCommunicatorEvent.VERBOSE_CONSOLE_MESSAGE; } dispatchListenerEvents(verbosity, msg); } /** * A bunch of methods to dispatch listener events with various arguments. */ protected void dispatchListenerEvents(final SerialCommunicatorEvent event, final String message) { dispatchListenerEvents(event, message, null); } protected void dispatchListenerEvents(final SerialCommunicatorEvent event, final GcodeCommand command) { dispatchListenerEvents(event, null, command); } private void dispatchListenerEvents(final SerialCommunicatorEvent event, final String string, final GcodeCommand command) { if (event == COMMAND_SENT || event == COMMAND_SKIPPED) { if (command == null) throw new IllegalArgumentException("Dispatching a COMMAND_SENT event requires a GcodeCommand object."); } else if (string == null) { throw new IllegalArgumentException("Dispatching a " +event+ " event requires a String object."); } final ArrayList<SerialCommunicatorListener> sclList = eventMap.get(event); if (launchEventsInDispatchThread) { this.eventQueue.add(new EventData(event, sclList, string, command)); } else { sendEventToListeners(event, sclList, string, command); } } private void sendEventToListeners(final SerialCommunicatorEvent event, ArrayList<SerialCommunicatorListener> sclList, String string, GcodeCommand command) { switch(event) { case COMMAND_SENT: for (SerialCommunicatorListener scl : sclList) scl.commandSent(command); break; case COMMAND_SKIPPED: for (SerialCommunicatorListener scl : sclList) scl.commandSkipped(command); break; case CONSOLE_MESSAGE: for (SerialCommunicatorListener scl : sclList) scl.messageForConsole(string); break; case VERBOSE_CONSOLE_MESSAGE: for (SerialCommunicatorListener scl : sclList) scl.verboseMessageForConsole(string); break; case RAW_RESPONSE: for (SerialCommunicatorListener scl : sclList) scl.rawResponseListener(string); default: } } /** * If commands complete very fast, like several comments in a row being * skipped, then multiple event handlers could process them out of order. To * prevent that from happening we use a blocking queue to add events in the * main thread, and process them in order a single event thread. */ private final LinkedBlockingDeque<EventData> eventQueue = new LinkedBlockingDeque<>(); private boolean stop = false; private Thread eventThread = new Thread(() -> { while (!stop) { try { EventData e = eventQueue.take(); sendEventToListeners(e.event, e.sclList, e.string, e.command); } catch (Exception e) {} } }); // Simple data class used to pass data to the event thread. private class EventData { public EventData( SerialCommunicatorEvent event, ArrayList<SerialCommunicatorListener> sclList, String string, GcodeCommand command) { this.sclList = sclList; this.event = event; this.command = command; this.string = string; } public ArrayList<SerialCommunicatorListener> sclList; public SerialCommunicatorEvent event; public GcodeCommand command; public String string; } }