/** * 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.rfxcom.internal.connector; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.TooManyListenersException; import java.util.concurrent.CopyOnWriteArrayList; import javax.xml.bind.DatatypeConverter; import org.apache.commons.io.IOUtils; 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; /** * RFXCOM connector for serial port communication. * * @author Pauli Anttila, Evert van Es, Jürgen Richtsfeld * @since 1.2.0 */ public class RFXComSerialConnector implements RFXComConnectorInterface { private static final Logger logger = LoggerFactory.getLogger(RFXComSerialConnector.class); private static final List<RFXComEventListener> _listeners = new CopyOnWriteArrayList<RFXComEventListener>(); InputStream in = null; OutputStream out = null; SerialPort serialPort = null; Thread readerThread = null; private boolean ignoreReceiveBuffer = false; public RFXComSerialConnector() { } @Override public void connect(String device) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException { CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(device); CommPort commPort = portIdentifier.open(this.getClass().getName(), 2000); serialPort = (SerialPort) commPort; serialPort.setSerialPortParams(38400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); serialPort.enableReceiveThreshold(1); serialPort.disableReceiveTimeout(); in = serialPort.getInputStream(); out = serialPort.getOutputStream(); out.flush(); if (in.markSupported()) { in.reset(); } readerThread = new SerialReader(in); readerThread.start(); } @Override public void disconnect() { logger.debug("Disconnecting"); if (readerThread != null) { logger.debug("Interrupt serial listener"); readerThread.interrupt(); } if (out != null) { logger.debug("Close serial out stream"); IOUtils.closeQuietly(out); } if (in != null) { logger.debug("Close serial in stream"); IOUtils.closeQuietly(in); } if (serialPort != null) { logger.debug("Close serial port"); serialPort.close(); } readerThread = null; serialPort = null; out = null; in = null; logger.debug("Closed"); } @Override public void sendMessage(byte[] data) throws IOException { out.write(data); out.flush(); } @Override public void addEventListener(RFXComEventListener rfxComEventListener) { _listeners.add(rfxComEventListener); } @Override public void removeEventListener(RFXComEventListener listener) { _listeners.remove(listener); } public class SerialReader extends Thread implements SerialPortEventListener { boolean interrupted = false; InputStream in; public SerialReader(InputStream in) { this.in = in; } @Override public void interrupt() { interrupted = true; super.interrupt(); try { in.close(); } catch (IOException e) { } // quietly close } @Override public void run() { final int dataBufferMaxLen = Byte.MAX_VALUE; byte[] dataBuffer = new byte[dataBufferMaxLen]; int msgLen = 0; int index = 0; boolean start_found = false; logger.debug("Data listener started"); // RXTX serial port library causes high CPU load // Start event listener, which will just sleep and slow down event loop try { serialPort.addEventListener(this); serialPort.notifyOnDataAvailable(true); } catch (TooManyListenersException e) { } try { byte[] tmpData = new byte[20]; int len = -1; while ((len = in.read(tmpData)) > 0 && !interrupted) { byte[] logData = Arrays.copyOf(tmpData, len); logger.trace("Received data (len={}): {}", len, DatatypeConverter.printHexBinary(logData)); if (ignoreReceiveBuffer) { // any data already in receive buffer will be ignored ignoreReceiveBuffer = false; start_found = false; if (index > 0) { logger.trace("Ignoring data in receive Buffer : " + index + " bytes"); } } for (int i = 0; i < len; i++) { if (index > dataBufferMaxLen) { // too many bytes received, try to find new start start_found = false; } if (start_found == false && tmpData[i] > 0) { start_found = true; index = 0; dataBuffer[index++] = tmpData[i]; msgLen = tmpData[i] + 1; } else if (start_found) { dataBuffer[index++] = tmpData[i]; if (index == msgLen) { // whole message received, send an event byte[] msg = new byte[msgLen]; for (int j = 0; j < msgLen; j++) { msg[j] = dataBuffer[j]; } RFXComMessageReceivedEvent event = new RFXComMessageReceivedEvent(this); try { Iterator<RFXComEventListener> iterator = _listeners.iterator(); while (iterator.hasNext()) { iterator.next().packetReceived(event, msg); } } catch (Exception e) { logger.error("Event listener invoking error", e); } // find new start start_found = false; } } } } } catch (InterruptedIOException e) { Thread.currentThread().interrupt(); logger.error("Interrupted via InterruptedIOException"); } catch (IOException e) { logger.error("Reading from serial port failed", e); } serialPort.removeEventListener(); logger.debug("Data listener stopped"); } @Override public void serialEvent(SerialPortEvent arg0) { try { logger.trace("RXTX library CPU load workaround, sleep forever"); sleep(Long.MAX_VALUE); } catch (InterruptedException e) { } } } public boolean isConnected() { return out != null; } @Override public void clearReceiveBuffer() { ignoreReceiveBuffer = true; } }