/**
* 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.serial.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TooManyListenersException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
/**
* This class represents a serial device that is linked to exactly one String item and/or Switch item.
*
* @author Kai Kreuzer
*
*/
public class SerialDevice implements SerialPortEventListener {
private static final Logger logger = LoggerFactory.getLogger(SerialDevice.class);
private String port;
private int baud = 9600;
private EventPublisher eventPublisher;
private TransformationService transformationService;
private CommPortIdentifier portId;
private SerialPort serialPort;
private InputStream inputStream;
private OutputStream outputStream;
private Map<String, ItemType> configMap;
class ItemType {
String pattern;
boolean base64;
String onCommand;
String offCommand;
String upCommand;
String downCommand;
String stopCommand;
Class<?> type;
}
public boolean isEmpty() {
return configMap.isEmpty();
}
public void addConfig(String itemName, Class<?> type, String pattern, boolean base64, String onCommand,
String offCommand, String upCommand, String downCommand, String stopCommand) {
if (configMap == null) {
configMap = new HashMap<String, ItemType>();
}
ItemType typeItem = new ItemType();
typeItem.pattern = pattern;
typeItem.base64 = base64;
typeItem.type = type;
typeItem.onCommand = onCommand;
typeItem.offCommand = offCommand;
typeItem.upCommand = upCommand;
typeItem.downCommand = downCommand;
typeItem.stopCommand = stopCommand;
configMap.put(itemName, typeItem);
}
public void removeConfig(String itemName) {
if (configMap != null) {
configMap.remove(itemName);
}
}
public SerialDevice(String port) {
this.port = port;
}
public SerialDevice(String port, int baud) {
this.port = port;
this.baud = baud;
}
public void setEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void unsetEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = null;
}
public void setTransformationService(TransformationService transformationService) {
this.transformationService = transformationService;
}
public String getPort() {
return port;
}
public String getOnCommand(String itemName) {
if (configMap.get(itemName) != null) {
return configMap.get(itemName).onCommand;
}
return "";
}
public String getOffCommand(String itemName) {
if (configMap.get(itemName) != null) {
return configMap.get(itemName).offCommand;
}
return "";
}
public String getUpCommand(String itemName) {
if (configMap.get(itemName) != null) {
return configMap.get(itemName).upCommand;
}
return "";
}
public String getDownCommand(String itemName) {
if (configMap.get(itemName) != null) {
return configMap.get(itemName).downCommand;
}
return "";
}
public String getStopCommand(String itemName) {
if (configMap.get(itemName) != null) {
return configMap.get(itemName).stopCommand;
}
return "";
}
/**
* Initialize this device and open the serial port
*
* @throws InitializationException if port can not be opened
*/
@SuppressWarnings("rawtypes")
public void initialize() throws InitializationException {
// parse ports and if the default port is found, initialized the reader
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
CommPortIdentifier id = (CommPortIdentifier) portList.nextElement();
if (id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
if (id.getName().equals(port)) {
logger.debug("Serial port '{}' has been found.", port);
portId = id;
}
}
}
if (portId != null) {
// initialize serial port
try {
serialPort = portId.open("openHAB", 2000);
} catch (PortInUseException e) {
throw new InitializationException(e);
}
try {
inputStream = serialPort.getInputStream();
} catch (IOException e) {
throw new InitializationException(e);
}
try {
serialPort.addEventListener(this);
} catch (TooManyListenersException e) {
throw new InitializationException(e);
}
// activate the DATA_AVAILABLE notifier
serialPort.notifyOnDataAvailable(true);
try {
// set port parameters
serialPort.setSerialPortParams(baud, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
} catch (UnsupportedCommOperationException e) {
throw new InitializationException(e);
}
try {
// get the output stream
outputStream = serialPort.getOutputStream();
} catch (IOException e) {
throw new InitializationException(e);
}
} else {
StringBuilder sb = new StringBuilder();
portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
CommPortIdentifier id = (CommPortIdentifier) portList.nextElement();
if (id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
sb.append(id.getName() + "\n");
}
}
throw new InitializationException(
"Serial port '" + port + "' could not be found. Available ports are:\n" + sb.toString());
}
}
@Override
public void serialEvent(SerialPortEvent event) {
switch (event.getEventType()) {
case SerialPortEvent.BI:
case SerialPortEvent.OE:
case SerialPortEvent.FE:
case SerialPortEvent.PE:
case SerialPortEvent.CD:
case SerialPortEvent.CTS:
case SerialPortEvent.DSR:
case SerialPortEvent.RI:
case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
break;
case SerialPortEvent.DATA_AVAILABLE:
// we get here if data has been received
StringBuilder sb = new StringBuilder();
byte[] readBuffer = new byte[20];
try {
do {
// read data from serial device
while (inputStream.available() > 0) {
int bytes = inputStream.read(readBuffer);
sb.append(new String(readBuffer, 0, bytes));
}
try {
// add wait states around reading the stream, so that interrupted transmissions are merged
Thread.sleep(100);
} catch (InterruptedException e) {
// ignore interruption
}
} while (inputStream.available() > 0);
// sent data
String result = sb.toString();
// send data to the bus
logger.debug("Received message '{}' on serial port {}", new String[] { result, port });
if (eventPublisher != null) {
if (configMap != null && !configMap.isEmpty()) {
for (Entry<String, ItemType> entry : configMap.entrySet()) {
// use pattern
if (entry.getValue().pattern != null) {
if (transformationService == null) {
logger.error("No transformation service available!");
} else {
try {
String value = transformationService.transform(entry.getValue().pattern,
result);
try {
State state = null;
if (entry.getValue().type.equals(NumberItem.class)) {
state = new DecimalType(value);
} else if (entry.getValue().type == RollershutterItem.class) {
state = new PercentType(value);
} else {
state = new StringType(value);
}
eventPublisher.postUpdate(entry.getKey(), state);
} catch (NumberFormatException e) {
logger.warn("Unable to convert regex result '{}' for item {} to number",
new String[] { result, entry.getKey() });
}
} catch (TransformationException e) {
logger.error("Unable to transform!", e);
}
}
} else if (entry.getValue().type == StringItem.class) {
if (entry.getValue().base64) {
result = Base64.encodeBase64String(result.getBytes());
}
eventPublisher.postUpdate(entry.getKey(), new StringType(result));
} else if (entry.getValue().type == SwitchItem.class) {
if (result.trim().isEmpty()) {
eventPublisher.postUpdate(entry.getKey(), OnOffType.ON);
eventPublisher.postUpdate(entry.getKey(), OnOffType.OFF);
} else if (result.equals(getOnCommand(entry.getKey()))) {
eventPublisher.postUpdate(entry.getKey(), OnOffType.ON);
} else if (result.equals(getOffCommand(entry.getKey()))) {
eventPublisher.postUpdate(entry.getKey(), OnOffType.OFF);
}
} else if (entry.getValue().type == RollershutterItem.class) {
if (result.trim().isEmpty()) {
eventPublisher.postUpdate(entry.getKey(), new PercentType(50));
} else if (result.equals(getUpCommand(entry.getKey()))) {
eventPublisher.postUpdate(entry.getKey(), PercentType.HUNDRED);
} else if (result.equals(getDownCommand(entry.getKey()))) {
eventPublisher.postUpdate(entry.getKey(), PercentType.ZERO);
} else if (result.equals(getStopCommand(entry.getKey()))) {
eventPublisher.postUpdate(entry.getKey(), new PercentType(50));
}
}
}
}
}
} catch (IOException e) {
logger.debug("Error receiving data on serial port {}: {}", new String[] { port, e.getMessage() });
}
break;
}
}
/**
* Sends a string to the serial port of this device
*
* @param msg the string to send
*/
public void writeString(String msg) {
logger.debug("Writing '{}' to serial port {}", new String[] { msg, port });
try {
// write string to serial port
if (msg.startsWith("BASE64:")) {
outputStream.write(Base64.decodeBase64(msg.substring(7, msg.length())));
} else {
outputStream.write(msg.getBytes());
}
outputStream.flush();
} catch (IOException e) {
logger.error("Error writing '{}' to serial port {}: {}", new String[] { msg, port, e.getMessage() });
}
}
/**
* Close this serial device
*/
public void close() {
serialPort.removeEventListener();
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
serialPort.close();
}
}