diff --git a/src/main/java/com/romraider/io/j2534/api/TestJ2534IsoTp.java b/src/main/java/com/romraider/io/j2534/api/TestJ2534IsoTp.java new file mode 100644 index 00000000..8262934d --- /dev/null +++ b/src/main/java/com/romraider/io/j2534/api/TestJ2534IsoTp.java @@ -0,0 +1,361 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2013 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.j2534.api; + +import static com.romraider.util.ByteUtil.asUnsignedInt; +import static com.romraider.util.HexUtil.asHex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.romraider.io.j2534.api.J2534Impl.Config; +import com.romraider.io.j2534.api.J2534Impl.Protocol; +import com.romraider.io.j2534.api.J2534Impl.TxFlags; +import com.romraider.logger.ecu.exception.InvalidResponseException; +import com.romraider.util.HexUtil; +import com.romraider.util.LogManager; + +/** + * This class is used to exercise the J2534 API against a real J2534 device and + * an active ECU using the ISO15765-2 protocol. + */ +public final class TestJ2534IsoTp { + private static J2534 api ; + private static final int LOOPBACK = 0; + private static final byte[] mask1 = { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + private static final byte[] match1 = { + (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0xe8}; + private static final byte[] fc1 = { + (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0xe0}; + private static final byte ECU_NRC = (byte) 0x7F; + private static final byte READ_MODE9_RESPONSE = (byte) 0x49; + private static final byte READ_MODE9_COMMAND = (byte) 0x09; + private static final byte[] READ_MODE9_PIDS = { + (byte) 0x02, (byte) 0x04, (byte) 0x06}; + private static final byte READ_MODE3_RESPONSE = (byte) 0x43; + private static final byte READ_MODE3_COMMAND = (byte) 0x03; + private static final byte ECU_INIT_COMMAND = (byte) 0x01; + private static final byte ECU_INIT_RESPONSE = (byte) 0x41; + private static final StringBuilder sb = new StringBuilder(); + + + public TestJ2534IsoTp() throws InterruptedException { + final int deviceId = api.open(); + sb.delete(0, sb.capacity()); + try { + version(deviceId); + final int channelId = api.connect(deviceId, 0, 500000); + + final double vBatt = api.getVbattery(deviceId); +// System.out.println("Pin 16: " + vBatt + " VDC"); + sb.append(String.format( + "J2534 Interface Pin 16: %sVDC%n", vBatt)); + + final byte[] mask2 = { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + final byte[] match2 = { + (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0xdf}; + final byte[] fc2 = { + (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0xdf}; //Tester + + final byte[] mask3 = { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + final byte[] match3 = { + (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0xe0}; + final byte[] fc3 = { + (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0xe0}; + + final int msgId = api.startFlowCntrlFilter( + channelId, mask1, match1, fc1, TxFlags.ISO15765_FRAME_PAD); +// final int msgId1 = api.startFlowCntrlFilter( +// channelId, mask2, match2, fc2, TxFlags.ISO15765_FRAME_PAD); +// final int msgId2 = api.startFlowCntrlFilter( +// channelId, mask3, match3, fc3, TxFlags.ISO15765_FRAME_PAD); + + try { + setConfig(channelId); + getConfig(channelId); + sb.append(String.format("%n--- Vehicle Information ---%n")); + + for (byte pid : READ_MODE9_PIDS) { + final byte[] mode9 = buildRequest( + READ_MODE9_COMMAND, // mode + pid, // pid + true, // pid valid + fc1); // source + api.writeMsg(channelId, + mode9, + 1000L, + TxFlags.ISO15765_FRAME_PAD); + + final byte[] response; + response = api.readMsg(channelId, 1, 1000L); +// System.out.println("Response = " + +// HexUtil.asHex(response)); + handleResponse(response); + } + final byte[] mode3 = buildRequest( + READ_MODE3_COMMAND, // mode + (byte) 0x00, // pid + false, // pid valid + fc1); // source + api.writeMsg(channelId, + mode3, + 1000L, + TxFlags.ISO15765_FRAME_PAD); + + final byte[] response; + response = api.readMsg(channelId, 1, 1000L); +// System.out.println("Response = " + +// HexUtil.asHex(response)); + handleResponse(response); + } + catch (Exception e) { +// System.out.println(e); + sb.append(e); + } + finally { + api.stopMsgFilter(channelId, msgId); +// api.stopMsgFilter(channelId, msgId1); +// api.stopMsgFilter(channelId, msgId2); + api.disconnect(channelId); + } + } finally { + api.close(deviceId); + } + } + + public final String toString() { + return sb.toString(); + } + + private final static void handleResponse(byte[] response) { + validateResponse(response); + final int responseLen = response.length; + if (response[4] == READ_MODE9_RESPONSE) { + final byte[] data = new byte[responseLen - 7]; + System.arraycopy(response, 7, data, 0, data.length); + if (response[5] == 0x02) { +// System.out.println("VIN: " + new String(data)); + sb.append(String.format( + "VIN: %s%n", new String(data))); + } + if (response[5] == 0x04) { + int i; + for (i = 0; i < data.length && data[i] != 0; i++) { } + final String str = new String(data, 0, i); +// System.out.println("CAL ID: " + str); + sb.append(String.format( + "CAL ID: %s%n", str)); + } + if (response[5] == 0x06) { +// System.out.println("CVN: " + HexUtil.asHex(data)); + sb.append(String.format( + "CVN: %s%n", HexUtil.asHex(data))); + } + if (response[5] == 0x08) { +// System.out.println("PID_8: " + HexUtil.asHex(data)); + } + if (response[5] == 0x0A) { + int i; + for (i = 0; i < data.length && data[i] != 0; i++) { } + final String str = new String(data, 0, i); +// System.out.println("Module: " + str); + sb.append(String.format( + "Module: %s%n", str)); + } + } + if (response[4] == READ_MODE3_RESPONSE) { + if (response[5] > 0x00) { + final byte[] data = new byte[responseLen - 6]; + System.arraycopy(response, 6, data, 0, data.length); + int i; + int j = 1; + final byte[] codeHex = new byte[2]; + for (i = 0; i < data.length; i = i + 2) { + System.arraycopy(data, i, codeHex, 0, 2); + final byte module = (byte) ((codeHex[0] & 0xC0) >> 6); + String moduleTxt = null; + switch (module) { + case 0: + moduleTxt = "P"; + break; + case 1: + moduleTxt = "C"; + break; + case 2: + moduleTxt = "B"; + break; + case 3: + moduleTxt = "U"; + break; + } + + final byte dtcB1 = (byte) ((codeHex[0] & 0x30) >> 4); + final byte dtcB2 = (byte) (codeHex[0] & 0x0F); + final byte dtcB3 = (byte) ((codeHex[1] & 0xF0) >> 4); + final byte dtcB4 = (byte) (codeHex[1] & 0x0F); + +// System.out.print( +// String.format("DTC %d: %s%s%s%s%s%n", +// j, +// moduleTxt, +// Character.forDigit(dtcB1, 16), +// Character.forDigit(dtcB2, 16), +// Character.forDigit(dtcB3, 16), +// Character.forDigit(dtcB4, 16))); + sb.append(String.format( + "DTC %d: %s%s%s%s%s%n", + j, + moduleTxt, + Character.forDigit(dtcB1, 16), + Character.forDigit(dtcB2, 16), + Character.forDigit(dtcB3, 16), + Character.forDigit(dtcB4, 16))); + j++; + } + } + } + } + + private final static void version(final int deviceId) { + final Version version = api.readVersion(deviceId); +// System.out.printf("Version => Firmware:[%s], DLL:[%s], API:[%s]%n", +// version.firmware, version.dll, version.api); + sb.append(String.format( + "J2534 Firmware:[%s], DLL:[%s], API:[%s]%n", + version.firmware, version.dll, version.api)); + } + + private final static void setConfig(int channelId) { + final ConfigItem loopback = new ConfigItem(Config.LOOPBACK.getValue(), LOOPBACK); + final ConfigItem bs = new ConfigItem(Config.ISO15765_BS.getValue(), 0); + final ConfigItem stMin = new ConfigItem(Config.ISO15765_STMIN.getValue(), 0); + final ConfigItem bs_tx = new ConfigItem(Config.BS_TX.getValue(), 0xffff); + final ConfigItem st_tx = new ConfigItem(Config.STMIN_TX.getValue(), 0xffff); + final ConfigItem wMax = new ConfigItem(Config.ISO15765_WFT_MAX.getValue(), 0); + api.setConfig(channelId, loopback, bs, stMin, bs_tx, st_tx, wMax); + } + + private final static void getConfig(int channelId) { + final ConfigItem[] configs = api.getConfig( + channelId, + Config.LOOPBACK.getValue(), + Config.ISO15765_BS.getValue(), + Config.ISO15765_STMIN.getValue(), + Config.BS_TX.getValue(), + Config.STMIN_TX.getValue(), + Config.ISO15765_WFT_MAX.getValue() + ); + int i = 1; + for (ConfigItem item : configs) { +// System.out.printf("J2534 Config item %d: Parameter: %s, value:%d%n", +// i, Config.get(item.parameter), item.value); + sb.append(String.format( + "J2534 Config item %d: Parameter: %s, value:%d%n", + i, Config.get(item.parameter), item.value)); + i++; + } + } + + private final static void validateResponse(byte[] response) { + assertEquals(match1, response, "Invalid ECU id"); + if (response.length == 7) { + assertNrc(ECU_NRC, response[4], response[5], response[6],"Request type not supported"); + } + assertOneOf(new byte[]{ECU_INIT_RESPONSE, READ_MODE3_RESPONSE, READ_MODE9_RESPONSE}, response[4], "Invalid response code"); + } + + private final static void assertNrc(byte expected, byte actual, byte command, byte code, String msg) { + if (actual == expected) { + String ec = " unsupported."; + if (code == 0x13) { + ec = " invalid format or length."; + } + throw new InvalidResponseException( + msg + ". Command: " + asHex(new byte[]{command}) + ec); + } + } + + private final static void assertEquals(byte[] expected, byte[] actual, String msg) { + final byte[] idBytes = new byte[4]; + System.arraycopy(actual, 0, idBytes, 0, 4); + final int idExpected = asUnsignedInt(expected); + final int idActual = asUnsignedInt(idBytes); + if (idActual != idExpected) { + throw new InvalidResponseException( + msg + ". Expected: " + asHex(expected) + + ". Actual: " + asHex(idBytes) + "."); + } + } + + private final static void assertOneOf(byte[] validOptions, byte actual, String msg) { + for (byte option : validOptions) { + if (option == actual) { + return; + } + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < validOptions.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(asHex(new byte[]{validOptions[i]})); + } + throw new InvalidResponseException(msg + ". Expected one of [" + builder.toString() + "]. Actual: " + asHex(new byte[]{actual}) + "."); + } + + private final static byte[] buildRequest( + byte mode, + byte pid, + boolean pidValid, + byte[]... content) { + + final ByteArrayOutputStream bb = new ByteArrayOutputStream(6); + try { + for (byte[] tmp : content) { + bb.write(tmp); + } + bb.write(mode); + if (pidValid) { + bb.write(pid); + } + } catch (IOException e) { + e.printStackTrace(); + } + return bb.toByteArray(); + } + + public final static void main(String args[]) throws InterruptedException{ + LogManager.initDebugLogging(); + if (args.length == 0) { //op20pt32 MONGI432 + api = new J2534Impl( + Protocol.ISO15765, "op20pt32"); + } + else { + api = new J2534Impl( + Protocol.ISO15765, args[0]); + } + + final TestJ2534IsoTp test1 = new TestJ2534IsoTp(); + System.out.print(test1.toString()); + } +}