/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.zwave.internal.protocol; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.Timer; import java.util.TimerTask; import java.util.TooManyListenersException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.openhab.binding.zwave.internal.protocol.SerialMessage.SerialMessageClass; import org.openhab.binding.zwave.internal.protocol.SerialMessage.SerialMessagePriority; import org.openhab.binding.zwave.internal.protocol.SerialMessage.SerialMessageType; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass.CommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClassDynamicState; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveMultiInstanceCommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSecurityCommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveSwitchAllCommandClass; import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveWakeUpCommandClass; import org.openhab.binding.zwave.internal.protocol.event.ZWaveEvent; import org.openhab.binding.zwave.internal.protocol.event.ZWaveInclusionEvent; import org.openhab.binding.zwave.internal.protocol.event.ZWaveNetworkEvent; import org.openhab.binding.zwave.internal.protocol.event.ZWaveNodeStatusEvent; import org.openhab.binding.zwave.internal.protocol.event.ZWaveTransactionCompletedEvent; import org.openhab.binding.zwave.internal.protocol.initialization.ZWaveNodeSerializer; import org.openhab.binding.zwave.internal.protocol.serialmessage.AddNodeMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.AssignReturnRouteMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.AssignSucReturnRouteMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.ControllerSetDefaultMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.DeleteReturnRouteMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.EnableSucMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.GetControllerCapabilitiesMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.GetRoutingInfoMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.GetSucNodeIdMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.GetVersionMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.IdentifyNodeMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.IsFailedNodeMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.MemoryGetIdMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.RemoveFailedNodeMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.RemoveNodeMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.RequestNodeInfoMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.RequestNodeNeighborUpdateMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.SendDataMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.SerialApiGetCapabilitiesMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.SerialApiGetInitDataMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.SerialApiSetTimeoutsMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.SerialApiSoftResetMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.SetSucNodeMessageClass; import org.openhab.binding.zwave.internal.protocol.serialmessage.ZWaveCommandProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.io.CommPort; import gnu.io.CommPortIdentifier; import gnu.io.NoSuchPortException; import gnu.io.PortInUseException; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import gnu.io.UnsupportedCommOperationException; /** * ZWave controller class. Implements communication with the Z-Wave * controller stick using serial messages. * * @author Victor Belov * @author Brian Crosby * @author Chris Jackson * @since 1.3.0 */ public class ZWaveController { private static final Logger logger = LoggerFactory.getLogger(ZWaveController.class); private static final int ZWAVE_RESPONSE_TIMEOUT = 5000; // 5000 ms ZWAVE_RESPONSE TIMEOUT private static final int ZWAVE_RECEIVE_TIMEOUT = 1000; // 1000 ms ZWAVE_RECEIVE_TIMEOUT private static final int INITIAL_TX_QUEUE_SIZE = 128; private static final int INITIAL_RX_QUEUE_SIZE = 8; private static final long WATCHDOG_TIMER_PERIOD = 10000; // 10 seconds watchdog timer public static final int TRANSMIT_OPTION_ACK = 0x01; public static final int TRANSMIT_OPTION_AUTO_ROUTE = 0x04; private static final int TRANSMIT_OPTION_EXPLORE = 0x20; private final ConcurrentHashMap<Integer, ZWaveNode> zwaveNodes = new ConcurrentHashMap<Integer, ZWaveNode>(); private final ArrayList<ZWaveEventListener> zwaveEventListeners = new ArrayList<ZWaveEventListener>(); private final PriorityBlockingQueue<SerialMessage> sendQueue = new PriorityBlockingQueue<SerialMessage>( INITIAL_TX_QUEUE_SIZE, new SerialMessage.SerialMessageComparator(this)); private final PriorityBlockingQueue<SerialMessage> recvQueue = new PriorityBlockingQueue<SerialMessage>( INITIAL_RX_QUEUE_SIZE, new SerialMessage.SerialMessageComparator(this)); private ZWaveSendThread sendThread; private ZWaveReceiveThread receiveThread; private ZWaveInputThread inputThread; private final Semaphore sendAllowed = new Semaphore(1); private final Semaphore transactionCompleted = new Semaphore(1); private volatile SerialMessage lastSentMessage = null; private long lastMessageStartTime = 0; private long longestResponseTime = 0; private SerialPort serialPort; private int zWaveResponseTimeout = ZWAVE_RESPONSE_TIMEOUT; private Timer watchdog; private String zWaveVersion = "Unknown"; private String serialAPIVersion = "Unknown"; private int homeId = 0; private int ownNodeId = 0; private int manufactureId = 0; private int deviceType = 0; private int deviceId = 0; private int ZWaveLibraryType = 0; private int sentDataPointer = 1; private boolean setSUC = false; private ZWaveDeviceType controllerType = ZWaveDeviceType.UNKNOWN; private int sucID = 0; private boolean softReset = false; private boolean masterController = false; private int SOFCount = 0; private int CANCount = 0; private int NAKCount = 0; private int ACKCount = 0; private int OOFCount = 0; private AtomicInteger timeOutCount = new AtomicInteger(0); private boolean isConnected; /** * This is required for secure pairing. see {@link ZWaveSecurityCommandClass} */ private ZWaveInclusionEvent lastIncludeSlaveFoundEvent; // Constructors /** * Constructor. Creates a new instance of the Z-Wave controller class. * * @param serialPortName the serial port name to use for * communication with the Z-Wave controller stick. * @throws SerialInterfaceException when a connection error occurs. */ public ZWaveController(final boolean masterController, final boolean isSUC, final String serialPortName, final Integer timeout, final boolean reset) throws SerialInterfaceException { logger.info("Starting Z-Wave controller"); this.masterController = masterController; this.setSUC = isSUC; this.softReset = reset; if (timeout != null && timeout >= 1500 && timeout <= 10000) { zWaveResponseTimeout = timeout; } logger.info("Z-Wave timeout is set to {}ms. Soft reset is {}.", zWaveResponseTimeout, reset); connect(serialPortName); this.watchdog = new Timer(true); this.watchdog.schedule(new WatchDogTimerTask(serialPortName), WATCHDOG_TIMER_PERIOD, WATCHDOG_TIMER_PERIOD); // We have a delay in running the initialisation sequence to allow any // frames queued in the controller to be received before sending the init // sequence. This avoids protocol errors (CAN errors). Timer initTimer = new Timer(); initTimer.schedule(new InitializeDelayTask(), 3000); } private class InitializeDelayTask extends TimerTask { private final Logger logger = LoggerFactory.getLogger(WatchDogTimerTask.class); /** * {@inheritDoc} */ @Override public void run() { logger.debug("Initialising network"); initialize(); } } // Incoming message handlers /** * Handles incoming Serial Messages. Serial messages can either be messages * that are a response to our own requests, or the stick asking us information. * * @param incomingMessage the incoming message to process. */ private void handleIncomingMessage(SerialMessage incomingMessage) { logger.debug(incomingMessage.toString()); switch (incomingMessage.getMessageType()) { case Request: handleIncomingRequestMessage(incomingMessage); break; case Response: handleIncomingResponseMessage(incomingMessage); break; default: logger.warn("Unsupported incomingMessageType: {}", incomingMessage.getMessageType()); } } /** * Handles an incoming request message. * An incoming request message is a message initiated by a node or the controller. * * @param incomingMessage the incoming message to process. */ private void handleIncomingRequestMessage(SerialMessage incomingMessage) { logger.trace("Incoming Message type = REQUEST"); ZWaveCommandProcessor processor = ZWaveCommandProcessor.getMessageDispatcher(incomingMessage.getMessageClass()); if (processor == null) { logger.warn(String.format("TODO: Implement processing of Request Message = %s (0x%02X)", incomingMessage.getMessageClass().getLabel(), incomingMessage.getMessageClass().getKey())); return; } boolean result = processor.handleRequest(this, lastSentMessage, incomingMessage); if (processor.isTransactionComplete()) { notifyEventListeners(new ZWaveTransactionCompletedEvent(this.lastSentMessage, result)); transactionCompleted.release(); logger.trace("Released. Transaction completed permit count -> {}", transactionCompleted.availablePermits()); } } /** * Handles a failed SendData request. This can either be because of the stick actively reporting it * or because of a time-out of the transaction in the send thread. * * @param originalMessage the original message that was sent */ private void handleFailedSendDataRequest(SerialMessage originalMessage) { new SendDataMessageClass().handleFailedSendDataRequest(this, originalMessage); } /** * Handles an incoming response message. * An incoming response message is a response, based one of our own requests. * * @param incomingMessage the response message to process. */ private void handleIncomingResponseMessage(SerialMessage incomingMessage) { logger.trace("Incoming Message type = RESPONSE"); ZWaveCommandProcessor processor = ZWaveCommandProcessor.getMessageDispatcher(incomingMessage.getMessageClass()); if (processor == null) { logger.warn(String.format("TODO: Implement processing of Response Message = %s (0x%02X)", incomingMessage.getMessageClass().getLabel(), incomingMessage.getMessageClass().getKey())); return; } boolean result = processor.handleResponse(this, lastSentMessage, incomingMessage); if (processor.isTransactionComplete()) { notifyEventListeners(new ZWaveTransactionCompletedEvent(this.lastSentMessage, result)); transactionCompleted.release(); logger.trace("Released. Transaction completed permit count -> {}", transactionCompleted.availablePermits()); } switch (incomingMessage.getMessageClass()) { case GetVersion: this.zWaveVersion = ((GetVersionMessageClass) processor).getVersion(); this.ZWaveLibraryType = ((GetVersionMessageClass) processor).getLibraryType(); break; case MemoryGetId: this.ownNodeId = ((MemoryGetIdMessageClass) processor).getNodeId(); this.homeId = ((MemoryGetIdMessageClass) processor).getHomeId(); break; case SerialApiGetInitData: this.isConnected = true; for (Integer nodeId : ((SerialApiGetInitDataMessageClass) processor).getNodes()) { addNode(nodeId); } break; case GetSucNodeId: // Remember the SUC ID this.sucID = ((GetSucNodeIdMessageClass) processor).getSucNodeId(); // If we want to be the SUC, enable it here if (this.setSUC == true && this.sucID == 0) { // We want to be SUC this.enqueue(new EnableSucMessageClass().doRequest(EnableSucMessageClass.SUCType.SERVER)); this.enqueue(new SetSucNodeMessageClass().doRequest(this.ownNodeId, SetSucNodeMessageClass.SUCType.SERVER)); } else if (this.setSUC == false && this.sucID == this.ownNodeId) { // We don't want to be SUC, but we currently are! // Disable SERVER functionality, and set the node to 0 this.enqueue(new EnableSucMessageClass().doRequest(EnableSucMessageClass.SUCType.NONE)); this.enqueue(new SetSucNodeMessageClass().doRequest(this.ownNodeId, SetSucNodeMessageClass.SUCType.NONE)); } this.enqueue(new GetControllerCapabilitiesMessageClass().doRequest()); break; case SerialApiGetCapabilities: this.serialAPIVersion = ((SerialApiGetCapabilitiesMessageClass) processor).getSerialAPIVersion(); this.manufactureId = ((SerialApiGetCapabilitiesMessageClass) processor).getManufactureId(); this.deviceId = ((SerialApiGetCapabilitiesMessageClass) processor).getDeviceId(); this.deviceType = ((SerialApiGetCapabilitiesMessageClass) processor).getDeviceType(); this.enqueue(new SerialApiGetInitDataMessageClass().doRequest()); break; case GetControllerCapabilities: this.controllerType = ((GetControllerCapabilitiesMessageClass) processor).getDeviceType(); break; default: break; } } // Controller methods /** * Connects to the comm port and starts send and receive threads. * * @param serialPortName the port name to open * @throws SerialInterfaceException when a connection error occurs. */ public void connect(final String serialPortName) throws SerialInterfaceException { logger.info("Connecting to serial port {}", serialPortName); try { CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName); CommPort commPort = portIdentifier.open("org.openhab.binding.zwave", 2000); this.serialPort = (SerialPort) commPort; this.serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); this.serialPort.enableReceiveThreshold(1); this.serialPort.enableReceiveTimeout(ZWAVE_RECEIVE_TIMEOUT); this.receiveThread = new ZWaveReceiveThread(); this.receiveThread.start(); this.sendThread = new ZWaveSendThread(); this.sendThread.start(); this.inputThread = new ZWaveInputThread(); this.inputThread.start(); // RXTX serial port library causes high CPU load // Start event listener, which will just sleep and slow down event loop serialPort.addEventListener(this.receiveThread); serialPort.notifyOnDataAvailable(true); logger.info("Serial port is initialized"); } catch (NoSuchPortException e) { logger.error("Serial Error: Port {} does not exist", serialPortName); throw new SerialInterfaceException(String.format("Port %s does not exist", serialPortName), e); } catch (PortInUseException e) { logger.error("Serial Error: Port {} in use.", serialPortName); throw new SerialInterfaceException(String.format("Port %s in use.", serialPortName), e); } catch (UnsupportedCommOperationException e) { logger.error("Serial Error: Unsupported comm operation on Port {}.", serialPortName); throw new SerialInterfaceException(String.format("Unsupported comm operation on Port %s.", serialPortName), e); } catch (TooManyListenersException e) { logger.error("Serial Error: Too many listeners on Port {}.", serialPortName); e.printStackTrace(); } } /** * Closes the connection to the Z-Wave controller. */ public void close() { logger.debug("Stopping Z-Wave controller"); if (watchdog != null) { watchdog.cancel(); watchdog = null; } disconnect(); // clear nodes collection and send queue ArrayList<ZWaveEventListener> copy = new ArrayList<ZWaveEventListener>(this.zwaveEventListeners); for (Object listener : copy.toArray()) { if (!(listener instanceof ZWaveNode)) { continue; } this.zwaveEventListeners.remove(listener); } this.zwaveNodes.clear(); this.sendQueue.clear(); this.recvQueue.clear(); logger.info("Stopped Z-Wave controller"); } /** * Disconnects from the serial interface and stops * send and receive threads. */ public void disconnect() { logger.debug("Disconnecting from serial port..."); if (sendThread != null) { sendThread.interrupt(); try { sendThread.join(); } catch (InterruptedException e) { } sendThread = null; } if (receiveThread != null) { receiveThread.interrupt(); try { receiveThread.join(); } catch (InterruptedException e) { } receiveThread = null; } if (inputThread != null) { inputThread.interrupt(); try { inputThread.join(); } catch (InterruptedException e) { } inputThread = null; } if (transactionCompleted.availablePermits() < 0) { transactionCompleted.release(transactionCompleted.availablePermits()); } transactionCompleted.drainPermits(); if (this.serialPort != null) { this.serialPort.close(); this.serialPort = null; } logger.info("Disconnected from serial port"); } /** * Removes the node, and restarts the initialisation sequence * * @param nodeId */ public void reinitialiseNode(int nodeId) { this.zwaveNodes.remove(nodeId); addNode(nodeId); } /** * Add a node to the controller * * @param nodeId the node number to add */ private void addNode(int nodeId) { new ZWaveInitNodeThread(this, nodeId).start(); } /** * Send All On message to all devices that support the Switch All command class */ public void allOn() { Enumeration<Integer> nodeIds = this.zwaveNodes.keys(); while (nodeIds.hasMoreElements()) { Integer nodeId = nodeIds.nextElement(); ZWaveNode node = this.getNode(nodeId); ZWaveSwitchAllCommandClass switchAllCommandClass = (ZWaveSwitchAllCommandClass) node .getCommandClass(ZWaveCommandClass.CommandClass.SWITCH_ALL); if (switchAllCommandClass != null) { logger.debug("NODE {}: Supports Switch All Command Class Sending AllOn Message", nodeId); switchAllCommandClass.setNode(node); switchAllCommandClass.setController(this); this.enqueue(switchAllCommandClass.allOnMessage()); continue; } } } /** * Send All Off message to all devices that support the Switch All command class */ public void allOff() { Enumeration<Integer> nodeIds = this.zwaveNodes.keys(); while (nodeIds.hasMoreElements()) { Integer nodeId = nodeIds.nextElement(); ZWaveNode node = this.getNode(nodeId); ZWaveSwitchAllCommandClass switchAllCommandClass = (ZWaveSwitchAllCommandClass) node .getCommandClass(ZWaveCommandClass.CommandClass.SWITCH_ALL); if (switchAllCommandClass != null) { logger.debug("NODE {}: Supports Switch All Command Class Sending AllOff Message", nodeId); switchAllCommandClass.setNode(node); switchAllCommandClass.setController(this); this.enqueue(switchAllCommandClass.allOffMessage()); continue; } } } private class ZWaveInitNodeThread extends Thread { int nodeId; ZWaveController controller; ZWaveInitNodeThread(ZWaveController controller, int nodeId) { this.nodeId = nodeId; this.controller = controller; } @Override public void run() { logger.debug("NODE {}: Init node thread start", nodeId); // Check if the node exists if (zwaveNodes.get(nodeId) != null) { logger.warn("NODE {}: Attempting to add node that already exists", nodeId); return; } ZWaveNode node = null; try { ZWaveNodeSerializer nodeSerializer = new ZWaveNodeSerializer(); node = nodeSerializer.DeserializeNode(nodeId); } catch (Exception e) { logger.error("NODE {}: Restore from config: Error deserialising XML file. {}", nodeId, e.toString()); node = null; } String name = null; String location = null; // Did the node deserialise ok? if (node != null) { // Remember the name and location - in case we decide the file was invalid name = node.getName(); location = node.getLocation(); // Sanity check the data from the file if (node.getManufacturer() == Integer.MAX_VALUE || node.getHomeId() != controller.homeId || node.getNodeId() != nodeId) { logger.warn("NODE {}: Restore from config: Error. Data invalid, ignoring config.", nodeId); node = null; } else { // The restore was ok, but we have some work to set up the links that aren't // made as the deserialiser doesn't call the constructor logger.debug("NODE {}: Restore from config: Ok.", nodeId); node.setRestoredFromConfigfile(controller); // Set the controller and node references for all command classes for (ZWaveCommandClass commandClass : node.getCommandClasses()) { commandClass.setController(controller); commandClass.setNode(node); // Handle event handlers if (commandClass instanceof ZWaveEventListener) { controller.addEventListener((ZWaveEventListener) commandClass); } // If this is the multi-instance class, add all command classes for the endpoints if (commandClass instanceof ZWaveMultiInstanceCommandClass) { for (ZWaveEndpoint endPoint : ((ZWaveMultiInstanceCommandClass) commandClass) .getEndpoints()) { for (ZWaveCommandClass endpointCommandClass : endPoint.getCommandClasses()) { endpointCommandClass.setController(controller); endpointCommandClass.setNode(node); endpointCommandClass.setEndpoint(endPoint); // Handle event handlers if (endpointCommandClass instanceof ZWaveEventListener) { controller.addEventListener((ZWaveEventListener) endpointCommandClass); } } } } } } } // Create a new node if it wasn't deserialised ok if (node == null) { node = new ZWaveNode(controller.homeId, nodeId, controller); // Try to maintain the name and location (user supplied data) // even if the XML file was considered corrupt and we reload data from the device. node.setName(name); node.setLocation(location); } if (nodeId == controller.ownNodeId) { // This is the controller node. // We already know the device type, id, manufacturer so set it here // It won't be set later as we probably won't request the manufacturer specific data node.setDeviceId(controller.getDeviceId()); node.setDeviceType(controller.getDeviceType()); node.setManufacturer(controller.getManufactureId()); } // Place nodes in the local ZWave Controller controller.zwaveNodes.putIfAbsent(nodeId, node); node.initialiseNode(); logger.debug("NODE {}: Init node thread finished", nodeId); } } /** * Enqueues a message for sending on the send queue. * * @param serialMessage the serial message to enqueue. */ public void enqueue(SerialMessage serialMessage) { // Sanity check! if (serialMessage == null) { return; } // First try and get the node // If we're sending to a node, then this obviously isn't to the controller, and we should // queue anything to a battery node (ie a node supporting the WAKEUP class)! ZWaveNode node = this.getNode(serialMessage.getMessageNode()); if (node != null) { // Does this message need to be security encapsulated? if (node.doesMessageRequireSecurityEncapsulation(serialMessage)) { ZWaveSecurityCommandClass securityCommandClass = (ZWaveSecurityCommandClass) node .getCommandClass(CommandClass.SECURITY); securityCommandClass.queueMessageForEncapsulationAndTransmission(serialMessage); // the above call will call enqueue again with the <b>encapsulated<b/> message, // so we discard this one without putting it on the queue return; } // Keep track of the number of packets sent to this device node.incrementSendCount(); // If the device isn't listening, queue the message if it supports the wakeup class if (!node.isListening() && !node.isFrequentlyListening()) { ZWaveWakeUpCommandClass wakeUpCommandClass = (ZWaveWakeUpCommandClass) node .getCommandClass(CommandClass.WAKE_UP); // If it's a battery operated device, check if it's awake or place in wake-up queue. if (wakeUpCommandClass != null && !wakeUpCommandClass.processOutgoingWakeupMessage(serialMessage)) { return; } } } // Add the message to the queue this.sendQueue.add(serialMessage); if (logger.isTraceEnabled()) { logger.debug("Enqueueing message. Queue length = {}, Queue = {}", this.sendQueue.size(), this.sendQueue); } else { logger.debug("Enqueueing message. Queue length = {}", this.sendQueue.size()); } } /** * Returns the size of the send queue. */ public int getSendQueueLength() { return this.sendQueue.size(); } /** * Notify our own event listeners of a Z-Wave event. * * @param event the event to send. */ public void notifyEventListeners(ZWaveEvent event) { logger.debug("NODE {}: Notifying event listeners: {}", event.getNodeId(), event.getClass().getSimpleName()); ArrayList<ZWaveEventListener> copy = new ArrayList<ZWaveEventListener>(this.zwaveEventListeners); for (ZWaveEventListener listener : copy) { listener.ZWaveIncomingEvent(event); } // We also need to handle the inclusion internally within the controller if (event instanceof ZWaveInclusionEvent) { ZWaveInclusionEvent incEvent = (ZWaveInclusionEvent) event; switch (incEvent.getEvent()) { case IncludeSlaveFound: // TODO: DB any potential negative consequences by changing from IncludeDone to // IncludeSlaveFound? // Trigger by IncludeSlaveFound since security based devices need the initial exchange to take place // immediately or they will time out logger.debug("NODE {}: Including node.", incEvent.getNodeId()); // First make sure this isn't an existing node if (getNode(incEvent.getNodeId()) != null) { logger.debug("NODE {}: Newly included node already exists - not initialising.", incEvent.getNodeId()); break; } this.lastIncludeSlaveFoundEvent = incEvent; // Initialise the new node addNode(incEvent.getNodeId()); break; case ExcludeDone: logger.debug("NODE {}: Excluding node.", incEvent.getNodeId()); // Remove the node from the controller if (getNode(incEvent.getNodeId()) == null) { logger.debug("NODE {}: Excluding node that doesn't exist.", incEvent.getNodeId()); break; } this.zwaveNodes.remove(incEvent.getNodeId()); // Remove the XML file ZWaveNodeSerializer nodeSerializer = new ZWaveNodeSerializer(); nodeSerializer.DeleteNode(event.getNodeId()); break; default: break; } } else if (event instanceof ZWaveNetworkEvent) { ZWaveNetworkEvent networkEvent = (ZWaveNetworkEvent) event; switch (networkEvent.getEvent()) { case DeleteNode: if (getNode(networkEvent.getNodeId()) == null) { logger.debug("NODE {}: Deleting a node that doesn't exist.", networkEvent.getNodeId()); break; } this.zwaveNodes.remove(networkEvent.getNodeId()); // Remove the XML file ZWaveNodeSerializer nodeSerializer = new ZWaveNodeSerializer(); nodeSerializer.DeleteNode(event.getNodeId()); break; default: break; } } else if (event instanceof ZWaveNodeStatusEvent) { ZWaveNodeStatusEvent statusEvent = (ZWaveNodeStatusEvent) event; logger.debug("NODE {}: Node Status event - Node is {}", statusEvent.getNodeId(), statusEvent.getState()); // Get the node ZWaveNode node = getNode(event.getNodeId()); if (node == null) { logger.error("NODE {}: Node is unknown!", statusEvent.getNodeId()); return; } // Handle node state changes switch (statusEvent.getState()) { case DEAD: break; case FAILED: break; case ALIVE: break; } } } /** * Initializes communication with the Z-Wave controller stick. */ public void initialize() { this.enqueue(new GetVersionMessageClass().doRequest()); this.enqueue(new MemoryGetIdMessageClass().doRequest()); this.enqueue(new SerialApiGetCapabilitiesMessageClass().doRequest()); this.enqueue(new SerialApiSetTimeoutsMessageClass().doRequest(150, 15)); this.enqueue(new GetSucNodeIdMessageClass().doRequest()); } /** * Send Identify Node message to the controller. * * @param nodeId the nodeId of the node to identify */ public void identifyNode(int nodeId) { this.enqueue(new IdentifyNodeMessageClass().doRequest(nodeId)); } /** * Send Request Node info message to the controller. * * @param nodeId the nodeId of the node to identify */ public void requestNodeInfo(int nodeId) { this.enqueue(new RequestNodeInfoMessageClass().doRequest(nodeId)); } /** * Polls a node for any dynamic information * * @param node */ public void pollNode(ZWaveNode node) { for (ZWaveCommandClass zwaveCommandClass : node.getCommandClasses()) { logger.trace("NODE {}: Inspecting command class {}", node.getNodeId(), zwaveCommandClass.getCommandClass().getLabel()); if (zwaveCommandClass instanceof ZWaveCommandClassDynamicState) { logger.debug("NODE {}: Found dynamic state command class {}", node.getNodeId(), zwaveCommandClass.getCommandClass().getLabel()); ZWaveCommandClassDynamicState zdds = (ZWaveCommandClassDynamicState) zwaveCommandClass; int instances = zwaveCommandClass.getInstances(); if (instances == 1) { Collection<SerialMessage> dynamicQueries = zdds.getDynamicValues(true); for (SerialMessage serialMessage : dynamicQueries) { sendData(serialMessage); } } else { for (int i = 1; i <= instances; i++) { Collection<SerialMessage> dynamicQueries = zdds.getDynamicValues(true); for (SerialMessage serialMessage : dynamicQueries) { sendData(node.encapsulate(serialMessage, zwaveCommandClass, i)); } } } } else if (zwaveCommandClass instanceof ZWaveMultiInstanceCommandClass) { ZWaveMultiInstanceCommandClass multiInstanceCommandClass = (ZWaveMultiInstanceCommandClass) zwaveCommandClass; for (ZWaveEndpoint endpoint : multiInstanceCommandClass.getEndpoints()) { for (ZWaveCommandClass endpointCommandClass : endpoint.getCommandClasses()) { logger.trace("NODE {}: Inspecting command class {} for endpoint {}", node.getNodeId(), endpointCommandClass.getCommandClass().getLabel(), endpoint.getEndpointId()); if (endpointCommandClass instanceof ZWaveCommandClassDynamicState) { logger.debug("NODE {}: Found dynamic state command class {}", node.getNodeId(), endpointCommandClass.getCommandClass().getLabel()); ZWaveCommandClassDynamicState zdds2 = (ZWaveCommandClassDynamicState) endpointCommandClass; Collection<SerialMessage> dynamicQueries = zdds2.getDynamicValues(true); for (SerialMessage serialMessage : dynamicQueries) { sendData(node.encapsulate(serialMessage, endpointCommandClass, endpoint.getEndpointId())); } } } } } } } /** * Request the node routing information. * * @param nodeId The address of the node to update */ public void requestNodeRoutingInfo(int nodeId) { this.enqueue(new GetRoutingInfoMessageClass().doRequest(nodeId)); } /** * Request the node neighbor list to be updated for the specified node. * Once this is complete, the requestNodeRoutingInfo will be called * automatically to update the data in the binding. * * @param nodeId The address of the node to update */ public void requestNodeNeighborUpdate(int nodeId) { this.enqueue(new RequestNodeNeighborUpdateMessageClass().doRequest(nodeId)); } /** * Puts the controller into inclusion mode to add new nodes */ public void requestAddNodesStart() { this.enqueue(new AddNodeMessageClass().doRequestStart(true)); logger.info("Starting inclusion mode"); } /** * Terminates the inclusion mode */ public void requestAddNodesStop() { this.enqueue(new AddNodeMessageClass().doRequestStop()); logger.info("Ending inclusion mode"); } /** * Puts the controller into exclusion mode to remove new nodes */ public void requestRemoveNodesStart() { this.enqueue(new RemoveNodeMessageClass().doRequestStart(true)); } /** * Terminates the exclusion mode */ public void requestRemoveNodesStop() { this.enqueue(new RemoveNodeMessageClass().doRequestStop()); } /** * Sends a request to perform a soft reset on the controller. * This will just reset the controller - probably similar to a power cycle. * It doesn't reinitialise the network, or change the network configuration. * * NOTE: At least for some (most!) sticks, this doesn't return a response. * Therefore, the number of retries is set to 1. * NOTE: On some (most!) ZWave-Plus sticks, this can cause the stick to hang. */ public void requestSoftReset() { logger.info("Performing soft reset on controller"); SerialMessage msg = new SerialApiSoftResetMessageClass().doRequest(); msg.attempts = 1; this.enqueue(msg); } /** * Sends a request to perform a hard reset on the controller. * This will reset the controller to its default, resetting the network completely */ public void requestHardReset() { logger.info("Performing hard reset on controller"); // Clear the queues // If we're resetting, there's no point in queuing messages! sendQueue.clear(); recvQueue.clear(); // Hard reset the stick - everything will be reset to factory default SerialMessage msg = new ControllerSetDefaultMessageClass().doRequest(); msg.attempts = 1; this.enqueue(msg); // Clear all the nodes and we'll reinitialise this.zwaveNodes.clear(); this.enqueue(new SerialApiGetInitDataMessageClass().doRequest()); } /** * Request if the node is currently marked as failed by the controller. * * @param nodeId The address of the node to check */ public void requestIsFailedNode(int nodeId) { this.enqueue(new IsFailedNodeMessageClass().doRequest(nodeId)); } /** * Removes a failed node from the network. * Note that this won't remove nodes that have not failed. * * @param nodeId The address of the node to remove */ public void requestRemoveFailedNode(int nodeId) { this.enqueue(new RemoveFailedNodeMessageClass().doRequest(nodeId)); } /** * Delete all return nodes from the specified node. This should be performed * before updating the routes * * @param nodeId */ public void requestDeleteAllReturnRoutes(int nodeId) { this.enqueue(new DeleteReturnRouteMessageClass().doRequest(nodeId)); } /** * Request the controller to set the return route between two nodes * * @param nodeId * Source node * @param destinationId * Destination node */ public void requestAssignReturnRoute(int nodeId, int destinationId) { this.enqueue(new AssignReturnRouteMessageClass().doRequest(nodeId, destinationId, getCallbackId())); } /** * Request the controller to set the return route from a node to the controller * * @param nodeId * Source node */ public void requestAssignSucReturnRoute(int nodeId) { this.enqueue(new AssignSucReturnRouteMessageClass().doRequest(nodeId, getCallbackId())); } /** * Returns the next callback ID * * @return callback ID */ public int getCallbackId() { if (++sentDataPointer > 0xFF) { sentDataPointer = 1; } logger.debug("Callback ID = {}", sentDataPointer); return sentDataPointer; } /** * Transmits the SerialMessage to a single Z-Wave Node. * Sets the transmission options as well. * * @param serialMessage the Serial message to send. */ public void sendData(SerialMessage serialMessage) { if (serialMessage == null) { logger.error("Null message for sendData"); return; } if (serialMessage.getMessageClass() != SerialMessageClass.SendData) { logger.error(String.format("Invalid message class %s (0x%02X) for sendData", serialMessage.getMessageClass().getLabel(), serialMessage.getMessageClass().getKey())); return; } if (serialMessage.getMessageType() != SerialMessageType.Request) { logger.error("Only request messages can be sent"); return; } // We need to wait on the ACK from the controller before completing the transaction. // This is required in case the Application Message is received from the SendData ACK serialMessage.setAckRequired(); // ZWaveSecurityCommandClass needs to set it's own transmit options. Only set them here if not already done if (!serialMessage.areTransmitOptionsSet()) { serialMessage .setTransmitOptions(TRANSMIT_OPTION_ACK | TRANSMIT_OPTION_AUTO_ROUTE | TRANSMIT_OPTION_EXPLORE); } serialMessage.setCallbackId(getCallbackId()); this.enqueue(serialMessage); } /** * Add a listener for Z-Wave events to this controller. * * @param eventListener the event listener to add. */ public void addEventListener(ZWaveEventListener eventListener) { synchronized (this.zwaveEventListeners) { this.zwaveEventListeners.add(eventListener); } } /** * Remove a listener for Z-Wave events to this controller. * * @param eventListener the event listener to remove. */ public void removeEventListener(ZWaveEventListener eventListener) { synchronized (this.zwaveEventListeners) { this.zwaveEventListeners.remove(eventListener); } } /** * Gets the API Version of the controller. * * @return the serialAPIVersion */ public String getSerialAPIVersion() { return serialAPIVersion; } /** * Gets the zWave Version of the controller. * * @return the zWaveVersion */ public String getZWaveVersion() { return zWaveVersion; } /** * Gets the Manufacturer ID of the controller. * * @return the manufactureId */ public int getManufactureId() { return manufactureId; } /** * Gets the device type of the controller; * * @return the deviceType */ public int getDeviceType() { return deviceType; } /** * Gets the device ID of the controller. * * @return the deviceId */ public int getDeviceId() { return deviceId; } /** * Gets the node ID of the controller. * * @return the deviceId */ public int getOwnNodeId() { return ownNodeId; } /** * Gets the device type of the controller. * * @return the device type */ public ZWaveDeviceType getControllerType() { return controllerType; } /** * Gets the networks SUC controller ID. * * @return the device id of the SUC, or 0 if none exists */ public int getSucId() { return sucID; } /** * Returns true if the binding is the master controller in the network. * The master controller simply means that we get notifications. * * @return true if this is the master */ public boolean isMasterController() { return masterController; } /** * Gets the node object using it's node ID as key. * Returns null if the node is not found * * @param nodeId the Node ID of the node to get. * @return node object */ public ZWaveNode getNode(int nodeId) { return this.zwaveNodes.get(nodeId); } /** * Gets the node list * * @return */ public Collection<ZWaveNode> getNodes() { return this.zwaveNodes.values(); } /** * Indicates a working connection to the * Z-Wave controller stick and initialization complete * * @return isConnected; */ public boolean isConnected() { return isConnected; // && initializationComplete; } /** * Gets the number of Start Of Frames received. * * @return the sOFCount */ public int getSOFCount() { return SOFCount; } /** * Gets the number of Canceled Frames received. * * @return the cANCount */ public int getCANCount() { return CANCount; } /** * Gets the number of Not Acknowledged Frames received. * * @return the nAKCount */ public int getNAKCount() { return NAKCount; } /** * Gets the number of Acknowledged Frames received. * * @return the aCKCount */ public int getACKCount() { return ACKCount; } /** * Returns the number of Out of Order frames received. * * @return the OOFCount */ public int getOOFCount() { return OOFCount; } /** * Returns the number of Time-Outs while sending. * * @return the timeoutCount */ public int getTimeOutCount() { return timeOutCount.get(); } /** * This is required by {@link ZWaveSecurityCommandClass} for the secure pairing process. * {@link ZWaveSecurityCommandClass} can't use the event handling because the * object won't exist when this occurs, so we hold it here so {@link ZWaveSecurityCommandClass} * can access it * * @return */ public ZWaveInclusionEvent getLastIncludeSlaveFoundEvent() { return lastIncludeSlaveFoundEvent; } // Nested classes and enumerations /** * Input thread. This processes incoming messages - it decouples the receive thread, * which responds to messages from the controller, and the actual processing of messages * to ensure we respond to the controller in a timely manner * * @author Chris Jackson */ private class ZWaveInputThread extends Thread { private ZWaveInputThread() { super("ZWaveInputThread"); } /** * Run method. Runs the actual receiving process. */ @Override public void run() { logger.debug("Starting Z-Wave thread: Input"); SerialMessage recvMessage; while (!interrupted()) { try { if (recvQueue.size() == 0) { sendAllowed.release(); } recvMessage = recvQueue.take(); logger.debug("Receive queue TAKE: Length={}", recvQueue.size()); logger.debug("Process Message = {}", SerialMessage.bb2hex(recvMessage.getMessageBuffer())); handleIncomingMessage(recvMessage); sendAllowed.tryAcquire(); } catch (InterruptedException e) { break; } catch (Exception e) { logger.error("Exception during Z-Wave thread: Input.", e); } } logger.debug("Stopped Z-Wave thread: Input"); } } /** * Z-Wave controller Send Thread. Takes care of sending all messages. * It uses a semaphore to synchronize communication with the receiving thread. * * @author Jan-Willem Spuij * @author Chris Jackson * @since 1.3.0 */ private class ZWaveSendThread extends Thread { private final Logger logger = LoggerFactory.getLogger(ZWaveSendThread.class); private ZWaveSendThread() { super("ZWaveSendThread"); } /** * Run method. Runs the actual sending process. */ @Override public void run() { logger.debug("Starting Z-Wave thread: Send"); try { while (!interrupted()) { // To avoid sending lots of frames when we still have input frames to // process, we wait here until we've processed all receive frames if (!sendAllowed.tryAcquire(1, zWaveResponseTimeout, TimeUnit.MILLISECONDS)) { logger.warn("Receive queue TIMEOUT:", recvQueue.size()); continue; } sendAllowed.release(); // Take the next message from the send queue try { lastSentMessage = sendQueue.take(); logger.debug("Took message from queue for sending. Queue length = {}", sendQueue.size()); } catch (InterruptedException e1) { logger.error("InterruptedException during Z-Wave thread: sendQueue.take {}", e1); break; } // Check we got a message if (lastSentMessage == null) { continue; } // Get the node for this message ZWaveNode node = getNode(lastSentMessage.getMessageNode()); // If it's a battery device, it needs to be awake, or we queue the frame until it is. if (node != null && !node.isListening() && !node.isFrequentlyListening()) { ZWaveWakeUpCommandClass wakeUpCommandClass = (ZWaveWakeUpCommandClass) node .getCommandClass(CommandClass.WAKE_UP); // If it's a battery operated device, check if it's awake or place in wake-up queue. if (wakeUpCommandClass != null && !wakeUpCommandClass.processOutgoingWakeupMessage(lastSentMessage)) { continue; } } // A transaction consists of (up to) 4 parts -: // 1) We send a REQUEST to the controller. // 2) The controller sends a RESPONSE almost immediately. // This RESPONSE typically tells us that the message was, // or wasn't, added to the sticks queue. // 3) The controller sends a REQUEST once it's received // the response from the device. // We need to be aware that there is no synchronization of // messages between steps 2 and 3 so we can get other messages // received at step 3 that are not related to our original // request. // 4) We ultimately receive the requested message from the device // if we're requesting such a message. // // A transaction is generally completed at the completion of step 4. // However, for some messages, there may not be a further REQUEST // so the transaction is terminated at step 2. This is handled // by the serial message class processor by setting // transactionCompleted. // // It seems that some of these steps may occur out of order. For // example, the requested message at step 4 may be received before // the REQUEST at step 3. This can (I guess) occur if the message to // the device is received by the device, but the ACK back to the controller // is lost. The device then sends the requested data, and then finally // the ACK is received. // We cover this by setting an 'AckPending' flag in the sent message. // This needs to be cleared before the transacion is completed. // Clear the semaphore used to acknowledge the completed transaction. transactionCompleted.drainPermits(); // Send the REQUEST message TO the controller byte[] buffer = lastSentMessage.getMessageBuffer(); logger.debug("NODE {}: Sending REQUEST Message = {}", lastSentMessage.getMessageNode(), SerialMessage.bb2hex(buffer)); lastMessageStartTime = System.currentTimeMillis(); try { synchronized (serialPort.getOutputStream()) { serialPort.getOutputStream().write(buffer); serialPort.getOutputStream().flush(); logger.trace("Message SENT"); } } catch (IOException e) { logger.error("Got I/O exception {} during sending. exiting thread.", e.getLocalizedMessage()); break; } if (lastSentMessage instanceof SecurityEncapsulatedSerialMessage) { // now that we've sent the encapsulated version, replace lastSentMessage with the original // this is required because a resend requires a new nonce to be requested and a new // security encapsulated message to be built ((SecurityEncapsulatedSerialMessage) lastSentMessage).setTransmittedAt(); // Take the callbackid from the encapsulated version and copy it to the original message int callbackId = lastSentMessage.getCallbackId(); lastSentMessage = ((SecurityEncapsulatedSerialMessage) lastSentMessage) .getMessageBeingEncapsulated(); lastSentMessage.setCallbackId(callbackId); } // Now wait for the RESPONSE, or REQUEST message FROM the controller // This will terminate when the transactionCompleted flag gets set // So, this might complete on a RESPONSE if there's an error (or no further REQUEST expected) // or it might complete on a subsequent REQUEST. try { if (!transactionCompleted.tryAcquire(1, zWaveResponseTimeout, TimeUnit.MILLISECONDS)) { timeOutCount.incrementAndGet(); // If this is a SendData message, then we need to abort // This should only be sent if we didn't get the initial ACK!!! // So we need to check the ACK flag and only abort if it's not set if (lastSentMessage.getMessageClass() == SerialMessageClass.SendData && lastSentMessage.isAckPending()) { buffer = new SerialMessage(SerialMessageClass.SendDataAbort, SerialMessageType.Request, SerialMessageClass.SendData, SerialMessagePriority.Immediate) .getMessageBuffer(); logger.debug("NODE {}: Sending ABORT Message = {}", lastSentMessage.getMessageNode(), SerialMessage.bb2hex(buffer)); try { synchronized (serialPort.getOutputStream()) { serialPort.getOutputStream().write(buffer); serialPort.getOutputStream().flush(); } } catch (IOException e) { logger.error("Got I/O exception {} during sending. exiting thread.", e.getLocalizedMessage()); break; } } // Check if we've exceeded the number of retries. // Requeue if we're ok, otherwise discard the message if (--lastSentMessage.attempts >= 0) { logger.error("NODE {}: Timeout while sending message. Requeueing - {} attempts left!", lastSentMessage.getMessageNode(), lastSentMessage.attempts); if (lastSentMessage.getMessageClass() == SerialMessageClass.SendData) { handleFailedSendDataRequest(lastSentMessage); } else { enqueue(lastSentMessage); } } else { logger.warn("NODE {}: Too many retries. Discarding message: {}", lastSentMessage.getMessageNode(), lastSentMessage.toString()); } continue; } long responseTime = System.currentTimeMillis() - lastMessageStartTime; if (responseTime > longestResponseTime) { longestResponseTime = responseTime; } logger.debug("NODE {}: Response processed for callback id {} after {}ms/{}ms.", lastSentMessage.getMessageNode(), lastSentMessage.getCallbackId(), responseTime, longestResponseTime); logger.trace("Acquired. Transaction completed permit count -> {}", transactionCompleted.availablePermits()); } catch (InterruptedException e) { logger.error("InterruptedException during Z-Wave thread: tryAcquire", e); break; } } } catch (Exception e) { logger.error("Exception during Z-Wave thread: Send", e); } logger.debug("Stopped Z-Wave thread: Send"); } } /** * Z-Wave controller Receive Thread. Takes care of receiving all messages. * It uses a semaphore to synchronize communication with the sending thread. * * @author Jan-Willem Spuij * @since 1.3.0 */ private class ZWaveReceiveThread extends Thread implements SerialPortEventListener { private static final int SOF = 0x01; private static final int ACK = 0x06; private static final int NAK = 0x15; private static final int CAN = 0x18; private final Logger logger = LoggerFactory.getLogger(ZWaveReceiveThread.class); private ZWaveReceiveThread() { super("ZWaveReceiveThread"); } @Override public void serialEvent(SerialPortEvent arg0) { try { logger.trace("RXTX library CPU load workaround, sleep forever"); Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { } } /** * Sends 1 byte frame response. * * @param response the response code to send. */ private void sendResponse(int response) { try { synchronized (serialPort.getOutputStream()) { serialPort.getOutputStream().write(response); serialPort.getOutputStream().flush(); logger.trace("Response SENT"); } } catch (IOException e) { logger.error(e.getMessage()); } } /** * Processes incoming message and notifies event handlers. * * @param buffer the buffer to process. * @throws InterruptedException */ private void processIncomingMessage(byte[] buffer) throws InterruptedException { SerialMessage recvMessage = new SerialMessage(buffer); if (recvMessage.isValid) { logger.trace("Message is valid, sending ACK"); sendResponse(ACK); } else { logger.error("Message is not valid, discarding"); sendResponse(NAK); // The semaphore is acquired when we start the receive. // We need to release it now... if (recvQueue.size() == 0) { sendAllowed.release(); } return; } recvQueue.add(recvMessage); logger.debug("Receive queue ADD: Length={}", recvQueue.size()); } /** * Run method. Runs the actual receiving process. */ @Override public void run() { logger.debug("Starting Z-Wave thread: Receive"); try { // Send a NAK to resynchronise communications sendResponse(NAK); // If we want to do a soft reset on the serial interfaces, do it here. // It seems there's no response to this message, so sending it through // 'normal' channels will cause a timeout. if (softReset == true) { try { synchronized (serialPort.getOutputStream()) { SerialMessage resetMsg = new SerialApiSoftResetMessageClass().doRequest(); byte[] buffer = resetMsg.getMessageBuffer(); serialPort.getOutputStream().write(buffer); serialPort.getOutputStream().flush(); } } catch (IOException e) { logger.error("Error sending soft reset on initialisation: {}", e.getMessage()); } } while (!interrupted()) { int nextByte; try { nextByte = serialPort.getInputStream().read(); if (nextByte == -1) { continue; } } catch (IOException e) { logger.error("Got I/O exception {} during receiving. exiting thread.", e.getLocalizedMessage()); break; } switch (nextByte) { case SOF: // Use the sendAllowed semaphore to signal that the receive queue is not empty! try { sendAllowed.acquire(); } catch (InterruptedException e) { logger.debug("Interrupted waiting for 'sendAllowed.acquire'."); // break; } SOFCount++; int messageLength; try { messageLength = serialPort.getInputStream().read(); } catch (IOException e) { logger.error("Got I/O exception {} during receiving. exiting thread.", e.getLocalizedMessage()); sendAllowed.release(); break; } byte[] buffer = new byte[messageLength + 2]; buffer[0] = SOF; buffer[1] = (byte) messageLength; int total = 0; while (total < messageLength) { try { int read = serialPort.getInputStream().read(buffer, total + 2, messageLength - total); total += (read > 0 ? read : 0); } catch (IOException e) { logger.error("Got I/O exception {} during receiving. exiting thread.", e.getLocalizedMessage()); return; } } logger.debug("Receive Message = {}", SerialMessage.bb2hex(buffer)); processIncomingMessage(buffer); break; case ACK: ACKCount++; logger.trace("Received ACK"); break; case NAK: NAKCount++; logger.error("Protocol error (NAK), discarding"); transactionCompleted.release(); logger.trace("Released. Transaction completed permit count -> {}", transactionCompleted.availablePermits()); break; case CAN: CANCount++; logger.error("Protocol error (CAN), resending"); try { Thread.sleep(100); } catch (InterruptedException e) { break; } enqueue(lastSentMessage); transactionCompleted.release(); logger.trace("Released. Transaction completed permit count -> {}", transactionCompleted.availablePermits()); break; default: OOFCount++; logger.warn(String.format("Protocol error (OOF). Got 0x%02X. Sending NAK.", nextByte)); sendResponse(NAK); break; } } } catch (Exception e) { logger.error("Exception during Z-Wave thread: Receive", e); } logger.debug("Stopped Z-Wave thread: Receive"); serialPort.removeEventListener(); } } /** * WatchDogTimerTask class. Acts as a watch dog and * checks the serial threads to see whether they are * still running. * * @author Jan-Willem Spuij * @since 1.3.0 */ private class WatchDogTimerTask extends TimerTask { private final Logger logger = LoggerFactory.getLogger(WatchDogTimerTask.class); private final String serialPortName; /** * Creates a new instance of the WatchDogTimerTask class. * * @param serialPortName the serial port name to reconnect to * in case the serial threads have died. */ public WatchDogTimerTask(String serialPortName) { this.serialPortName = serialPortName; } /** * {@inheritDoc} */ @Override public void run() { logger.trace("Watchdog: Checking Serial threads"); if ((receiveThread != null && !receiveThread.isAlive()) || (sendThread != null && !sendThread.isAlive()) || (inputThread != null && !inputThread.isAlive())) { logger.warn("Threads not alive, respawning"); disconnect(); try { connect(serialPortName); } catch (SerialInterfaceException e) { logger.error("Unable to restart Serial threads: {}", e.getLocalizedMessage()); } } } } }