/* * RomRaider Open-Source Tuning, Logging and Reflashing * Copyright (C) 2006-2012 RomRaider.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package com.romraider.io.serial.connection; import com.romraider.io.connection.ConnectionProperties; import com.romraider.logger.ecu.exception.NotConnectedException; import com.romraider.logger.ecu.exception.PortNotFoundException; import com.romraider.logger.ecu.exception.SerialCommunicationException; import com.romraider.logger.ecu.exception.UnsupportedPortTypeException; import static com.romraider.util.HexUtil.asHex; import static com.romraider.util.ParamChecker.checkNotNull; import static com.romraider.util.ParamChecker.checkNotNullOrEmpty; import static com.romraider.util.ThreadUtil.sleep; import gnu.io.CommPortIdentifier; import static gnu.io.CommPortIdentifier.PORT_SERIAL; import static gnu.io.CommPortIdentifier.getPortIdentifier; import gnu.io.NoSuchPortException; import gnu.io.PortInUseException; import gnu.io.SerialPort; import static gnu.io.SerialPort.FLOWCONTROL_NONE; import static java.lang.System.currentTimeMillis; import gnu.io.UnsupportedCommOperationException; import org.apache.log4j.Logger; import static org.apache.log4j.Logger.getLogger; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public final class SerialConnectionImpl implements SerialConnection { private static final Logger LOGGER = getLogger(SerialConnectionImpl.class); private static final String RXTX_READ_LINE_HACK = "Underlying input stream returned zero bytes"; private final SerialPort serialPort; private final BufferedOutputStream os; private final BufferedInputStream is; private final BufferedReader reader; public SerialConnectionImpl(String portName, ConnectionProperties connectionProperties) { checkNotNullOrEmpty(portName, "portName"); checkNotNull(connectionProperties, "connectionProperties"); try { serialPort = connect(portName, connectionProperties); os = new BufferedOutputStream(serialPort.getOutputStream()); is = new BufferedInputStream(serialPort.getInputStream()); reader = new BufferedReader(new InputStreamReader(is)); LOGGER.info("Serial connection initialised: " + connectionProperties); } catch (Exception e) { close(); throw new NotConnectedException(e); } } public void write(byte[] bytes) { try { os.write(bytes, 0, bytes.length); os.flush(); } catch (IOException e) { throw new SerialCommunicationException(e); } } public int available() { try { return is.available(); } catch (IOException e) { throw new SerialCommunicationException(e); } } public int read() { try { waitForBytes(1); return is.read(); } catch (IOException e) { throw new SerialCommunicationException(e); } } public void read(byte[] bytes) { try { waitForBytes(bytes.length); is.read(bytes, 0, bytes.length); } catch (IOException e) { throw new SerialCommunicationException(e); } } public String readLine() { try { waitForBytes(1); return reader.readLine(); } catch (IOException e) { /* This is a dodgy hack to workaround RXTX seemingly not respecting the request to disable to the receive timeout. ie. gnu.io.SerialPort.disableReceiveTimeout() */ if (RXTX_READ_LINE_HACK.equalsIgnoreCase(e.getMessage())) return null; throw new SerialCommunicationException(e); } } public byte[] readAvailable() { byte[] response = new byte[available()]; read(response); return response; } public void readStaleData() { if (available() <= 0) return; final long end = currentTimeMillis() + 100L; do { byte[] staleBytes = readAvailable(); LOGGER.debug("Stale data read: " + asHex(staleBytes)); sleep(2); } while ( (available() > 0) && (currentTimeMillis() <= end)); } public void close() { if (os != null) { try { os.close(); } catch (IOException e) { LOGGER.error("Error closing output stream", e); } } if (reader != null) { try { //readStaleData(); reader.close(); } catch (IOException e) { LOGGER.error("Error closing input stream reader", e); } } if (is != null) { try { //readStaleData(); is.close(); } catch (IOException e) { LOGGER.error("Error closing input stream", e); } } if (serialPort != null) { try { serialPort.close(); } catch (Exception e) { LOGGER.error("Error closing serial port", e); } } LOGGER.info("Connection closed."); } public void sendBreak(int duration) { try { serialPort.sendBreak(duration); } catch (Exception e) { throw new SerialCommunicationException(e); } } private SerialPort connect(String portName, ConnectionProperties connectionProperties) { CommPortIdentifier portIdentifier = resolvePortIdentifier(portName); SerialPort serialPort = openPort(portIdentifier, connectionProperties.getConnectTimeout()); initSerialPort(serialPort, connectionProperties.getBaudRate(), connectionProperties.getDataBits(), connectionProperties.getStopBits(), connectionProperties.getParity()); LOGGER.info("Connected to: " + portName); return serialPort; } private SerialPort openPort(CommPortIdentifier portIdentifier, int connectTimeout) { checkIsSerialPort(portIdentifier); try { return (SerialPort) portIdentifier.open(this.getClass().getName(), connectTimeout); } catch (PortInUseException e) { throw new SerialCommunicationException("Port is currently in use: " + portIdentifier.getName()); } } private void checkIsSerialPort(CommPortIdentifier portIdentifier) { if (portIdentifier.getPortType() != PORT_SERIAL) { throw new UnsupportedPortTypeException("Port type " + portIdentifier.getPortType() + " not supported - must be serial."); } } private void initSerialPort(SerialPort serialPort, int baudrate, int dataBits, int stopBits, int parity) { try { serialPort.setFlowControlMode(FLOWCONTROL_NONE); serialPort.setSerialPortParams(baudrate, dataBits, stopBits, parity); serialPort.disableReceiveTimeout(); serialPort.setRTS(false); } catch (UnsupportedCommOperationException e) { throw new UnsupportedOperationException(e); } } private CommPortIdentifier resolvePortIdentifier(String portName) { try { return getPortIdentifier(portName); } catch (NoSuchPortException e) { throw new PortNotFoundException("Unable to resolve port: " + portName); } } private void waitForBytes(int numBytes) { while (available() < numBytes) sleep(2L); } }