/* * (C) Copyright 2015 by fr3ts0n <erwin.scheuch-heilig@gmx.at> * * 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., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package com.fr3ts0n.prot.gui; import org.apache.log4j.Logger; import com.fr3ts0n.prot.ProtUtils; import com.fr3ts0n.prot.SerialExt; import com.fr3ts0n.prot.TelegramWriter; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.io.IOException; import java.util.Vector; import javax.swing.Timer; /** * Communication handler for KL-Adaptor * * @author erwin */ public class KLHandlerGeneric extends SerialHandlerGeneric implements TelegramWriter { /** * Status of package transmission / reception - to determine if we are * processing data, or complements */ public enum PacketStatus { ECHO, COMPLEMENT, DATA, } // baud rates supported by this adapter int[] baudRates = { 10400, 9600, 4800, 2400 }; // synch char private static final char SYNC_CHAR = 0x55; // current packet status private PacketStatus pktStat = PacketStatus.COMPLEMENT; // telegram block counter private char blockCounter = 0; /* current address in use */ private int currAddress = 0; // (see ecuadr.csv) /** current baud rate */ private int currBaudRate = 0; // last byte sent; private char lastTxChar = 0; /** Queue of telegrams to be sent */ Vector<char[]> txTelegramQueue = new Vector<char[]>(); /** current telegram to be sent */ char[] currTxTgm = {}; /** current position of byte to be sent from within currTxTgm */ int currTxCharPos = 0; /** * Protocol timing parameters */ /** time of last reception */ long lastRxTime = 0; /** time [ms] to detect communication timeout */ static int commTimeoutTime = 2000; // [ms] /** number of bits to pause (parameter fo calculation of interByteTime) */ static final int numBitsPause = 0; /** time [nanoseconds] to wait for sending next byte */ long interByteTime = 0; // [ns] /** Timer to detect communication timeout */ Timer commTimer = null; /** Handler for communication Timeout */ ActionListener commTimeoutHandler = new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { log.warn("CommTimeout"); close(); setProtStat(ProtStatus.TIMEOUT); } }; /** * Default constructor */ public KLHandlerGeneric() { // set the logger object log = Logger.getLogger("com.fr3ts0n.prot.kl"); commTimer = new Timer(commTimeoutTime, commTimeoutHandler); commTimer.setInitialDelay(commTimeoutTime); commTimer.stop(); } /** * construct with connecting to device * * @param device device to connect */ public KLHandlerGeneric(String device) { try { setDeviceName(device); } catch (Exception ex) { log.fatal(ex); } commTimer = new Timer(commTimeoutTime, commTimeoutHandler); commTimer.setInitialDelay(commTimeoutTime); commTimer.stop(); } /** * @return the currBaudRate */ // current baud rate in use public int getCurrBaudRate() { return currBaudRate; } /** * @param currBaudRate the currBaudRate to set */ public void setCurrBaudRate(int currBaudRate) { firePropertyChange(new PropertyChangeEvent(this, "baud", this.currBaudRate, currBaudRate)); this.currBaudRate = currBaudRate; } /** * @return the currAddress */ public int getCurrAddress() { return currAddress; } /** * @param currAddress the currAddress to set */ public void setCurrAddress(int currAddress) { this.currAddress = currAddress; } /** * close communication port */ @Override public void close() { txTelegramQueue.clear(); if (commTimer != null) { commTimer.stop(); } super.close(); // set shutdown status setProtStat(ProtStatus.OFFLINE); } /** * add new listener for communication timeout * * @param listener new timeout listener */ public void addTimeoutListener(ActionListener listener) { commTimer.addActionListener(listener); } /** * set port speed to specified baud rate * * @param newBaudRate new baud rate to be set */ public void setCustomBaudRate(int newBaudRate) { try { SerialExt.setCustomBaudrate(newBaudRate); } catch (Exception e) { log.error("Set custom baudrate", e); } // show baudrate setting as debug output try { log.debug(String.format("Baudrate:%d/%d", SerialExt.getCustomBaudrate(), newBaudRate)); } catch (Exception e) { log.error(null, e); } // calculate new interByteTime (1M ns *numBits / bps) interByteTime = 1000000000 * numBitsPause / newBaudRate; } /** * send a single bit with 5 baud bit width * * @param bitValue */ void sendBit5Baud(boolean bitValue) throws IOException { // set line status SerialExt.setBreak(bitValue ? 0 : 1); // RTS line follows inverse data bit ... // device.setRTS(!bitValue); // and wait a single bit time try { Thread.sleep(200); } catch (InterruptedException e) { log.error(null, e); } } /** * send a byte with 5 baud line speed * this version sends out timed line break bits w/o changing the nominal * baud rate * * @param address CU to be addressed */ void sendByte5Baud(int address) throws IOException { long start = System.currentTimeMillis(); // ensure stop bit // sendBit5Baud(true); // start sending // device.setDTR(false); // start bit sendBit5Baud(false); // 8 data bits, LSB first for (int i = 0; i < 8; i++) { sendBit5Baud((address & (1 << i)) != 0); } // stop bit sendBit5Baud(true); // finish sending // device.setDTR(true); log.debug(String.format("TX-5Baud:%02X finished after %dms", address, System.currentTimeMillis() - start)); } /** * Initialize communication by sending specified ECU address with 5 baud * * @param address ECU to be addressed * @return initialization status (0=OK, -1=Error) */ int send5Baud(int address) throws IOException { int result = 0; setProtStat(ProtStatus.CONNECTING); sendByte5Baud(address); message = ""; // return status return (result); } /** * send specified char to output device * * @param txByte byte to be sent * @param rememberTx remember last TX character */ public void writeChar(char txByte, boolean rememberTx) { try { log.debug("TX:" + String.format("%02X", (byte) txByte)); // if this byte should be remembered as TX byte, do it if (rememberTx) { lastTxChar = txByte; } SerialExt.sendChar((byte) txByte); pktStat = PacketStatus.ECHO; } catch (Exception ex) { log.error(this.toString(), ex); } } /** * confirm reception of last byte by sending complement of it back * * @param lastByte last received byte */ public void confirmByte(char lastByte) { // change to complement status ... pktStat = PacketStatus.COMPLEMENT; writeChar((char) ~lastByte, true); } /** * Handler for receiving chars from serial port */ public class RxThread extends Thread { @Override public void run() { int chr; log.debug("Reader Thread started"); while (getProtStat() != ProtStatus.OFFLINE && getProtStat() != ProtStatus.TIMEOUT) { try { chr = SerialExt.receiveChar(); log.debug(String.format("RX:%02X : %c\t%s\t%s", chr, chr < 32 || chr > 127 ? '.' : chr, getProtStat(), pktStat)); switch (getProtStat()) { case RECEIVING: // restart comm timeout timer commTimer.restart(); switch (pktStat) { case ECHO: lastTxChar = 0; // ignore the echo of sent complement pktStat = PacketStatus.DATA; break; case COMPLEMENT: // ignore the echo of sent complement pktStat = PacketStatus.DATA; break; case DATA: // remember time of last RX lastRxTime = System.currentTimeMillis(); // add char to current RX message message += (char) chr; // do we have received a complete package? if (chr == 0x03 && message.length() > message.charAt(0)) { // // package is complete // log.debug("RX:" + ProtUtils.hexDumpBuffer(message.toCharArray())); // update block counter with the received one blockCounter = message.charAt(1); // notify protocol handler of the new package messageHandler.handleTelegram(message.toCharArray()); // clear the message message = ""; // and change to send status setProtStat(ProtStatus.SENDING); sendNextByte(); } else { // // package is still incomplete // // and send out confirmation for received byte confirmByte((char) chr); } break; } break; case SENDING: { switch (pktStat) { case ECHO: case DATA: // ignore the echo of sent data pktStat = PacketStatus.COMPLEMENT; break; case COMPLEMENT: // restart comm timeout timer commTimer.restart(); if (chr == (~lastTxChar & 0xFF)) { pktStat = PacketStatus.DATA; sendNextByte(); } else { log.error(String.format("Wrong complement:%02X, expected:%02X", chr, ~lastTxChar)); // TODO: handle communication error } break; } } break; case CONNECTING: { // if we receive the echo of the init byte, sending is done if (chr == lastTxChar) { // ignore echo byte } else { if (chr == SYNC_CHAR) { message = ""; } // otherwise collect sync and key bytes message += (char) chr; // did we receive all INIT bytes (SYNC_CHAR, KW-lo, KW-hi)? if (message.length() > 2 && message.charAt(0) == SYNC_CHAR) { // notify protocol handler of initialization message to get keyword etc // messageHandler.handleTelegram(message.toCharArray()); message = ""; // Initialization is finished, change to status RECEIVING setProtStat(ProtStatus.RECEIVING); // confirm the last received byte confirmByte((char) chr); } } } default: break; } } catch (Exception ex) { log.error(ex.toString()); try { sleep(0); } catch (InterruptedException ex1) { log.error(ex1.toString()); } } } log.warn("Reader Tread finished"); } } ; /** * write a telegram to serial port * implementation of SerialHandler */ @Override public int writeTelegram(char[] buffer) { return writeTelegram(buffer, 0, null); } /** * write a telegram to serial port * implementation of SerialHandler */ @Override public int writeTelegram(char[] buffer, int type, Object id) { return enqueueTelegram(buffer); } /** * start the serial handler * Implementation for compatibility */ @Override public void start() { // start the receive thread new Thread() { @Override public void run() { init5Baud(getCurrAddress()); } }.start(); try { Thread.sleep(500); } catch (Exception e) { log.error(e.getMessage()); } new RxThread().start(); } /** * send next available byte * * @return error status of dequeue and send operation */ int sendNextByte() { int result = 0; // wait for slow ECUs try { // calculate remaining time to wait (Sytem time resolution sucks...) long tDiff = interByteTime - ((System.currentTimeMillis() - lastRxTime) * 1000000000); if (tDiff > 0) { log.debug("TX: waiting " + tDiff + " ns"); Thread.sleep(tDiff / 1000000L, (int) (tDiff % 1000000L)); } } catch (InterruptedException ex) { log.error("wait: " + ex.toString()); } // if we have to dequeue a new telegram ... if (currTxCharPos >= currTxTgm.length) { // ... then try to dequeue one result = dequeueTelegram(); } // if there is something to send, send the next char if (result == 0) { writeChar(currTxTgm[currTxCharPos++], true); if (currTxCharPos >= currTxTgm.length) { setProtStat(ProtStatus.RECEIVING); } } return result; } /** * enqueue a telegram to be sent * * @param buffer telegram to be sent * @return error status of enqueue */ int enqueueTelegram(char[] buffer) { int result = 0; // if the queue is empty and current message is completely sent if (txTelegramQueue.size() == 0 && currTxCharPos >= currTxTgm.length) { // make buffer the current telegram currTxTgm = buffer; currTxCharPos = 0; } else { // otherwise put it into queue result = txTelegramQueue.add(buffer) ? 0 : -1; } return (result); } /** * get a acknowledge telegram for current protocol state * * @return ACK telegram with current block counter */ char[] getAckTelegram() { return new char[] { 0x03, ++blockCounter, 0x09, 0x03 }; } /** * dequeue next telegram to be sent * * @return error code of dequeue operation */ int dequeueTelegram() { int result = -1; // is there any telegram in the queue? if (txTelegramQueue.size() > 0) { // ... then dequeue it currTxTgm = txTelegramQueue.remove(0); // if necessary, adjust block counter if (currTxTgm[1] != (blockCounter + 1)) { log.info(String.format("MsgCounter adjusted:%02x->%02x", (int) currTxTgm[1], (int) blockCounter + 1)); currTxTgm[1] = ++blockCounter; } // ... and set byte pointer to start of telegram currTxCharPos = 0; result = 0; } else { // nothing in the pipe, so just send an acknowledge telegram currTxTgm = getAckTelegram(); currTxCharPos = 0; result = 0; } return (result); } /** * Initialize communication by sending specified ECU address with 5 baud * * @param address ECU to be addressed * @return baud rate of successful connection, or 0 if no connection could be established */ public synchronized int init5Baud(int address) { int result = 0; // set current address setCurrAddress(address); try { // stop timeout handler commTimer.stop(); // set device name / open port setDeviceName(deviceName); // loop through all known baud rates for (int i = 0; i < baudRates.length; i++) { setProtStat(ProtStatus.CONNECTING); log.info(String.format("Init Device:%s Address:%02x, Speed:%d", deviceName, address, baudRates[i])); // set communication baud rate setCustomBaudRate(baudRates[i]); // remember current baud rate setCurrBaudRate(baudRates[i]); // init communication with 5 baud address send5Baud(address); try { Thread.sleep(500); } catch (InterruptedException e) { log.error("Sleep", e); } // if we received an answer, we are not initializing any more if (getProtStat() != ProtStatus.CONNECTING) { log.info(String.format("Init OK Device:%s Address:%02x, Speed:%d", deviceName, address, baudRates[i])); // set return value result = getCurrBaudRate(); // start communication timer commTimer.restart(); break; } } if (result == 0) { log.warn(String.format("Init Timeout Device:%s Address:%02x", deviceName, address)); close(); } } catch (Exception ex) { log.error(deviceName + ": " + ex.toString()); close(); setProtStat(ProtStatus.ERROR); result = 0; } // remember current baud rate setCurrBaudRate(result); return (result); } /** * The main routine for testing * * @param args the command line arguments */ public static void main(String args[]) { // set default parameters int address = 0x01; String device = "/dev/ttyUSB0"; // update parameters by command line params if (args.length > 0) { device = args[0]; } if (args.length > 1) { address = Integer.parseInt(args[1], 16); } KLHandlerGeneric hdlr = new KLHandlerGeneric(device); hdlr.init5Baud(address); // wait until we are shutting down while (hdlr.getProtStat() != ProtStatus.CONNECTING && hdlr.getProtStat() != ProtStatus.OFFLINE) { try { Thread.sleep(1000); } catch (Exception e) { } } hdlr.close(); } }