/*
* (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.ecu.prot.obd;
import com.fr3ts0n.prot.TelegramListener;
import com.fr3ts0n.prot.TelegramWriter;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
/**
* Communication protocol to talk to a ELM327 OBD interface
*
* @author erwin
*/
public class ElmProt
extends ObdProt
implements TelegramListener, TelegramWriter, Runnable
{
/** virtual OBD service for CAN monitoring */
public static final int OBD_SVC_CAN_MONITOR = 256;
/** property name for ECU addresses */
public static final String PROP_ECU_ADDRESS = "ecuaddr";
/** property name for protocol status */
public static final String PROP_STATUS = "status";
/** CAN protocol handler */
public static CanProtFord canProt = new CanProtFord();
/** Adaptive timing handler */
public AdaptiveTiming mAdaptiveTiming = new AdaptiveTiming();
/** number of bytes expected from opponent */
private int charsExpected = 0;
/** remember last command which was sent */
private char[] lastCommand;
/** preferred ELM protocol to be selected */
static private PROT preferredProtocol = PROT.ELM_PROT_AUTO;
/** list of identified ECU addresses */
private TreeSet<Integer> ecuAddresses = new TreeSet<Integer>();
/** selected ECU address */
private int selectedEcuAddress = 0;
/** custom ELM initialisation commands */
Vector<String> customInitCommands = new Vector<String>();
/**
* ELM protocol ID's
*/
public enum PROT
{
ELM_PROT_AUTO ( "Automatic" ),
ELM_PROT_J1850PWM ( "SAE J1850 PWM (41.6 KBaud)" ),
ELM_PROT_J1850VPW ( "SAE J1850 VPW (10.4 KBaud)" ),
ELM_PROT_9141_2 ( "ISO 9141-2 (5 Baud Init)" ),
ELM_PROT_14230_4 ( "ISO 14230-4 KWP (5 Baud Init)" ),
ELM_PROT_14230_4F ( "ISO 14230-4 KWP (fast Init)" ),
ELM_PROT_15765_11_F ( "ISO 15765-4 CAN (11 Bit ID, 500 KBit)" ),
ELM_PROT_15765_29_F ( "ISO 15765-4 CAN (29 Bit ID, 500 KBit)" ),
ELM_PROT_15765_11_S ( "ISO 15765-4 CAN (11 Bit ID, 250 KBit)" ),
ELM_PROT_15765_29_S ( "ISO 15765-4 CAN (29 Bit ID, 250 KBit)" ),
ELM_PROT_J1939_29_S ( "SAE J1939 CAN (29 bit ID, 250* kbaud)" ),
ELM_PROT_USER1_CAN_11_S ( "User1 CAN (11* bit ID, 125* kbaud)" ),
ELM_PROT_USER2_CAN_11_S ( "User2 CAN (11* bit ID, 50* kbaud)" );
private String description;
PROT(String _description)
{
description = _description;
}
@Override
public String toString()
{
return description;
}
}
/**
* possible ELM responses and ID's
*/
enum RSP_ID
{
PROMPT ( ">" ),
OK ( "OK" ),
MODEL ( "ELM" ),
NODATA ( "NODATA" ),
SEARCH ( "SEARCHING" ),
ERROR ( "ERROR" ),
NOCONN ( "UNABLE" ),
NOCONN2 ( "NABLETO" ),
CANERROR ( "CANERROR" ),
BUSBUSY ( "BUSBUSY" ),
BUSERROR ( "BUSERROR" ),
BUSINIERR ( "BUSINIT:ERR" ),
BUSINIERR2( "BUSINIT:BUS" ),
BUSINIERR3( "BUSINIT:...ERR" ),
FBERROR ( "FBERROR" ),
DATAERROR ( "DATAERROR" ),
BUFFERFULL( "BUFFERFULL" ),
STOPPED ( "STOPPED" ),
RXERROR ( "<" ),
QMARK ( "?" ),
UNKNOWN ( "" );
private String response;
RSP_ID(String response)
{
this.response = response;
}
@Override
public String toString()
{
return response;
}
}
/**
* possible communication states
*/
public enum STAT
{
UNDEFINED ( "Undefined" ),
INITIALIZING ( "Initializing" ),
INITIALIZED ( "Initialized" ),
ECU_DETECT ( "ECU detect" ),
ECU_DETECTED ( "ECU detected" ),
CONNECTING ( "Connecting" ),
CONNECTED ( "Connected" ),
NODATA ( "No data" ),
STOPPED ( "Stopped" ),
DISCONNECTED ( "Disconnected" ),
BUSERROR ( "BUS error" ),
DATAERROR ( "DATA error" ),
RXERROR ( "RX error" ),
ERROR ( "Error" );
private String elmState;
STAT(String state)
{
elmState = state;
}
@Override
public String toString()
{
return elmState;
}
}
/**
* numeric IDs for commands
*/
public enum CMD
{
RESET( "Z" , 0, false), ///< reset adapter
DEFAULTS( "D" , 0, false), ///< set all to defaults
INFO( "I" , 0, true ), ///< request adapter info
LOWPOWER( "LP" , 0, true ), ///< switch to low power mode
ECHO( "E" , 1, true ), ///< enable/disable echo
SETLINEFEED( "L" , 1, true ), ///< enable/disable line feeds
SETSPACES( "S" , 1, true ), ///< enable/disable spaces
SETHEADER( "H" , 1, true ), ///< enable/disable header response
GETPROT( "DP" , 0, true ), ///< get protocol
SETPROT( "SP" , 1, true ), ///< set protocol
CANMONITOR( "MA" , 0, true ), ///< monitor CAN messages
SETPROTAUTO( "SPA" , 1, true ), ///< set protocol auto
SETTIMEOUT( "ST" , 2, true ), ///< set timeout (x*4ms)
SETTXHDR( "SH" , 3, true ), ///< set TX header
SETCANRXFLT( "CRA" , 3, true ), ///< set CAN RX filter
CLRCANRXFLT( "CRA" , 0, true ); ///< clear CAN RX filter
protected static final String CMD_HEADER = "AT";
private String command;
protected int paramDigits;
private boolean disablingAllowed;
private boolean enabled = true;
CMD(String cmd, int numDigitsParameter, boolean allowAdaption)
{
command = cmd;
paramDigits = numDigitsParameter;
disablingAllowed = allowAdaption;
}
@Override
public String toString()
{
return CMD_HEADER+command;
}
public boolean isEnabled()
{
return enabled;
}
public void setEnabled(boolean enabled)
{
if (disablingAllowed)
{
log.debug(String.format("ELM command '%s' -> %s",
toString(),
enabled ? "enabled" : "disabled"));
this.enabled = enabled;
}
}
public boolean isDisablingAllowed()
{
return disablingAllowed;
}
}
/**
* Adaptive ELM timing handler
* * optimizes ELM message timeout at runtime
*/
public class AdaptiveTiming
{
/**
* for ELM message timeout handling
*/
/** max. ELM Message Timeout [ms] */
private static final int ELM_TIMEOUT_MAX = 1000;
/** default ELM message timeout */
private static final int ELM_TIMEOUT_DEFAULT = 200;
/** Learning resolution of ELM Message Timeout [ms] */
private static final int ELM_TIMEOUT_RES = 4;
/** minimum ELM timeout */
protected int ELM_TIMEOUT_MIN = 12;
/** minimum ELM timeout (learned from vehicle) */
protected int ELM_TIMEOUT_LRN_LOW = 12;
/** ELM message timeout: defaults to approx 200 [ms] */
protected int elmMsgTimeout = ELM_TIMEOUT_MAX;
/** adaptive timing handling enabled? */
private boolean enabled = true;
/**
* adaptive timing handling enabled?
*/
public boolean isEnabled()
{
return enabled;
}
/**
* enable/disable adaptive timing handler
* @param newEnabled enable/disable
*/
public void setEnabled(boolean newEnabled)
{
enabled = newEnabled;
}
/**
* min. (configured) ELM Message Timeout
* @return minimum (configured) ELM timeout value [ms]
*/
public int getElmTimeoutMin()
{
return ELM_TIMEOUT_MIN;
}
/**
* Set min. (configured) ELM Message Timeout
* @param elmTimeoutMin minimum (configured) ELM timeout value [ms]
*/
public void setElmTimeoutMin(int elmTimeoutMin)
{
log.info(String.format("ELM min timeout: %d -> %d",
ELM_TIMEOUT_MIN, elmTimeoutMin));
ELM_TIMEOUT_MIN = elmTimeoutMin;
}
/**
* Initialize timing hadler
*/
public void initialize()
{
if(!enabled) return;
// since device just restarted, assume device timeout
// to be set to default value ...
elmMsgTimeout = ELM_TIMEOUT_DEFAULT;
// ... reset learned minimum timeout ...
setElmTimeoutLrnLow(getElmTimeoutMin());
// set default timeout
setElmMsgTimeout(ELM_TIMEOUT_DEFAULT);
}
/**
* Adapt ELM message timeout
* @param increaseTimeout increase/decrease timeout
*/
public void adapt(boolean increaseTimeout)
{
if(!enabled) return;
if(increaseTimeout)
{
// increase OBD timeout since we may expect answers too fast
if ((elmMsgTimeout + ELM_TIMEOUT_RES) < ELM_TIMEOUT_MAX)
{
// increase timeout, since we have just timed out
setElmMsgTimeout(elmMsgTimeout + ELM_TIMEOUT_RES);
// ... and limit MIN timeout for this session
setElmTimeoutLrnLow(elmMsgTimeout);
}
}
else
{
// reduce OBD timeout towards minimum limit
if ((elmMsgTimeout - ELM_TIMEOUT_RES) >= getElmTimeoutLrnLow())
{
setElmMsgTimeout(elmMsgTimeout - ELM_TIMEOUT_RES);
}
}
}
/**
* LOW Learn value ELM Message Timeout
* @return currently learned timout value [ms]
*/
private int getElmTimeoutLrnLow()
{
return ELM_TIMEOUT_LRN_LOW;
}
/**
* set LOW Learn value ELM Message Timeout
* @param elmTimeoutLrnLow new learn value [ms]
*/
private void setElmTimeoutLrnLow(int elmTimeoutLrnLow)
{
log.info(String.format("ELM learn timeout: %d -> %d",
ELM_TIMEOUT_LRN_LOW, elmTimeoutLrnLow));
ELM_TIMEOUT_LRN_LOW = elmTimeoutLrnLow;
}
/**
* Set message timeout to ELM adapter to wait for valid response from vehicle
* If this timeout expires before a valid response is received from the
* vehicle, the ELM adapter will respond with "NO DATA"
*
* @param newTimeout desired timeout in milliseconds
*/
private void setElmMsgTimeout(int newTimeout)
{
if (newTimeout > 0 && newTimeout != elmMsgTimeout)
{
log.info("ELM Timeout: " + elmMsgTimeout + " -> " + newTimeout);
// set the timeout variable
elmMsgTimeout = newTimeout;
// queue the new timeout message
pushCommand(CMD.SETTIMEOUT, newTimeout / 4);
}
}
}
/**
* Creates a new instance of ElmProtocol
*/
public ElmProt()
{
}
/**
* set preferred ELM protocol to be used
* @param protoIndex preferred ELM protocol index
*/
public static void setPreferredProtocol(int protoIndex)
{
preferredProtocol = PROT.values()[protoIndex];
log.info("Preferred protocol: " + preferredProtocol);
}
/**
* set ECU address to be received
* @param ecuAddress ECU address to be filtered / 0 = clear address filter
*/
public void setEcuAddress(int ecuAddress)
{
log.info(String.format("Set ECU address: 0x%x", ecuAddress));
selectedEcuAddress = ecuAddress;
// ensure headers are off
pushCommand(CMD.SETHEADER, 0);
// set/clear RX filter
pushCommand((selectedEcuAddress != 0) ? CMD.SETCANRXFLT : CMD.CLRCANRXFLT, selectedEcuAddress);
}
/**
* disable a set of ELM commands ELM commands from preference
* @param disabledCmds set of ELM commands (ATxx strings) to be disabled
*/
public static void disableCommands(Set<String> disabledCmds)
{
for (CMD cmd : CMD.values())
{
cmd.setEnabled(disabledCmds == null
|| !disabledCmds.contains(cmd.toString()));
}
}
/**
* create ELM command string from command id and paramter
*
* @param cmdID ID of ELM command
* @param param parameter for ELM command (0 if not required)
* @return command char sequence or NULL if command disabled/invalid
*/
public String createCommand(CMD cmdID, int param)
{
String cmd = null;
if(cmdID.isEnabled())
{
cmd = cmdID.toString();
// if parameter is required and provided, add parameter to command
if (cmdID.paramDigits > 0)
{
String fmtString = "%0".concat(String.valueOf(cmdID.paramDigits)).concat("X");
cmd += String.format(fmtString, param);
}
}
// return command String
return cmd;
}
/**
* send command to ELM adapter
*
* @param cmdID ID of ELM command
* @param param parameter for ELM command (0 if not required)
*/
public void sendCommand(CMD cmdID, int param)
{
// now send command
String cmd = createCommand(cmdID, param);
if(cmd != null) sendTelegram(cmd.toCharArray());
}
/**
* queue command to ELM command queue
*
* @param cmdID ID of ELM command
* @param param parameter for ELM command (0 if not required)
*/
public void pushCommand(CMD cmdID, int param)
{
String cmd = createCommand(cmdID, param);
if(cmd != null) cmdQueue.add(cmd);
}
@Override
public void sendTelegram(char[] buffer)
{
log.debug(this.toString() + " TX:'" + String.valueOf(buffer) + "'");
lastCommand = buffer;
super.sendTelegram(buffer);
}
/**
* return numeric ID to given response
*
* @param response clear text response from ELM adapter
*/
static RSP_ID getResponseId(String response)
{
RSP_ID result = RSP_ID.UNKNOWN;
for (RSP_ID id : RSP_ID.values())
{
if (response.startsWith(id.toString()))
{
result = id;
break;
}
}
// return ID
return (result);
}
/**
* send ELM adapter to sleep mode
*/
public void goToSleep()
{
sendCommand(CMD.LOWPOWER, 0);
}
/**
* reset ELM adapter
*/
public void reset()
{
// reset all learned protocol data
super.reset();
sendCommand(CMD.RESET, 0);
}
/**
* request addresses of all connected ECUs
* (received IDs are evaluated in @ref:handleDataMessage)
*/
private void queryEcus()
{
// set status to INITIALIZING
setStatus(STAT.ECU_DETECT);
// clear all identified ECU addresses
ecuAddresses.clear();
// clear selected ECU
selectedEcuAddress = 0;
// remember to disable headers again
pushCommand(CMD.SETHEADER, 0);
// request PIDs (from all devices)
cmdQueue.add("0100");
// enable headers
sendCommand(CMD.SETHEADER, 1);
}
private void initialize()
{
// set status to INITIALIZING
setStatus(STAT.INITIALIZING);
// push custom init commands
cmdQueue.addAll(customInitCommands);
// set to preferred protocol
pushCommand(CMD.SETPROT, preferredProtocol.ordinal());
// initialize adaptive timing handler
mAdaptiveTiming.initialize();
// speed up protocol by removing spaces and line feeds from output
pushCommand(CMD.SETSPACES, 0);
pushCommand(CMD.SETLINEFEED, 0);
// immediate set echo off
pushCommand(CMD.ECHO, 0);
}
/**
* Implementation of TelegramListener
*/
/** multiline response is pending, for responses w/o a length info */
private boolean responsePending = false;
/**
* handle incoming protocol telegram
*
* @param buffer - telegram buffer
* @return number of listeners notified
*/
@Override
public synchronized int handleTelegram(char[] buffer)
{
int result = 0;
String bufferStr = new String(buffer);
log.debug(this.toString() + " RX:'" + bufferStr + "'");
// empty result
if (buffer.length == 0)
{
return result;
}
// if ths is echo of last command
if (lastTxMsg.compareToIgnoreCase(bufferStr) == 0)
{
// ignore echoed command
return result;
}
// handle response
switch (getResponseId(bufferStr))
{
case SEARCH:
setStatus(status != STAT.ECU_DETECT ? STAT.CONNECTING : status);
// NO break here
case QMARK:
case NODATA:
case OK:
case ERROR:
case NOCONN:
case NOCONN2:
case CANERROR:
case BUSERROR:
case BUSINIERR:
case BUSINIERR2:
case BUSINIERR3:
case BUSBUSY:
case FBERROR:
case DATAERROR:
case BUFFERFULL:
case RXERROR:
case STOPPED:
// remember this as last received message
// do NOT respond immediately
lastRxMsg = bufferStr;
log.info("ELM rx:'" + bufferStr + "' ("+lastTxMsg+")");
break;
case MODEL:
initialize();
break;
// received a PROMPT, what was the last response?
case PROMPT:
// check for last received message
switch (getResponseId(lastRxMsg))
{
case NOCONN:
case NOCONN2:
case CANERROR:
case BUSERROR:
case BUSINIERR:
case BUSINIERR2:
case BUSINIERR3:
case BUSBUSY:
case FBERROR:
setStatus(STAT.DISCONNECTED);
// re-queue last command
cmdQueue.add(String.valueOf(lastCommand));
// Initialize adaptive timing
mAdaptiveTiming.initialize();
// set to AUTO protocol
sendCommand(CMD.SETPROT, PROT.ELM_PROT_AUTO.ordinal());
break;
case DATAERROR:
setStatus(STAT.DATAERROR);
sendCommand(CMD.RESET, 0);
break;
case BUFFERFULL:
case RXERROR:
setStatus(STAT.RXERROR);
sendCommand(CMD.RESET, 0);
break;
case ERROR:
setStatus(STAT.ERROR);
sendCommand(CMD.RESET, 0);
break;
case NODATA:
setStatus(STAT.NODATA);
// re-queue next data item
if(service != OBD_SVC_NONE)
cmdQueue.add(
String.valueOf(createTelegram(emptyBuffer, service, getNextSupportedPid()))
);
// increase OBD timeout since we may expect answers too fast
mAdaptiveTiming.adapt(true);
// NO break here since reaction is only quqeued
case MODEL:
case SEARCH:
case STOPPED:
// was already handled before prompt
case QMARK:
// last command stays ignored
case OK:
default:
// if there is a pending data response, handle it now ...
if(responsePending)
{
result = handleDataMessage(lastRxMsg);
}
// queued commands will be sent first
if (cmdQueue.size() > 0)
{
// get last command
String cmd = cmdQueue.lastElement();
// and remove it from list
cmdQueue.remove(cmd);
// send the command
sendTelegram(cmd.toCharArray());
} else
{
// all queued commands are sent -> we are done initializing
if(status == STAT.INITIALIZING)
{
// set status to initialized
setStatus(STAT.INITIALIZED);
// initiate query of connected ECUs
queryEcus();
break;
}
// all queued commands are sent -> we are done detecting ECUs
setStatus(status == STAT.ECU_DETECT ? STAT.ECU_DETECTED : status);
switch (service)
{
case OBD_SVC_VEH_INFO:
// if all pid's have been read once ...
if(pidsWrapped)
{
// ... terminate service loop
break;
}
// no break here ...
case OBD_SVC_DATA:
case OBD_SVC_FREEZEFRAME:
{
// otherwise the next PID will be requested
writeTelegram(emptyBuffer, service, getNextSupportedPid());
// reduce OBD timeout towards minimum limit
mAdaptiveTiming.adapt(false);
}
break;
case OBD_SVC_NONE:
default:
// do nothing
}
}
}
break;
// handle data response
default:
// if we are still initializing check for address entries
switch(status)
{
case ECU_DETECT:
{
// start of 0100 response is end of address
int adrEnd = bufferStr.indexOf("41");
if(adrEnd > 0)
{
// odd address length -> CAN address length = 3 digits + 2 digits frame type
if(adrEnd % 2 != 0) adrEnd = 3;
// extract address
String address = bufferStr.substring(0, adrEnd);
log.debug(String.format("Found ECU address: 0x%s", address));
// and add to list of addresses
ecuAddresses.add(Integer.valueOf(address, 16));
}
return lastRxMsg.length();
}
default:
break;
}
// we are connected ...
setStatus(STAT.CONNECTED);
// is this a length identifier?
if (buffer[0] == '0' && buffer.length == 3)
{
// then remember the length to be expected
charsExpected = Integer.valueOf(bufferStr, 16) * 2;
lastRxMsg = "";
return (result);
}
// is this a multy-line response
int idx = bufferStr.indexOf(':');
if (idx >= 0)
{
/* no length known, set marker for pending response
response will be finished on reception of prompt */
responsePending = (charsExpected == 0);
if(buffer[0] == '0')
{
// first line of a multiline message
lastRxMsg = bufferStr.substring(idx + 1);
}
else
{
// continuation lines
// concat response without line counter
lastRxMsg += bufferStr.substring(idx + 1);
}
} else
{
// otherwise use this as last received message
lastRxMsg = bufferStr;
charsExpected = 0;
responsePending = false;
}
// if we haven't received complete result yet, then wait for the rest
if (lastRxMsg.length() < charsExpected)
{
return (result);
}
// if response is finished, handle it
if(!responsePending)
{
result = handleDataMessage(lastRxMsg);
}
}
return (result);
}
/**
* forward data message for further handling
* @param lastRxMsg received message to be forwarded
* @return number of bytes processed
*/
private int handleDataMessage(String lastRxMsg)
{
int result = 0;
// otherwise process response
switch (service)
{
case OBD_SVC_NONE:
// ignore messages
break;
case OBD_SVC_CAN_MONITOR:
result = canProt.handleTelegram(lastRxMsg.toCharArray());
break;
default:
// Let the OBD protocol handle the telegram
result = super.handleTelegram(lastRxMsg.toCharArray());
}
return result;
}
// switch to exit the demo thread
public static boolean runDemo;
/**
* run threaded loop to simulate incoming telegrams
*/
public void run()
{
int value = 0;
Integer pid;
runDemo = true;
log.info("ELM DEMO thread started");
while (runDemo)
{
try
{
handleTelegram(RSP_ID.MODEL.toString().toCharArray());
setStatus(STAT.ECU_DETECT);
handleTelegram("SEARCHING...".toCharArray());
handleTelegram("7EA074100000000".toCharArray());
handleTelegram("7E8064100000000".toCharArray());
handleTelegram("7E9074100000000".toCharArray());
handleTelegram("7E9074100000000".toCharArray());
setStatus(STAT.ECU_DETECTED);
while (runDemo)
{
switch (service)
{
// read any kinds of trouble codes
case OBD_SVC_READ_CODES:
// simulate 12 TCs set as multy line response
// send codes as multy line response
handleTelegram("014".toCharArray());
handleTelegram("0:438920B920BD".toCharArray());
handleTelegram("1:C002242A246E02".toCharArray());
handleTelegram("2:36010101162453".toCharArray());
// number of codes = 12 + MIL ON
handleTelegram("41018C000000".toCharArray());
Thread.sleep(500);
break;
case OBD_SVC_PENDINGCODES:
// simulate 12 TCs set as subsequent single line responses
// send codes as subsequent single line responses
handleTelegram("470920B920BD".toCharArray());
handleTelegram("4709C002242A246E".toCharArray());
handleTelegram("4709023601010116".toCharArray());
handleTelegram("4709245300000000".toCharArray());
// number of codes = 12 + MIL OFF
handleTelegram("41010C000000".toCharArray());
Thread.sleep(500);
break;
case OBD_SVC_PERMACODES:
// simulate 12 TCs set as multy line response
// send codes as multy line response
handleTelegram("014".toCharArray());
handleTelegram("0:4A8920B920BD".toCharArray());
handleTelegram("1:C002242A246E02".toCharArray());
handleTelegram("2:36010101162453".toCharArray());
// number of codes = 12 + MIL ON
handleTelegram("41018C000000".toCharArray());
Thread.sleep(500);
break;
// otherwise send data ...
case OBD_SVC_DATA:
case OBD_SVC_FREEZEFRAME:
pid = getNextSupportedPid();
if (pid != 0)
{
value++;
value &= 0xFF;
// format new data message and handle it as new reception
handleTelegram(String.format(
service == OBD_SVC_DATA ? "4%X%02X%02X%02X%02X%02X" : "4%X%02X00%02X%02X%02X%02X",
service, pid, value, value, value, value).toCharArray());
} else
{
// simulate "ALL PIDs supported"
int i;
for (i = 0; i < 0xE0; i += 0x20)
handleTelegram(String.format(
service == OBD_SVC_DATA ? "4%X%02XFFFFFFFF"
: "4%X%02X00FFFFFFFF", service, i).toCharArray());
handleTelegram(String.format(
service == OBD_SVC_DATA ? "4%X%02XFFFFFFFE"
: "4%X%02X00FFFFFFFE", service, i).toCharArray());
}
break;
case OBD_SVC_VEH_INFO:
pid = getNextSupportedPid();
if (pid == 0)
{
// simulate "ALL pids supported"
handleTelegram("490054000000".toCharArray());
}
// send VIN "1234567890ABCDEFG"
handleTelegram("013".toCharArray());
handleTelegram("1:4902313233".toCharArray());
handleTelegram("2:34353637383930".toCharArray());
handleTelegram("3:41424344454647".toCharArray());
// send CAL-ID "GSPA..." without length id
handleTelegram("0:490401475350".toCharArray());
handleTelegram("1:412D3132333435".toCharArray());
handleTelegram("2:36373839303000".toCharArray());
// CAL-ID 01234567
handleTelegram("490601234567".toCharArray());
break;
case OBD_SVC_NONE:
// just keep quiet until soneone requests something
break;
default:
// respond "service not supported"
handleTelegram(String.format("7F%02X11", service).toCharArray());
Thread.sleep(500);
break;
}
Thread.sleep(50);
}
} catch (Exception ex)
{
log.error(ex.getLocalizedMessage());
}
}
log.info("ELM DEMO thread finished");
}
/**
* set custom initialisation commands
* @param commands custom initialisation commands
*/
public void setCustomInitCommands(String[] commands)
{
List<String> cmds = Arrays.asList(commands);
// reverse list, since all commands are pushed rather than queued
Collections.reverse(cmds);
// clear list
customInitCommands.clear();
// add all entries
customInitCommands.addAll(cmds);
}
/**
* Setter for property service.
*
* @param service New value of property service.
* @param clearLists clear data list for this service
*/
@Override
public void setService(int service, boolean clearLists)
{
// log the change in service
if (service != this.service)
{
log.info("OBD Service: " + this.service + "->" + service);
this.service = service;
// send corresponding command(s)
switch (service)
{
case OBD_SVC_CAN_MONITOR:
sendCommand(CMD.CANMONITOR, 0);
break;
default:
super.setService(service, clearLists);
}
}
}
/**
* set OBD service - compatibility function
* @param service New value of property service.
*/
public void setService(int service)
{
setService(service, true);
}
/**
* Holds value of property status.
*/
private STAT status;
/**
* Getter for property status.
*
* @return Value of property status.
*/
public STAT getStatus()
{
return this.status;
}
/**
* Setter for property status.
*
* @param status New value of property status.
*/
public void setStatus(STAT status)
{
STAT oldStatus = this.status;
this.status = status;
if (status != oldStatus)
{
log.info("Status change: " + oldStatus + "->" + status);
// ECUs detected -> send identified ECU addresses
if(status == STAT.ECU_DETECTED)
{
firePropertyChange(new PropertyChangeEvent(this, PROP_ECU_ADDRESS, null, ecuAddresses));
}
// now fire regular status change
firePropertyChange(new PropertyChangeEvent(this, PROP_STATUS, oldStatus, status));
}
}
}