/*
*
* Copyright (C) 2012-2014 R T Huitema. All Rights Reserved.
* Web: www.42.co.nz
* Email: robert@42.co.nz
* Author: R T Huitema
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
* WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package nz.co.fortytwo.signalk.server;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import nz.co.fortytwo.signalk.util.ConfigConstants;
import nz.co.fortytwo.signalk.util.SignalKConstants;
import nz.co.fortytwo.signalk.util.Util;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.impl.DefaultExchange;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;
import com.ctc.wstx.io.CharsetNames;
import com.google.common.primitives.Bytes;
import purejavacomm.CommPortIdentifier;
import purejavacomm.SerialPort;
import purejavacomm.SerialPortEvent;
import purejavacomm.SerialPortEventListener;
/**
* Wrapper to read serial port via rxtx, then fire messages into the camel route
* via the seda queue.
*
* @author robert
*
*/
public class SerialPortReader implements Processor {
private static Logger logger = LogManager.getLogger(SerialPortReader.class);
private String portName;
private File portFile;
private ProducerTemplate producer;
private boolean running = true;
private boolean mapped = false;
private String deviceType = null;
private SerialPort serialPort = null;
private LinkedBlockingQueue<String> queue;
private SerialReader serialReader;
public SerialPortReader() {
super();
queue = new LinkedBlockingQueue<String>(100);
}
/**
* Opens a connection to the serial port, and starts two threads, one to read, one to write.
* A background thread looks for new/lost USB devices and (re)attaches them
*
*
* @param portName
* @throws Exception
*/
void connect(String portName, int baudRate) throws Exception {
this.portName = portName;
if(!SystemUtils.IS_OS_WINDOWS){
this.portFile = new File(portName);
}
CommPortIdentifier portid = CommPortIdentifier.getPortIdentifier(portName);
serialPort = (SerialPort) portid.open("FreeboardSerialReader", 100);
//TODO: change baud rate to config based setup
serialPort.setSerialPortParams(baudRate, 8, 1, 0);
serialReader = new SerialReader();
serialPort.enableReceiveTimeout(1000);
serialPort.notifyOnDataAvailable(true);
serialPort.addEventListener(serialReader);
//(new Thread(new SerialReader())).start();
(new Thread(new SerialWriter())).start();
}
public class SerialWriter implements Runnable {
BufferedOutputStream out;
public SerialWriter() throws Exception {
this.out = new BufferedOutputStream(serialPort.getOutputStream());
}
public void run() {
try {
while (running) {
String msg = queue.poll(5, TimeUnit.SECONDS);
if (StringUtils.isNotBlank(msg)) {
out.write((msg + "\r\n").getBytes());
out.flush();
}
}
} catch (IOException e) {
logger.error(portName+":"+ e.getMessage());
logger.debug(e.getMessage(),e);
} catch (InterruptedException e) {
// do nothing
}finally {
//clean up
running = false;
try {
out.close();
} catch (Exception e1) {
logger.error(portName+":"+ e1.getMessage());
//logger.debug(e1.getMessage(),e1);
}
}
}
}
/** */
public class SerialReader implements SerialPortEventListener {
//BufferedReader in;
private Pattern uid;
//List<String> lines = new ArrayList<String>();
String line = null;
StringBuffer lineBuf = new StringBuffer();
private boolean enableSerial=true;
private boolean complete;
protected InputStream in;
byte[] buff = new byte[256];
int x=0;
Map<String, Object> headers = new HashMap<String, Object>();
public SerialReader() throws Exception {
//this.in = new BufferedReader(new InputStreamReader(serialPort.getInputStream()));
this.in = new BufferedInputStream(serialPort.getInputStream());
headers.put(SignalKConstants.MSG_TYPE, SignalKConstants.SERIAL);
headers.put(SignalKConstants.MSG_SERIAL_PORT, portName);
headers.put(SignalKConstants.MSG_SRC_BUS, portName);
uid = Pattern.compile(ConfigConstants.UID + ":");
if(logger.isDebugEnabled())logger.info("Setup serialReader on :"+portName);
enableSerial = Util.getConfigPropertyBoolean(ConfigConstants.ENABLE_SERIAL);
}
//@Override
public void serialEvent(SerialPortEvent event) {
//if(logger.isTraceEnabled())logger.trace("SerialEvent:"+event.getEventType());
try{
if (running && event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
int r=0;
while((r>-1)&& in.available()>0 ){
if(!running)break;
try {
r = in.read();
buff[x]=(byte) r;
x++;
//10=LF, 13=CR, lines should end in CR/LF
if(r==10 ||x==256){
if(r==10){
complete=true;
}
lineBuf.append(new String(buff));
buff=new byte[256];
x=0;
}
} catch (IOException e) {
logger.error(portName + ":"+e.getMessage());
logger.debug(e.getMessage(),e);
return;
}
//we have a line ending in CR/LF
if (complete && StringUtils.isNotBlank(lineBuf)) {
line = lineBuf.toString().trim();
if(logger.isDebugEnabled())logger.debug(portName + ":Serial Received:" + line);
//its not empty!
if(line.length()>0){
//map it if we havent already
if (!mapped && uid.matcher(line).matches()) {
// add to map
logger.debug(portName + ":Serial Received:" + line);
String type = StringUtils.substringBetween(line.toString(), ConfigConstants.UID + ":", ",");
if (type != null) {
logger.debug(portName + ": device name:" + type);
deviceType = type.trim();
mapped = true;
}
}
if(enableSerial){
try{
Exchange ex = new DefaultExchange(CamelContextFactory.getInstance());
ex.getIn().setBody(line);
ex.getIn().getHeaders().putAll(headers);
producer.asyncSend(producer.getDefaultEndpoint(), ex);
}catch(CamelExecutionException ce){
if(ce.getCause() instanceof IllegalStateException){
if("Queue full".equals(ce.getCause().getMessage())){
logger.error("Unable to send serial data - Queue full - skipping..");
}
}
}
}else{
if(logger.isDebugEnabled())logger.debug("enableSerial false:"+line);
}
}
complete=false;
line=null;
lineBuf= new StringBuffer();
}
}
}
}catch (Exception e) {
running=false;
stopReader();
logger.error(portName, e);
}
}
protected void stopReader() {
serialPort.removeEventListener();
try {
in.close();
} catch (IOException e1) {
logger.error(portName, e1);
}
}
}
/** */
/**
* Set the camel producer, which fire the messages into camel
*
* @param producer
*/
public void setProducer(ProducerTemplate producer) {
this.producer = producer;
}
/**
* True if the serial port read/write threads are running
*
* @return
*/
public boolean isRunning() {
//no good on windoze
if (!SystemUtils.IS_OS_WINDOWS && !portFile.exists()) {
try {
serialPort.close();
serialReader.stopReader();
} catch (Exception e) {
logger.error("Problem disconnecting port " + portName +", "+ e.getMessage());
logger.debug(e.getMessage(),e);
}
running = false;
}
return running;
}
/**
* Set to false to stop the serial port read/write threads.
* You must connect() to restart.
*
* @param running
*/
public void setRunning(boolean running) {
this.running = running;
if(!running){
serialReader.stopReader();
serialPort.close();
}
}
public String getPortName() {
return portName;
}
public void setPortName(String portName) {
this.portName = portName;
}
/*
* Handles the messages to be delivered to the device attached to this port.
*
* @see org.apache.camel.Processor#process(org.apache.camel.Exchange)
*/
public void process(Exchange exchange) throws Exception {
// send to device
String message = exchange.getIn().getBody(String.class);
logger.debug(portName + ":msg received for device:" + message);
if (StringUtils.isNotBlank(message)) {
// check its valid for this device
if (running && deviceType == null || message.contains(ConfigConstants.UID + ":" + deviceType)) {
if(logger.isDebugEnabled())logger.debug(portName + ":wrote out to device:" + message);
// queue them and write in background
if(!queue.offer(message)){
if(logger.isDebugEnabled())logger.debug("Output queue id full for "+portName);
}
}
}
}
}