/**
* 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.nikobus.internal.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
/**
* Serial Interface for connecting to the Nikobus system. This interface has
* been designed to work with the Nikobus PC-Link (05-200) module.
*
* To view default serial ports on linux use lsusb.
*
* @author Davy Vanherbergen
* @since 1.3.0
*/
public class NikobusInterface implements SerialPortEventListener {
private static Logger log = LoggerFactory.getLogger(NikobusInterface.class);
private static final String RS232_SET_CMD_MODE = "++++";
private static final String RS232_DISCONNECT = "ATH0";
private static final String RS232_RESET_DEVICE = "ATZ";
private static final String PC_LINK_IDENTIFIER = "$10110000B8CF9D";
private String serialPort;
private static final int CONNECT_TIMEOUT = 2000;
private static final String SERIAL_PORT_PROPERTY_NAME = "gnu.io.rxtx.SerialPorts";
private SerialPort port;
private LinkedBlockingQueue<byte[]> bufferQueue = new LinkedBlockingQueue<byte[]>();
private volatile long lastEventTimestamp;
/**
* @return true if any data was received so far.
*/
private boolean hasReceivedData() {
return lastEventTimestamp > 0;
}
/**
* Check if we have an active connection.
*
* @return true if there is an active serial connection.
*/
public boolean isConnected() {
if (port == null) {
return false;
}
try {
if (port.getInputStream() == null) {
return false;
}
} catch (IOException e) {
return false;
}
return hasReceivedData();
}
/**
* Initialize a serial connection to the Nikobus module.
*
* @throws Exception
*/
public void connect() throws Exception {
if (isConnected()) {
return;
}
// reset flag
lastEventTimestamp = 0;
if (serialPort == null) {
throw new Exception("No serial port defined. Check your configuration.");
}
openCommPort(serialPort);
// send connection sequence
write(RS232_SET_CMD_MODE);
write(RS232_DISCONNECT);
write(RS232_RESET_DEVICE);
write(PC_LINK_IDENTIFIER);
write("#L0");
write("#E0");
write("#L0");
write("#E1");
// wait until we receive some acknowledgment from the nikobus
// if we don't received it within 2 seconds, we shut it down..
long start = System.currentTimeMillis();
while (start - System.currentTimeMillis() < 2000) {
if (!hasReceivedData()) {
// give the pc-link some time to respond..
Thread.sleep(200);
} else {
break;
}
}
if (isConnected()) {
log.info("Connected to Nikobus :-)");
} else {
log.error("Could not connect to Nikobus.");
if (port != null) {
try {
port.removeEventListener();
port.getInputStream().close();
port.getOutputStream().close();
} catch (IOException e) {
// noop
}
port.close();
}
}
}
/**
* Locate serial port and open connection.
*
* @param portName
* @throws Exception
*/
@SuppressWarnings("rawtypes")
private void openCommPort(String portName) throws Exception {
if (log.isDebugEnabled()) {
Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
while (portIdentifiers.hasMoreElements()) {
CommPortIdentifier id = (CommPortIdentifier) portIdentifiers.nextElement();
log.debug("Found port: {} x {}", id.getName(), id.getCurrentOwner());
}
}
CommPortIdentifier portIdentifier = null;
try {
portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
} catch (NoSuchPortException e) {
log.debug("Port not found during first attempt : {}", e.getMessage());
}
if (portIdentifier == null) {
try {
System.setProperty(SERIAL_PORT_PROPERTY_NAME, portName);
portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
} catch (Exception e) {
log.debug("Port not found during second attempt : {}", e.getMessage());
System.clearProperty(SERIAL_PORT_PROPERTY_NAME);
}
}
if (portIdentifier == null) {
throw new Exception("Serial port '" + portName + "' not found.");
}
if (portIdentifier.isCurrentlyOwned()) {
throw new Exception("Serial port '" + portName + "' is in use.");
}
// open port
port = portIdentifier.open(this.getClass().getSimpleName(), CONNECT_TIMEOUT);
log.info("Connected to serial port '{}'", portIdentifier.getName());
// configure
port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
port.notifyOnDataAvailable(true);
port.addEventListener(this);
}
/**
* Disconnect the serial port connection to the Nikobus module.
*/
public void disconnect() {
log.info("Closing Nikobus connection.");
if (!isConnected()) {
return;
}
lastEventTimestamp = 0;
port.removeEventListener();
try {
write(RS232_DISCONNECT);
} catch (Exception e) {
log.error("Could not send disconnect command", e);
}
if (port != null) {
try {
port.getInputStream().close();
port.getOutputStream().close();
} catch (IOException e) {
log.error("Error closing port: {}", e.getMessage());
}
port.close();
}
}
/**
* Write a message to the serial port. The message will be terminated with a
* CR.
*
* @param message
* @throws Exception
*/
private void write(String message) throws Exception {
if (hasReceivedData() && !isConnected()) {
log.warn("Nikobus interface connected lost. Reconnecting...");
connect();
}
log.debug("Sending : {}", message);
port.getOutputStream().write(message.getBytes());
port.getOutputStream().write("\r".getBytes());
port.getOutputStream().flush();
}
/**
* Write a message to the serial port. The message will be terminated with a
* CR.
*
* @param message
* @throws Exception
*/
public void writeMessage(String message) throws Exception {
if (!hasReceivedData()) {
return;
}
write(message);
}
/**
* {@inheritDoc}
*
* Any data received from a serial event, is immediately passed on to the
* command receiver thread.
*/
@Override
public void serialEvent(SerialPortEvent event) {
if (event.getEventType() != SerialPortEvent.DATA_AVAILABLE) {
return;
}
InputStream is = null;
try {
lastEventTimestamp = System.currentTimeMillis();
is = port.getInputStream();
int available = is.available();
if (available == 0) {
log.warn("Received data available event, but no data was found!");
return;
}
byte[] buffer = new byte[available];
is.read(buffer, 0, available);
bufferQueue.add(buffer);
if (available != 1 || buffer[0] != 13) {
// don't print single CR's in log..
log.trace("Received: {}", new String(buffer));
}
} catch (IOException e) {
log.error("Error receiving data on serial port {}: {}", port.getName(), e.getMessage());
}
}
/**
* @return queue containing all received characters.
*/
public LinkedBlockingQueue<byte[]> getBufferQueue() {
return bufferQueue;
}
/**
* Set the name of the serial port to use, e.g. /dev/ttyUSB0
*
* @param configuredSerialPort
*/
public void setPort(String configuredSerialPort) {
serialPort = configuredSerialPort;
}
/**
* @return name of serial port.
*/
public String getPort() {
return serialPort;
}
/**
* @return time when last serial event was received
*/
public long getLastEventTimestamp() {
return lastEventTimestamp;
}
}