From b66ba92692ccb6d2bb0ffb6ab6ebbfd05b3773e3 Mon Sep 17 00:00:00 2001 From: Dale Schultz Date: Mon, 18 May 2020 20:25:47 -0400 Subject: [PATCH] Implement DS2 "Telegram" logging mode --- .../connection/DS2LoggerConnection.properties | 1 + .../romraider/io/protocol/ProtocolDS2.java | 11 +- .../ds2/iso9141/DS2LoggerProtocol.java | 99 +++++++++++++++- .../io/protocol/ds2/iso9141/DS2Protocol.java | 61 +++++++--- .../io/connection/DS2LoggerConnection.java | 109 +++++++++++++++--- .../comms/io/protocol/LoggerProtocolDS2.java | 24 +++- 6 files changed, 260 insertions(+), 45 deletions(-) create mode 100644 i18n/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.properties diff --git a/i18n/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.properties b/i18n/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.properties new file mode 100644 index 00000000..91d9c7ca --- /dev/null +++ b/i18n/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.properties @@ -0,0 +1 @@ +TOOLARGE = Parameter request too large (maximum 32), un-select some parameters diff --git a/src/main/java/com/romraider/io/protocol/ProtocolDS2.java b/src/main/java/com/romraider/io/protocol/ProtocolDS2.java index 135d1cd8..62cd1563 100644 --- a/src/main/java/com/romraider/io/protocol/ProtocolDS2.java +++ b/src/main/java/com/romraider/io/protocol/ProtocolDS2.java @@ -1,6 +1,6 @@ /* * RomRaider Open-Source Tuning, Logging and Reflashing - * Copyright (C) 2006-2014 RomRaider.com + * Copyright (C) 2006-2020 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 @@ -21,12 +21,17 @@ package com.romraider.io.protocol; import com.romraider.logger.ecu.definition.Module; - - public interface ProtocolDS2 extends Protocol { + byte[] constructReadProcedureRequest(Module module, byte[][] addresses); + byte[] constructReadGroupRequest(Module module, byte[][] addresses); byte[] constructReadMemoryRequest( Module module, byte[][] convertToByteAddresses, int length); + + byte[] constructSetAddressRequest( + Module module, byte[][] convertToByteAddresses); + + void validateSetAddressResponse(byte[] response); } diff --git a/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2LoggerProtocol.java b/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2LoggerProtocol.java index cf8b8ea4..2d76ea8a 100644 --- a/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2LoggerProtocol.java +++ b/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2LoggerProtocol.java @@ -1,6 +1,6 @@ /* * RomRaider Open-Source Tuning, Logging and Reflashing - * Copyright (C) 2006-2015 RomRaider.com + * Copyright (C) 2006-2020 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 @@ -22,6 +22,7 @@ package com.romraider.io.protocol.ds2.iso9141; import static com.romraider.io.protocol.ds2.iso9141.DS2Protocol.RESPONSE_NON_DATA_BYTES; import static com.romraider.io.protocol.ds2.iso9141.DS2ResponseProcessor.extractResponseData; import static com.romraider.io.protocol.ds2.iso9141.DS2ResponseProcessor.filterRequestFromResponse; +import static com.romraider.util.ByteUtil.asUnsignedInt; import static com.romraider.util.HexUtil.asBytes; import static com.romraider.util.ParamChecker.checkNotNull; import static com.romraider.util.ParamChecker.checkNotNullOrEmpty; @@ -60,8 +61,16 @@ public final class DS2LoggerProtocol implements LoggerProtocolDS2 { public byte[] constructReadAddressRequest( Module module, Collection queries) { - Collection filteredQueries = filterDuplicates(queries); return protocol.constructReadAddressRequest( + module, new byte[][]{}); + } + + @Override + public byte[] constructReadProcedureRequest( + Module module, Collection queries) { + + Collection filteredQueries = filterDuplicates(queries); + return protocol.constructReadProcedureRequest( module, convertToByteAddresses(filteredQueries)); } @@ -90,6 +99,26 @@ public final class DS2LoggerProtocol implements LoggerProtocolDS2 { module, convertToByteAddresses(queries), length); } + /** + * Convert address for the list of queries into the hex format the + * ECU expects. + **/ + @Override + public byte[] constructSetAddressRequest(Module module, + Collection queries) { + Collection filteredQueries = filterDuplicates(queries); + return protocol.constructSetAddressRequest( + module, convertListToByteAddresses(filteredQueries)); + } + + /** + * An Address set response is a 4 byte ACK sequence + **/ + @Override + public byte[] constructSetAddressResponse(int requestSize) { + return new byte[requestSize + RESPONSE_NON_DATA_BYTES]; + } + @Override public byte[] constructReadAddressResponse(Collection queries, PollingState pollState) { @@ -102,11 +131,11 @@ public final class DS2LoggerProtocol implements LoggerProtocolDS2 { checkNotNullOrEmpty(queries, "queries"); Collection filteredQueries = filterDuplicates(queries); - int numAddresses = 0; + int responseBytes = requestSize + RESPONSE_NON_DATA_BYTES; for (EcuQuery ecuQuery : filteredQueries) { - numAddresses += EcuQueryData.getDataLength(ecuQuery); + responseBytes += EcuQueryData.getDataLength(ecuQuery); } - return new byte[requestSize + RESPONSE_NON_DATA_BYTES + numAddresses]; + return new byte[responseBytes]; } @Override @@ -156,7 +185,7 @@ public final class DS2LoggerProtocol implements LoggerProtocolDS2 { * array. **/ @Override - public void processReadAddressResponses(Collection queries, byte[] response, PollingState pollState) { + public void processReadAddressResponse(Collection queries, byte[] response, PollingState pollState) { checkNotNullOrEmpty(queries, "queries"); checkNotNullOrEmpty(response, "response"); byte[] responseData = extractResponseData(response); @@ -215,6 +244,36 @@ public final class DS2LoggerProtocol implements LoggerProtocolDS2 { } } + @Override + public void processReadAddressResponses(Collection queries, + byte[] response, PollingState pollState) { + checkNotNullOrEmpty(queries, "queries"); + checkNotNullOrEmpty(response, "response"); + final byte[] responseData = extractResponseData(response); + final Collection filteredQueries = filterDuplicates(queries); + final Map addressResults = new HashMap(); + int i = 0; + for (EcuQuery filteredQuery : filteredQueries) { + final int dataLength = EcuQueryData.getDataLength(filteredQuery); + final byte[] data = new byte[dataLength]; + arraycopy(responseData, i, data, 0, dataLength); + addressResults.put(filteredQuery.getHex(), data); + i += dataLength; + } + for (EcuQuery query : queries) { + query.setResponse(addressResults.get(query.getHex())); + } + } + + /** + * Validate response headers, length and checksum + **/ + @Override + public void validateSetAddressResponse(byte[] response) { + checkNotNullOrEmpty(response, "response"); + protocol.validateSetAddressResponse(response); + } + @Override public Protocol getProtocol() { return protocol; @@ -263,6 +322,34 @@ public final class DS2LoggerProtocol implements LoggerProtocolDS2 { return addresses; } + private byte[][] convertListToByteAddresses(Collection queries) { + /* + * Address format is five bytes. First byte is data type + * (byte, word, procedure) followed by the four byte address. + */ + final byte[][] addresses = new byte[queries.size()][5]; + int i = 0; + for (EcuQuery query : queries) { + final int addrLength = query.getBytes().length; + final byte[] bytes = query.getBytes(); + for (int j = 0; j < bytes.length / addrLength; j++) { + arraycopy(bytes, j * addrLength, addresses[i], 1, addrLength); + } + // Update the first byte to the appropriate data type + // 0 = byte, 1 = word and 2 = procedure + // procedure + if (asUnsignedInt(bytes) < 0x1C) { + addresses[i][0] = 2; + } + // word or byte + else { + addresses[i][0] = (byte) (EcuQueryData.getDataLength(query) - 1); + } + i++; + } + return addresses; + } + private int getDataLength(Collection queries) { int dataLength = 0; for (EcuQuery query : queries) { diff --git a/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2Protocol.java b/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2Protocol.java index 861a3fbd..b2e546fb 100644 --- a/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2Protocol.java +++ b/src/main/java/com/romraider/io/protocol/ds2/iso9141/DS2Protocol.java @@ -1,6 +1,6 @@ /* * RomRaider Open-Source Tuning, Logging and Reflashing - * Copyright (C) 2006-2015 RomRaider.com + * Copyright (C) 2006-2020 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 @@ -40,12 +40,12 @@ import com.romraider.logger.ecu.exception.UnsupportedProtocolException; public final class DS2Protocol implements ProtocolDS2 { private static final byte[] READ_MEMORY_COMMAND = new byte[]{0x06, 0x00}; - private static final byte[] READ_ADDRESS_COMMAND = new byte[]{0x0B, 0x02, 0x0e}; + private static final byte[] READ_PROCEDURE_COMMAND = new byte[]{0x0B, 0x02}; private static final byte[] ECU_INIT_COMMAND = new byte[]{0x00}; private static final byte[] ECU_RESET_COMMAND = new byte[]{0x43}; + private static final byte[] SET_ADDRESS_COMMAND = new byte[]{0x0B, 0x01}; + private static final byte[] READ_ADDRESS_COMMAND = new byte[]{0x0B, 0x00}; public static final byte VALID_RESPONSE = (byte) 0xA0; - private static final int ADDRESS_SIZE = 3; - private static final int DATA_SIZE = 1; public static final int RESPONSE_NON_DATA_BYTES = 4; public static Module module; private final ByteArrayOutputStream bb = new ByteArrayOutputStream(255); @@ -97,9 +97,18 @@ public final class DS2Protocol implements ProtocolDS2 { public byte[] constructReadAddressRequest( Module module, byte[][] addresses) { checkNotNull(module, "module"); + // 0x12 0x05 0x0B 0x00 + return buildRequest( + READ_ADDRESS_COMMAND, new byte[0], new byte[]{0}); + } + + @Override + public byte[] constructReadProcedureRequest( + Module module, byte[][] addresses) { + checkNotNull(module, "module"); checkNotNullOrEmpty(addresses, "addresses"); - // 0x12 data_length group subgroup [address] checksum - return buildRequest(READ_ADDRESS_COMMAND, new byte[0], addresses); + // 0x12 data_length group subgroup [procedure] checksum + return buildRequest(READ_PROCEDURE_COMMAND, new byte[0], addresses); } public byte[] constructReadGroupRequest( @@ -110,6 +119,14 @@ public final class DS2Protocol implements ProtocolDS2 { return buildRequest(new byte[0], new byte[0], addresses); } + @Override + public byte[] constructSetAddressRequest(Module module, + byte[][] addresses) { + checkNotNull(module, "module"); + checkNotNullOrEmpty(addresses, "address"); + return buildRequest(SET_ADDRESS_COMMAND, new byte[0], addresses); + } + @Override public byte[] preprocessResponse(byte[] request, byte[] response, PollingState pollState) { return DS2ResponseProcessor.filterRequestFromResponse(request, response, pollState); @@ -128,6 +145,13 @@ public final class DS2Protocol implements ProtocolDS2 { DS2ResponseProcessor.validateResponse(processedResponse); } + @Override + public void validateSetAddressResponse(byte[] processedResponse) { + // Valid response is an ACK: 12 04 A0 B6 + checkNotNullOrEmpty(processedResponse, "processedResponse"); + DS2ResponseProcessor.validateResponse(processedResponse); + } + @Override public EcuInit parseEcuInitResponse(byte[] processedResponse) { return new DS2EcuInit(parseResponseData(processedResponse)); @@ -194,23 +218,34 @@ public final class DS2Protocol implements ProtocolDS2 { byte[] request = new byte[0]; try { int length = 3; - for (byte[] tmp : content) { - length += tmp.length; - } length += command.length; length += readLen.length; bb.reset(); bb.write(module.getAddress()); bb.write((byte) length); bb.write(command); - for (byte[] tmp : content) { - bb.write(tmp); + if (command == SET_ADDRESS_COMMAND) { + length++; + bb.write(content.length); // number of parameters } - if (readLen.length > 0) { + for (byte[] tmp : content) { // address payload + if (command == READ_PROCEDURE_COMMAND + && tmp.length == 3) {// legacy ADC def 0x0B020E support + length++; + bb.write((byte) 0x00); + } + else if (command == READ_ADDRESS_COMMAND) { + break; // no content + } + length += tmp.length; + bb.write(tmp); + } + if (readLen.length > 0) { // readMemory, # of bytes to reads bb.write(readLen); } - bb.write((byte) 0x00); + bb.write((byte) 0x00); // chksum placeholder request = bb.toByteArray(); + request[1] = (byte) length; // update length value final byte cs = calculateChecksum(request); request[request.length - 1] = cs; } catch (IOException e) { diff --git a/src/main/java/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.java b/src/main/java/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.java index e449e018..194251a8 100644 --- a/src/main/java/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.java +++ b/src/main/java/com/romraider/logger/ecu/comms/io/connection/DS2LoggerConnection.java @@ -1,6 +1,6 @@ /* * RomRaider Open-Source Tuning, Logging and Reflashing - * Copyright (C) 2006-2015 RomRaider.com + * Copyright (C) 2006-2020 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 @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.ResourceBundle; import org.apache.log4j.Logger; @@ -41,13 +42,18 @@ import com.romraider.logger.ecu.comms.query.EcuQuery; import com.romraider.logger.ecu.comms.query.EcuQueryRangeTest; import com.romraider.logger.ecu.definition.EcuData; import com.romraider.logger.ecu.definition.Module; +import com.romraider.logger.ecu.exception.SerialCommunicationException; +import com.romraider.util.ResourceUtil; import com.romraider.util.SettingsManager; public final class DS2LoggerConnection implements LoggerConnection { private static final Logger LOGGER = getLogger(DS2LoggerConnection.class); + private static final ResourceBundle rb = new ResourceUtil().getBundle( + DS2LoggerConnection.class.getName()); private final LoggerProtocolDS2 protocol; private final ConnectionManager manager; - Settings settings = SettingsManager.getSettings(); + private final Settings settings = SettingsManager.getSettings(); + private int queryCount; public DS2LoggerConnection(ConnectionManager manager) { checkNotNull(manager, "manager"); @@ -85,36 +91,44 @@ public final class DS2LoggerConnection implements LoggerConnection { Module module, PollingState pollState) { + // Group the queries into common command groups final Map> groupList = getGroupList(queries); + + // for each group populate the queries with results for (String group : groupList.keySet().toArray(new String[0])) { + final Collection querySet = groupList.get(group); byte[] request = new byte[0]; byte[] response = new byte[0]; - if (group.equalsIgnoreCase("0x0b0x020e")) { + final String groupTest = group.toLowerCase(); + + // read from procedure [02 XX XX XX PN] PN - Procedure Number<28 (0x1C) + if (groupTest.startsWith("0x0b0x02")) { for (EcuQuery query : querySet) { final Collection queryList = new ArrayList(); queryList.add(query); - request = protocol.constructReadAddressRequest( + request = protocol.constructReadProcedureRequest( module, queryList); - LOGGER.debug("Mode:" + pollState.getCurrentState() + " " + - module + " Request ---> " + asHex(request)); + LOGGER.debug(String.format("Mode:%s %s Procedure request ---> %s", + pollState.getCurrentState(), module, asHex(request))); response = protocol.constructReadAddressResponse( queryList, request.length); - protocol.processReadAddressResponses( + protocol.processReadAddressResponse( queryList, sendRcv(module, request, response, pollState), pollState); } } - else if (group.equalsIgnoreCase("0x060x00")) { + // read data starting at address [00 SG HI LO NN] NN - number of bytes<249 + else if (groupTest.startsWith("0x060x00")) { final EcuQueryRangeTest range = new EcuQueryRangeTest(querySet); final Collection newQuery = range.validate(); int length = range.getLength(); if (newQuery != null && length > 0) { request = protocol.constructReadMemoryRange( module, newQuery, length); - LOGGER.debug("Mode:" + pollState.getCurrentState() + " " + - module + " Request ---> " + asHex(request)); + LOGGER.debug(String.format("Mode:%s %s Range request ---> %s", + pollState.getCurrentState(), module, asHex(request))); response = protocol.constructReadMemoryRangeResponse( request.length, length); protocol.processReadMemoryRangeResponse( @@ -127,24 +141,75 @@ public final class DS2LoggerConnection implements LoggerConnection { queryList.add(query); request = protocol.constructReadMemoryRequest( module, queryList); - LOGGER.debug("Mode:" + pollState.getCurrentState() + " " + - module + " Request ---> " + asHex(request)); + LOGGER.debug(String.format("Mode:%s %s Memory request ---> %s", + pollState.getCurrentState(), module, asHex(request))); response = protocol.constructReadAddressResponse( queryList, request.length); - protocol.processReadAddressResponses( + protocol.processReadAddressResponse( queryList, sendRcv(module, request, response, pollState), pollState); } } } - else { + // Pre-defined Group parameter calls + // #03 Engine Parameters + // #04 Switch Parameters + // #91h Lambda Adaptations + // #92h Other Adaptations + // #93h Timing Correction/Fuel Compensation + else if (groupTest.startsWith("0x0b0x03") + || groupTest.startsWith("0x0b0x04") + || groupTest.startsWith("0x0b0x91") + || groupTest.startsWith("0x0b0x92") + || groupTest.startsWith("0x0b0x93")) { + request = protocol.constructReadGroupRequest( module, group); - LOGGER.debug("Mode:" + pollState.getCurrentState() + " " + - module + " Request ---> " + asHex(request)); + LOGGER.debug(String.format("Mode:%s %s Group request ---> %s", + pollState.getCurrentState(), module, asHex(request))); response = protocol.constructReadGroupResponse( querySet, request.length); + protocol.processReadAddressResponse( + querySet, + sendRcv(module, request, response, pollState), + pollState); + } + // user selected parameter list + // [01 NN B4 B3 B2 B1 B0 ... B4n B3n B2n B1n B0n] NN<33h + else if (groupTest.startsWith("0x0b0x01")) { + // update address list if size or new query changes + if (querySet.size() != queryCount + || pollState.isNewQuery()) { + // if too many parameters selected then notify user + // to un-select some + if (querySet.size() > 0x32) { + throw new SerialCommunicationException( + rb.getString("TOOLARGE")); + } + + // Set new address list + request = protocol.constructSetAddressRequest( + module, querySet); + LOGGER.debug(String.format("Mode:%s %s Load address request ---> %s", + pollState.getCurrentState(), module, asHex(request))); + pollState.setLastState(PollingState.State.STATE_0); + // Response to address list set is just an ACK + response = protocol.constructSetAddressResponse( + request.length); + protocol.validateSetAddressResponse( + sendRcv(module, request, response, pollState)); + queryCount = querySet.size(); + } + // Read set addresses + request = protocol.constructReadAddressRequest( + module, querySet); + LOGGER.debug(String.format("Mode:%s %s Read addresses request ---> %s", + pollState.getCurrentState(), module, asHex(request))); + pollState.setLastState(PollingState.State.STATE_0); + // Response to address list read is the parameter bytes + response = protocol.constructReadAddressResponse( + querySet, request.length); protocol.processReadAddressResponses( querySet, sendRcv(module, request, response, pollState), @@ -155,11 +220,13 @@ public final class DS2LoggerConnection implements LoggerConnection { @Override public void clearLine() { + clearQueryCount(); manager.clearLine(); } @Override public void close() { + clearQueryCount(); manager.close(); } @@ -189,7 +256,11 @@ public final class DS2LoggerConnection implements LoggerConnection { } } - // Create a map of groups each with a value of a list of queries having the same group + /** + * Return a map of groups each with a value of a list of queries having the same group + * @param queries + * @return Map(Group, Collection(EcuQuery)) + */ private Map> getGroupList(Collection queries) { final Map> groups = new HashMap>(); String group; @@ -219,4 +290,8 @@ public final class DS2LoggerConnection implements LoggerConnection { module + " Response <--- " + asHex(processedResponse)); return processedResponse; } + + public void clearQueryCount() { + queryCount = -1; + } } diff --git a/src/main/java/com/romraider/logger/ecu/comms/io/protocol/LoggerProtocolDS2.java b/src/main/java/com/romraider/logger/ecu/comms/io/protocol/LoggerProtocolDS2.java index 4b1d12a2..7523837e 100644 --- a/src/main/java/com/romraider/logger/ecu/comms/io/protocol/LoggerProtocolDS2.java +++ b/src/main/java/com/romraider/logger/ecu/comms/io/protocol/LoggerProtocolDS2.java @@ -1,6 +1,6 @@ /* * RomRaider Open-Source Tuning, Logging and Reflashing - * Copyright (C) 2006-2015 RomRaider.com + * Copyright (C) 2006-2020 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 @@ -21,22 +21,24 @@ package com.romraider.logger.ecu.comms.io.protocol; import java.util.Collection; +import com.romraider.logger.ecu.comms.manager.PollingState; import com.romraider.logger.ecu.comms.query.EcuQuery; import com.romraider.logger.ecu.definition.Module; - - public interface LoggerProtocolDS2 extends LoggerProtocol { + byte[] constructReadProcedureRequest(Module module, + Collection queries); + + byte[] constructReadAddressResponse( + Collection queries, int requestSize); + byte[] constructReadGroupRequest( Module module, String group); byte[] constructReadGroupResponse( Collection queries, int requestSize); - byte[] constructReadAddressResponse( - Collection queries, int requestSize); - byte[] constructReadMemoryRequest( Module module, Collection queryList); @@ -45,5 +47,15 @@ public interface LoggerProtocolDS2 extends LoggerProtocol { public byte[] constructReadMemoryRangeResponse(int requestSize, int length); + void processReadAddressResponse(Collection queries, + byte[] response, PollingState pollState); + void processReadMemoryRangeResponse(Collection queries, byte[] response); + + byte[] constructSetAddressRequest( + Module module, Collection queryList); + + byte[] constructSetAddressResponse(int length); + + void validateSetAddressResponse(byte[] response); }