diff --git a/customize/ncslearning.properties b/customize/ncslearning.properties new file mode 100644 index 00000000..00a2595c --- /dev/null +++ b/customize/ncslearning.properties @@ -0,0 +1,3 @@ +# use a : to separate possible table names to match against +ltft_table_column_names=Learning map TP shaft lattice point table: +ltft_table_row_names=Learning map N shaft lattice point table: \ No newline at end of file diff --git a/lib/linux/j2534.so b/lib/linux/j2534.so new file mode 100644 index 00000000..bf01bba6 Binary files /dev/null and b/lib/linux/j2534.so differ diff --git a/src/main/java/com/romraider/io/j2534/api/J2534ConnectionISO14230.java b/src/main/java/com/romraider/io/j2534/api/J2534ConnectionISO14230.java new file mode 100644 index 00000000..0b8250d5 --- /dev/null +++ b/src/main/java/com/romraider/io/j2534/api/J2534ConnectionISO14230.java @@ -0,0 +1,298 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.io.protocol.ncs.iso14230.NCSChecksumCalculator.calculateChecksum; +import static com.romraider.util.HexUtil.asHex; +import static com.romraider.util.ParamChecker.checkNotNull; +import static java.lang.System.arraycopy; +import static org.apache.log4j.Logger.getLogger; + +import org.apache.log4j.Logger; + +import com.romraider.io.connection.ConnectionManager; +import com.romraider.io.connection.ConnectionProperties; +import com.romraider.io.connection.KwpConnectionProperties; +import com.romraider.io.j2534.api.J2534Impl.Config; +import com.romraider.io.j2534.api.J2534Impl.Flag; +import com.romraider.io.j2534.api.J2534Impl.Protocol; +import com.romraider.io.j2534.api.J2534Impl.TxFlags; +import com.romraider.logger.ecu.comms.manager.PollingState; + +public final class J2534ConnectionISO14230 implements ConnectionManager { + private static final Logger LOGGER = getLogger(J2534ConnectionISO14230.class); + private J2534 api = null; + private int LOOPBACK = 0; + private int channelId; + private int deviceId; + private int msgId; + private byte[] lastResponse; + private long timeout; + private boolean commsStarted; + private final byte[] startReq = { + (byte) 0x81, (byte) 0x10, (byte) 0xFC, (byte) 0x81, (byte) 0x0E}; + private final byte[] stopReq = { + (byte) 0x81, (byte) 0x10, (byte) 0xFC, (byte) 0x82, (byte) 0x0F}; + + public J2534ConnectionISO14230(ConnectionProperties connectionProperties, String library) { + checkNotNull(connectionProperties, "connectionProperties"); + deviceId = -1; + commsStarted = false; + timeout = (long)connectionProperties.getConnectTimeout(); + initJ2534(connectionProperties, library); + LOGGER.info("J2534/ISO14230 connection initialised"); + } + + // Send request and wait for response with known length + public void send(byte[] request, byte[] response, PollingState pollState) { + checkNotNull(request, "request"); + checkNotNull(response, "response"); + checkNotNull(pollState, "pollState"); + + if (pollState.getCurrentState() == PollingState.State.STATE_0 && + pollState.getLastState() == PollingState.State.STATE_1) { + clearLine(); + } + + if (pollState.getCurrentState() == PollingState.State.STATE_0) { + api.writeMsg(channelId, request, timeout, TxFlags.NO_FLAGS); + } + api.readMsg(channelId, response, timeout); + + if (pollState.getCurrentState() == PollingState.State.STATE_1){ + if ( (response[0] + 2) == response.length + && response[response.length - 1] == calculateChecksum(response)) { + + lastResponse = new byte[response.length]; + arraycopy(response, 0, lastResponse, 0, response.length); + } + else{ + LOGGER.error(String.format( + "J2534/ISO14230 Bad Data response: %s", asHex(response))); + arraycopy(lastResponse, 0, response, 0, response.length); + pollState.setNewQuery(true); + } + } + } + + // Send request and wait specified time for response with unknown length + public byte[] send(byte[] request) { + checkNotNull(request, "request"); + api.writeMsg(channelId, request, timeout, TxFlags.NO_FLAGS); + return api.readMsg(channelId, 1, timeout); + } + + public void clearLine() { + boolean repeat = true; + while (repeat) { + LOGGER.debug("J2534/ISO14230 sending line break"); + int p3_min = getP3Min(); + setP3Min(2); + api.writeMsg( + channelId, + stopReq, + 0L, + TxFlags.WAIT_P3_MIN_ONLY); + setP3Min(p3_min); + api.clearBuffers(channelId); + boolean empty = false; + int i = 1; + do { + byte[] badBytes = api.readMsg(channelId, 700L); + if (badBytes.length > 0) { + LOGGER.debug(String.format( + "J2534/ISO14230 clearing line (stale data %d): %s", i, asHex(badBytes))); + empty = false; + i++; + } + else { + empty = true; + repeat = false; + } + } while (!empty && i <= 3); + } + try { + fastInit(); + } + catch (J2534Exception e) { + // If fastInit fails because the ECU is no longer responding, for + // a variety of reasons, ignore it and close off the connection + // cleanly + LOGGER.error(String.format( + "J2534/ISO14230 Error performing fast initialization after clearing line: %s", e.getMessage())); + } + } + + public void close() { + try { + if (commsStarted) stopComms(); + commsStarted = false; + } + catch (J2534Exception e) { + // If the stop command fails because the ECU is no longer responding, for + // a variety of reasons, ignore it and close off the connection + // cleanly + LOGGER.error(String.format( + "J2534/ISO14230 Error stopping communications after clearing line: %s", e.getMessage())); + } + stopMsgFilter(); + disconnectChannel(); + closeDevice(); + } + + private void initJ2534(ConnectionProperties connectionProperties, String library) { + try { + api = new J2534Impl(Protocol.ISO14230, library); + deviceId = api.open(); + try { + version(deviceId); + channelId = api.connect( + deviceId, Flag.ISO9141_NO_CHECKSUM.getValue(), + connectionProperties.getBaudRate()); + setConfig(channelId, (KwpConnectionProperties) connectionProperties); + msgId = api.startPassMsgFilter(channelId, (byte) 0x00, (byte) 0x00); + LOGGER.debug(String.format( + "J2534/ISO14230 connection success: deviceId:%d, channelId:%d, msgId:%d, baud:%d", + deviceId, channelId, msgId, connectionProperties.getBaudRate())); + fastInit(); + commsStarted = true; + } catch (Exception e) { + LOGGER.debug(String.format( + "J2534/ISO14230 exception: deviceId:%d, channelId:%d, msgId:%d", + deviceId, channelId, msgId)); + close(); + throw new J2534Exception(String.format( + "J2534/ISO14230 Error opening device: %s",e.getMessage()), e); + } + } catch (J2534Exception e) { + if (deviceId != -1) api.close(deviceId); + api = null; + throw new J2534Exception(e.getMessage(), e); + } + } + + private void version(int deviceId) { + if (!LOGGER.isDebugEnabled()) return; + final Version version = api.readVersion(deviceId); + LOGGER.info(String.format( + "J2534 Version => firmware: %s, dll: %s, api: %s", + version.firmware, version.dll, version.api)); + } + + private void setConfig(int channelId, KwpConnectionProperties connectionProperties) { + final ConfigItem p1Max = new ConfigItem(Config.P1_MAX.getValue(), + (connectionProperties.getP1Max() * 2)); + final ConfigItem p3Min = new ConfigItem(Config.P3_MIN.getValue(), + (connectionProperties.getP3Min() * 2)); + final ConfigItem p4Min = new ConfigItem(Config.P4_MIN.getValue(), + (connectionProperties.getP4Min() * 2)); + final ConfigItem loopback = new ConfigItem(Config.LOOPBACK.getValue(), + LOOPBACK); + final ConfigItem dataBits = new ConfigItem( + Config.DATA_BITS.getValue(), + (connectionProperties.getDataBits() == 8 ? 0 : 1)); + final ConfigItem parity = new ConfigItem( + Config.PARITY.getValue(), + connectionProperties.getParity()); + api.setConfig(channelId, dataBits, parity, p1Max, p3Min, p4Min, loopback); + LOGGER.debug(String.format("J2534/ISO14230 connection properties: %s", + connectionProperties.toString())); + } + + private void stopMsgFilter() { + try { + api.stopMsgFilter(channelId, msgId); + LOGGER.debug(String.format( + "J2534/ISO14230 stopped message filter:%s", msgId)); + } catch (Exception e) { + LOGGER.warn(String.format( + "J2534/ISO14230 Error stopping msg filter: %s", e.getMessage())); + } + } + + private void disconnectChannel() { + try { + api.disconnect(channelId); + LOGGER.debug(String.format( + "J2534/ISO14230 disconnected channel:%d", channelId)); + } catch (Exception e) { + LOGGER.warn(String.format( + "J2534/ISO14230 Error disconnecting channel: %s", e.getMessage())); + } + } + + private void closeDevice() { + try { + api.close(deviceId); + LOGGER.info(String.format( + "J2534/ISO14230 closed connection to device:%d", deviceId)); + } catch (Exception e) { + LOGGER.warn(String.format( + "J2534/ISO14230 Error closing device: %s", e.getMessage())); + } + } + + private void fastInit() { + final byte[] timing = api.fastInit(channelId, startReq); + LOGGER.debug(String.format( + "J2534/ISO14230 Fast Init: deviceId:%d, channelId:%d, timing:%s", + deviceId, channelId, asHex(timing))); + } + + private void stopComms() { + final byte[] response = send(stopReq); + LOGGER.debug(String.format("Stop comms Response = %s", asHex(response))); + } + + private void setP3Min(int msec) { + final ConfigItem p3_min = new ConfigItem( + Config.P3_MIN.getValue(), + msec); + api.setConfig(channelId, p3_min); + LOGGER.trace(String.format("Config set P3_MIN value = %d msec", msec / 2)); + } + + private int getP3Min() { + final ConfigItem[] configs = api.getConfig( + channelId, + Config.P3_MIN.getValue()); + int i = 10; + for (ConfigItem item : configs) { + if (Config.get(item.parameter) == Config.P3_MIN) { + i = item.value; + } + } + LOGGER.trace(String.format("Config get P3_MIN value = %d msec", i / 2)); + return i; + } +// +// private void addressLoadReset() { +// final byte[] loadReset = { +// (byte) 0x02, +// (byte) 0xAC, (byte) 0x81 +// (byte) 0x04, (byte) 0x21, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x2A +// }; +// //04210004012A +// api.writeMsg(channelId, loadReset, 155L, TxFlags.NO_FLAGS); +// LOGGER.debug(String.format("loadReset Request = %s", asHex(loadReset))); +// final byte[] response = api.readMsg(channelId, 1, 2000L); +// LOGGER.debug(String.format("loadReset Response = %s", asHex(response))); +// } +} diff --git a/src/main/java/com/romraider/io/protocol/ProtocolNCS.java b/src/main/java/com/romraider/io/protocol/ProtocolNCS.java new file mode 100644 index 00000000..dc67f386 --- /dev/null +++ b/src/main/java/com/romraider/io/protocol/ProtocolNCS.java @@ -0,0 +1,40 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.protocol; + +import com.romraider.logger.ecu.comms.manager.PollingState; +import com.romraider.logger.ecu.definition.Module; + + +public interface ProtocolNCS extends Protocol { + + byte[] constructEcuFastInitRequest(Module module); + + byte[] constructReadSidPidRequest(Module module, byte sid, byte[][] pid); + + byte[] constructLoadAddressRequest(byte[][] addresses); + + void validateLoadAddressResponse(byte[] response); + + void checkValidSidPidResponse(byte[] response); + + byte[] constructReadAddressRequest(Module module, byte[][] bs, + PollingState pollState); +} diff --git a/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSChecksumCalculator.java b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSChecksumCalculator.java new file mode 100644 index 00000000..34935db2 --- /dev/null +++ b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSChecksumCalculator.java @@ -0,0 +1,39 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.protocol.ncs.iso14230; + +import static com.romraider.util.ByteUtil.asByte; +import static com.romraider.util.ByteUtil.asInt; + +public final class NCSChecksumCalculator { + + private NCSChecksumCalculator() { + throw new UnsupportedOperationException(); + } + + public static byte calculateChecksum(byte[] bytes) { + int total = 0; + for (int i = 0; i < (bytes.length - 1); i++) { + byte b = bytes[i]; + total += asInt(b); + } + return asByte(total - ((total >>> 8) << 8)); + } +} diff --git a/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSLoggerProtocol.java b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSLoggerProtocol.java new file mode 100644 index 00000000..c7c78fbf --- /dev/null +++ b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSLoggerProtocol.java @@ -0,0 +1,210 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.protocol.ncs.iso14230; + +import static com.romraider.io.protocol.ncs.iso14230.NCSResponseProcessor.extractResponseData; +import static com.romraider.io.protocol.ncs.iso14230.NCSResponseProcessor.filterRequestFromResponse; +import static com.romraider.util.ParamChecker.checkNotNull; +import static com.romraider.util.ParamChecker.checkNotNullOrEmpty; +import static java.lang.System.arraycopy; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.romraider.io.protocol.Protocol; +import com.romraider.io.protocol.ProtocolNCS; +import com.romraider.logger.ecu.comms.io.protocol.LoggerProtocolNCS; +import com.romraider.logger.ecu.comms.manager.PollingState; +import com.romraider.logger.ecu.comms.query.EcuInit; +import com.romraider.logger.ecu.comms.query.EcuInitCallback; +import com.romraider.logger.ecu.comms.query.EcuQuery; +import com.romraider.logger.ecu.comms.query.EcuQueryData; +import com.romraider.logger.ecu.definition.Module; + +public final class NCSLoggerProtocol implements LoggerProtocolNCS { + private final ProtocolNCS protocol = new NCSProtocol(); + + @Override + public byte[] constructEcuFastInitRequest(Module module) { + return protocol.constructEcuFastInitRequest(module); + } + + @Override + public byte[] constructEcuInitRequest(Module module) { + return protocol.constructEcuInitRequest(module); + } + + @Override + public byte[] constructEcuResetRequest(Module module, int resetCode) { + return protocol.constructEcuResetRequest(module, resetCode); + } + + @Override + public byte[] constructReadAddressRequest(Module module, + Collection queries) { + return protocol.constructReadAddressRequest( + module, new byte[0][0]); + } + + @Override + public byte[] constructReadAddressRequest( + Module module, Collection queries, PollingState pollState) { + return protocol.constructReadAddressRequest( + module, new byte[0][0], pollState); + } + + @Override + public byte[] constructReadSidPidRequest(Module module, byte sid, byte[] pid) { + final byte[][] request = new byte[1][pid.length]; + arraycopy(pid, 0, request[0], 0, pid.length); + return protocol.constructReadSidPidRequest(module, sid, request); + } + + @Override + public byte[] constructLoadAddressRequest(Collection queries) { + Collection filteredQueries = filterDuplicates(queries); + return protocol.constructLoadAddressRequest( + convertToByteAddresses(filteredQueries)); + } + + @Override + public void validateLoadAddressResponse(byte[] response) { + protocol.validateLoadAddressResponse(response); + } + + @Override + public byte[] constructReadAddressResponse( + Collection queries, PollingState pollState) { + + checkNotNullOrEmpty(queries, "queries"); + // length + // one byte - Response sid + // one byte - option + // variable bytes of data defined for pid + // checksum + Collection filteredQueries = filterDuplicates(queries); + int numAddresses = 0; + for (EcuQuery ecuQuery : filteredQueries) { + numAddresses += EcuQueryData.getDataLength(ecuQuery); + } + return new byte[(numAddresses + 4)]; + } + + @Override + public byte[] preprocessResponse( + byte[] request, byte[] response, PollingState pollState) { + + return filterRequestFromResponse(request, response, pollState); + } + + @Override + public void processEcuInitResponse(EcuInitCallback callback, byte[] response) { + checkNotNull(callback, "callback"); + checkNotNullOrEmpty(response, "response"); + EcuInit ecuInit = protocol.parseEcuInitResponse(response); + callback.callback(ecuInit); + } + + @Override + public void processReadSidPidResponse(byte[] response) { + checkNotNullOrEmpty(response, "response"); + protocol.checkValidSidPidResponse(response); + } + + @Override + public void processEcuResetResponse(byte[] response) { + checkNotNullOrEmpty(response, "response"); + protocol.checkValidEcuResetResponse(response); + } + + // processes the response bytes and sets individual responses on corresponding query objects + @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())); + } + } + + @Override + public Protocol getProtocol() { + return protocol; + } + + @Override + public byte[] constructWriteAddressRequest( + Module module, byte[] writeAddress, byte value) { + + return protocol.constructWriteAddressRequest(module, writeAddress, value); + } + + @Override + public void processWriteResponse(byte[] data, byte[] response) { + checkNotNullOrEmpty(data, "data"); + checkNotNullOrEmpty(response, "response"); + protocol.checkValidWriteResponse(data, response); + } + + private Collection filterDuplicates(Collection queries) { + Collection filteredQueries = new ArrayList(); + for (EcuQuery query : queries) { + if (!filteredQueries.contains(query)) { + filteredQueries.add(query); + } + } + return filteredQueries; + } + + private byte[][] convertToByteAddresses(Collection queries) { + int byteCount = 0; + for (EcuQuery query : queries) { + byteCount += query.getAddresses().length; + } + byte[][] addresses = new byte[byteCount][]; + int i = 0; + for (EcuQuery query : queries) { + byte[] bytes = query.getBytes(); + int addrCount = query.getAddresses().length; + int addrLen = bytes.length / addrCount; + for (int j = 0; j < addrCount; j++) { + final byte[] addr = new byte[addrLen]; + arraycopy(bytes, j * addrLen, addr, 0, addr.length); + addresses[i++] = addr; + } + } + return addresses; + } +} diff --git a/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSProtocol.java b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSProtocol.java new file mode 100644 index 00000000..6e3dade9 --- /dev/null +++ b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSProtocol.java @@ -0,0 +1,405 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.protocol.ncs.iso14230; + +import static com.romraider.io.protocol.ncs.iso14230.NCSChecksumCalculator.calculateChecksum; +import static com.romraider.util.HexUtil.asHex; +import static com.romraider.util.ParamChecker.checkNotNull; +import static com.romraider.util.ParamChecker.checkNotNullOrEmpty; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.romraider.io.connection.ConnectionProperties; +import com.romraider.io.connection.KwpConnectionProperties; +import com.romraider.io.protocol.ProtocolNCS; +import com.romraider.logger.ecu.comms.manager.PollingState; +import com.romraider.logger.ecu.comms.query.EcuInit; +import com.romraider.logger.ecu.comms.query.NCSEcuInit; +import com.romraider.logger.ecu.definition.Module; +import com.romraider.logger.ecu.exception.InvalidResponseException; +import com.romraider.logger.ecu.exception.UnsupportedProtocolException; + +public final class NCSProtocol implements ProtocolNCS { + private final ByteArrayOutputStream bb = new ByteArrayOutputStream(255); + public static final byte PHY_ADDR = (byte) 0x80; + public static final byte READ_MEMORY_PADDING = (byte) 0x00; + public static final byte READ_MEMORY_COMMAND = (byte) 0xA0; + public static final byte READ_MEMORY_RESPONSE = (byte) 0xE0; + public static final byte LOAD_ADDRESS_COMMAND = (byte) 0xAC; + public static final byte LOAD_ADDRESS_RESPONSE = (byte) 0xEC; + public static final byte READ_LOAD_COMMAND = (byte) 0x21; + public static final byte READ_LOAD_RESPONSE = (byte) 0x61; + public static final byte WRITE_MEMORY_COMMAND = (byte) 0xB0; + public static final byte WRITE_MEMORY_RESPONSE = (byte) 0xF0; + public static final byte WRITE_ADDRESS_COMMAND = (byte) 0xB8; + public static final byte WRITE_ADDRESS_RESPONSE = (byte) 0xF8; + public static final byte FASTINIT_COMMAND = (byte) 0x81; + public static final byte FASTINIT_RESPONSE = (byte) 0xC1; + public static final byte ECU_ID_SID = (byte) 0x1A; + public static final byte OPTION_81 = (byte) 0x81; + public static final byte FIELD_TYPE_01 = (byte) 0x01; + public static final byte FIELD_TYPE_02 = (byte) 0x02; + public static final byte FIELD_TYPE_83 = (byte) 0x83; + public static final byte SID_21 = (byte) 0x21; + public static final byte SID_22 = (byte) 0x22; + public static final byte ECU_ID_SID_RESPONSE = (byte) 0x5A; + public static final byte READ_SID_GRP_RESPONSE = (byte) 0x62; + public static final byte ECU_RESET_COMMAND = (byte) 0x04; + public static final byte ECU_RESET_RESPONSE = (byte) 0x44; + public static final byte NCS_NRC = (byte) 0x7F; + public static final int RESPONSE_NON_DATA_BYTES = 3; + public static final int ADDRESS_SIZE = 3; + public static Module module; + + public byte[] constructEcuFastInitRequest(Module module) { + checkNotNull(module, "module"); + NCSProtocol.module = module; + final byte[] request = buildRequest( + FASTINIT_COMMAND, false, new byte[]{}); + return request; + } + + @Override + public byte[] constructEcuInitRequest(Module module) { + checkNotNull(module, "module"); + NCSProtocol.module = module; + // len SID opt chk + // 0x02 0x1A 0x81 0x9D + final byte[] request = buildRequest( + ECU_ID_SID, true, new byte[]{OPTION_81}); + return request; + } + + @Override + public byte[] constructReadSidPidRequest(Module module, byte sid, byte[][] pid) { + checkNotNull(module, "module"); + NCSProtocol.module = module; + final byte[] request = buildSidPidRequest(sid, true, pid); + return request; + } + + @Override + //TODO: not yet implemented + public byte[] constructWriteMemoryRequest( + Module module, byte[] address, byte[] values) { + + throw new UnsupportedProtocolException( + "Write memory command is not supported on for address: " + + asHex(address)); + } + + @Override + //TODO: not yet implemented + public byte[] constructWriteAddressRequest( + Module module, byte[] address, byte value) { + + throw new UnsupportedProtocolException( + "Write Address command is not supported on for address: " + + asHex(address)); + } + + @Override + //TODO: not yet implemented + public byte[] constructReadMemoryRequest( + Module module, byte[] address, int numBytes) { + + throw new UnsupportedProtocolException( + "Read memory command is not supported on for address: " + + asHex(address)); + } + + @Override + public byte[] constructLoadAddressRequest(byte[][] addresses) { + checkNotNullOrEmpty(addresses, "addresses"); + // len 0xac 0x81 fld_typ address1 [[fld_typ address2] ... [fld_typ addressN]] checksum + return buildLoadAddrRequest(true, addresses); + } + + @Override + public byte[] constructReadAddressRequest(Module module, byte[][] addresses) { + // read previously loaded addresses + // len 0x21 0x81 0x04 0x01 checksum + return buildRequest( + READ_LOAD_COMMAND, true, new byte[]{(byte) 0x81, (byte) 0x04, (byte) 0x01}); + } + + @Override + public byte[] constructReadAddressRequest(Module module, byte[][] bs, + PollingState pollState) { + byte opt_byte3; + if (pollState.isFastPoll()) { + // continuously read response of previously loaded addresses + // len 0x21 0x81 0x06 0x01 checksum + opt_byte3 = (byte) 0x06; + } + else { + // read one response of previously loaded addresses + // len 0x21 0x81 0x04 0x01 checksum + opt_byte3 = (byte) 0x04; + } + return buildRequest( + READ_LOAD_COMMAND, true, new byte[]{(byte) 0x81, opt_byte3, (byte) 0x01}); + } + + @Override + public byte[] preprocessResponse( + byte[] request, byte[] response, PollingState pollState) { + return NCSResponseProcessor.filterRequestFromResponse( + request, response, pollState); + } + + @Override + public byte[] parseResponseData(byte[] processedResponse) { + checkNotNullOrEmpty(processedResponse, "processedResponse"); + return NCSResponseProcessor.extractResponseData(processedResponse); + } + + @Override + public void checkValidEcuInitResponse(byte[] processedResponse) { + checkNotNullOrEmpty(processedResponse, "processedResponse"); + NCSResponseProcessor.validateResponse(processedResponse); + } + + @Override + public EcuInit parseEcuInitResponse(byte[] processedResponse) { + checkNotNullOrEmpty(processedResponse, "processedResponse"); + //final byte[] ecuInitBytes = parseResponseData(processedResponse); + return new NCSEcuInit(processedResponse); + } + + @Override + public void validateLoadAddressResponse(byte[] response) { + checkNotNullOrEmpty(response, "addressLoadResponse"); + NCSResponseProcessor.validateResponse(response); + } + + @Override + //TODO: not yet implemented + public byte[] constructEcuResetRequest(Module module, int resetCode) { + checkNotNull(module, "module"); + NCSProtocol.module = module; + return buildRequest((byte) 0, false, new byte[]{ECU_RESET_COMMAND}); + } + + @Override + public void checkValidSidPidResponse(byte[] response) { + checkNotNullOrEmpty(response, "SidPidResponse"); + NCSResponseProcessor.validateResponse(response); + } + + @Override + //TODO: not yet implemented + public void checkValidEcuResetResponse(byte[] processedResponse) { + checkNotNullOrEmpty(processedResponse, "processedResponse"); + byte responseType = processedResponse[4]; + if (responseType != ECU_RESET_RESPONSE) { + throw new InvalidResponseException( + "Unexpected OBD Reset response: " + + asHex(processedResponse)); + } + } + + @Override + public void checkValidWriteResponse(byte[] data, byte[] processedResponse) { + } + + @Override + public ConnectionProperties getDefaultConnectionProperties() { + return new KwpConnectionProperties() { + + public int getBaudRate() { + return 10400; + } + + public void setBaudRate(int b) { + + } + + public int getDataBits() { + return 8; + } + + public int getStopBits() { + return 1; + } + + public int getParity() { + return 0; + } + + public int getConnectTimeout() { + return 2000; + } + + public int getSendTimeout() { + return 255; + } + + + public int getP1Max() { + return 0; + } + + public int getP3Min() { + return 5; + } + + public int getP4Min() { + return 0; + } + }; + } + + private final byte[] buildRequest(byte command, boolean shortHeader, + byte[]... content) { + + int length = 1; + for (byte[] tmp : content) { + length += tmp.length; + } + byte[] request = new byte[0]; + try { + bb.reset(); + if (shortHeader) { + bb.write(length); + } + else { + bb.write(PHY_ADDR + length); + bb.write(module.getAddress()); + bb.write(module.getTester()); + } + bb.write(command); + for (byte[] tmp : content) { + bb.write(tmp); + } + bb.write((byte) 0x00); + request = bb.toByteArray(); + final byte cs = calculateChecksum(request); + request[request.length - 1] = cs; + } + catch (IOException e) { + e.printStackTrace(); + } + return request; + } + + private final byte[] buildSidPidRequest(byte command, boolean shortHeader, + byte[]... content) { + + int length = 3; + for (byte[] tmp : content) { + length += tmp.length; + } + byte[] request = new byte[0]; + try { + bb.reset(); + if (shortHeader) { + bb.write(length); + } + else { + bb.write(PHY_ADDR + length); + bb.write(module.getAddress()); + bb.write(module.getTester()); + } + bb.write(command); + for (byte[] tmp : content) { + bb.write(tmp); + } + bb.write((byte) 0x04); + bb.write((byte) 0x01); + bb.write((byte) 0x00); + request = bb.toByteArray(); + final byte cs = calculateChecksum(request); + request[request.length - 1] = cs; + } + catch (IOException e) { + e.printStackTrace(); + } + return request; + } + + private final byte[] buildLoadAddrRequest(boolean shortHeader, + byte[]... content) { + + int length = 2; + byte[] request = new byte[0]; + try { + bb.reset(); + if (shortHeader) { + bb.write(length); + } + else { + bb.write(PHY_ADDR); + bb.write(module.getAddress()); + bb.write(module.getTester()); + } + bb.write(LOAD_ADDRESS_COMMAND); + bb.write(OPTION_81); + for (byte[] tmp : content) { + if (tmp[0] == SID_21) { + bb.write(FIELD_TYPE_01); + bb.write(tmp, 1, tmp.length - 1); + continue; + } + if (tmp[0] == SID_22) { + bb.write(FIELD_TYPE_02); + bb.write(tmp, 1, tmp.length - 1); + continue; + } + if (tmp[0] == (byte) 0xFF) { + bb.write(FIELD_TYPE_83); + bb.write((byte) 0xFF); + bb.write(tmp); + } + else { //assume a short length ROM address + bb.write(FIELD_TYPE_83); + bb.write((byte) 0x00); + switch (tmp.length) { + case 1: + bb.write((byte) 0x00); + bb.write((byte) 0x00); + break; + case 2: + bb.write((byte) 0x00); + break; + case 3: + break; + } + bb.write(tmp); + } + } + bb.write(0); // reserve last byte for checksum + request = bb.toByteArray(); + if (shortHeader) { + request[0] = (byte)(request.length - 2); + } + else { + request[0] = (byte)(PHY_ADDR + request.length - 4); + } + final byte cs = calculateChecksum(request); + request[request.length - 1] = cs; + } + catch (IOException e) { + e.printStackTrace(); + } + return request; + } +} diff --git a/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSResponseProcessor.java b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSResponseProcessor.java new file mode 100644 index 00000000..efe6ba0b --- /dev/null +++ b/src/main/java/com/romraider/io/protocol/ncs/iso14230/NCSResponseProcessor.java @@ -0,0 +1,134 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.protocol.ncs.iso14230; + +import static com.romraider.io.protocol.ncs.iso14230.NCSChecksumCalculator.calculateChecksum; +import static com.romraider.io.protocol.ncs.iso14230.NCSProtocol.LOAD_ADDRESS_RESPONSE; +import static com.romraider.io.protocol.ncs.iso14230.NCSProtocol.ECU_ID_SID_RESPONSE; +import static com.romraider.io.protocol.ncs.iso14230.NCSProtocol.NCS_NRC; +import static com.romraider.io.protocol.ncs.iso14230.NCSProtocol.READ_LOAD_RESPONSE; +import static com.romraider.io.protocol.ncs.iso14230.NCSProtocol.RESPONSE_NON_DATA_BYTES; +import static com.romraider.io.protocol.ncs.iso14230.NCSProtocol.READ_SID_GRP_RESPONSE; +import static com.romraider.util.ByteUtil.asUnsignedInt; +import static com.romraider.util.HexUtil.asHex; +import static com.romraider.util.ParamChecker.checkNotNullOrEmpty; + +import java.util.Arrays; + +import com.romraider.logger.ecu.comms.manager.PollingState; +import com.romraider.logger.ecu.exception.InvalidResponseException; + +public final class NCSResponseProcessor { + + private NCSResponseProcessor() { + throw new UnsupportedOperationException(); + } + + public final static byte[] filterRequestFromResponse( + byte[] request, byte[] response, PollingState pollState) { + checkNotNullOrEmpty(response, "response"); + // If J2534 device Loopback is off, the request is filtered out by J2534 device + // and only the response is present + return response; + } + + public final static void validateResponse(byte[] response) { + checkNotNullOrEmpty(response, "response"); + assertTrue(response.length > RESPONSE_NON_DATA_BYTES, + "Invalid response length"); + validateChecksum(response); + if (response[1] == NCS_NRC) { + assertNrc((byte) (response[2] + 0x40), response[1], response[2], response[3], + "Request type not supported"); + } + assertOneOf(new byte[]{ECU_ID_SID_RESPONSE, LOAD_ADDRESS_RESPONSE, + READ_LOAD_RESPONSE, READ_SID_GRP_RESPONSE}, + response[1], "Invalid response code"); + } + + public final static byte[] extractResponseData(byte[] response) { + checkNotNullOrEmpty(response, "response"); + // len response_sid option response_data1 ... [response_dataN] + validateResponse(response); + final byte[] data = new byte[response.length - 4]; + System.arraycopy(response, RESPONSE_NON_DATA_BYTES, data, 0, data.length); + return data; + } + + private final static void validateChecksum(byte[] response) { + final byte calc_chk = calculateChecksum(response); + final byte pkt_cs = response[response.length - 1]; + assertTrue(calc_chk == pkt_cs, String.format( + "Response checksum match failure. Expected: %s, Actual: %s", + asHex(calc_chk), asHex(pkt_cs))); + } + + private final static void assertTrue(boolean condition, String msg) { + if (!condition) { + throw new InvalidResponseException(msg); + } + } + + private final static void assertNrc( + byte expected, byte actual, byte command, byte code, String msg) { + + if (actual == expected) { + String ec = "unsupported."; + if (code == 0x10) { + ec = "general reject no specific reason."; + } + if (code == 0x12) { + ec = "request sub-function is not supported or invalid format."; + } + if (code == 0x13) { + ec = "invalid format or length."; + } + if (code == 0x21) { + ec = "busy, repeat request."; + } + if (code == 0x22) { + ec = "conditions not correct or request sequence error."; + } + throw new InvalidResponseException(String.format( + "%s. Command: %s, %s", + msg, asHex(command), ec)); + } + } + + private final static void assertOneOf( + byte[] validOptions, byte actual, String msg) { + + for (byte option : validOptions) { + if (option == actual) { + return; + } + } + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < validOptions.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(asHex(validOptions[i])); + } + throw new InvalidResponseException(String.format( + "%s. Expected one of [%s]. Actual: %s.", + msg, builder.toString(), asHex(actual))); + } +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/io/connection/NCSLoggerConnection.java b/src/main/java/com/romraider/logger/ecu/comms/io/connection/NCSLoggerConnection.java new file mode 100644 index 00000000..58ce6b24 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/io/connection/NCSLoggerConnection.java @@ -0,0 +1,262 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.logger.ecu.comms.io.connection; + +import static com.romraider.util.HexUtil.asHex; +import static com.romraider.util.ParamChecker.checkNotNull; +import static com.romraider.util.ThreadUtil.sleep; +import static org.apache.log4j.Logger.getLogger; + +import java.util.Collection; +import java.util.Map; + +import org.apache.log4j.Logger; + +import com.romraider.Settings; +import com.romraider.util.SettingsManager; +import com.romraider.io.connection.ConnectionManager; +import com.romraider.io.protocol.ProtocolFactory; +import com.romraider.logger.ecu.comms.io.protocol.LoggerProtocolNCS; +import com.romraider.logger.ecu.comms.manager.PollingState; +import com.romraider.logger.ecu.comms.manager.PollingStateImpl; +import com.romraider.logger.ecu.comms.query.EcuInitCallback; +import com.romraider.logger.ecu.comms.query.EcuQuery; +import com.romraider.logger.ecu.definition.Module; +import com.romraider.logger.ecu.exception.SerialCommunicationException; + +public final class NCSLoggerConnection implements LoggerConnection { + private static final Logger LOGGER = getLogger(NCSLoggerConnection.class); + private final LoggerProtocolNCS protocol; + private final ConnectionManager manager; + private int queryCount; + + public NCSLoggerConnection(ConnectionManager manager) { + checkNotNull(manager, "manager"); + this.manager = manager; + final Settings settings = SettingsManager.getSettings(); + this.protocol = (LoggerProtocolNCS) ProtocolFactory.getProtocol( + settings.getLoggerProtocol(), + settings.getTransportProtocol()); + } + + @Override + //TODO: not yet implemented + public void ecuReset(Module module, int resetCode) { + byte[] request = protocol.constructEcuResetRequest(module, resetCode); + LOGGER.debug(String.format("%s Reset Request ---> %s", + module, asHex(request))); + byte[] response = manager.send(request); + byte[] processedResponse = protocol.preprocessResponse( + request, response, new PollingStateImpl()); + LOGGER.debug(String.format("%s Reset Response <--- %s", + module, asHex(processedResponse))); + protocol.processEcuResetResponse(processedResponse); + } + + @Override + // Build an init string similar to the SSM version so the logger definition + // can reference supported parameters with ecubyte/bit attributes. + public void ecuInit(EcuInitCallback callback, Module module) { + final byte[] initResponse = new byte[422]; + byte[] request = protocol.constructEcuInitRequest(module); + LOGGER.debug(String.format("%s ID Request ---> %s", + module, asHex(request))); + byte[] response = new byte[9]; + manager.send(request, response, new PollingStateImpl()); + LOGGER.debug(String.format("%s ID Response <--- %s", + module, asHex(response))); + System.arraycopy(response, 0, initResponse, 0, response.length - 1); + sleep(55L); + + final byte[] supportedPidsPid = { + (byte) 0x00, (byte) 0x20, (byte) 0x40, (byte) 0x60, + (byte) 0x80, (byte) 0xA0, (byte) 0xC0, (byte) 0xE0}; + int i = 8; + byte sid = (byte) 0x21; + boolean test_grp = true; + for (byte pid : supportedPidsPid) { + if (test_grp) { + request = protocol.constructReadSidPidRequest( + module, sid, new byte[]{pid}); + LOGGER.debug(String.format("%s SID %02X, PID Group %02X Request ---> %s", + module, sid, pid, asHex(request))); + response = new byte[8]; + manager.send(request, response, new PollingStateImpl()); + LOGGER.debug(String.format("%s SID %02X, PID Group %02X Response <--- %s", + module, sid ,pid, asHex(response))); + // Validate response + protocol.processReadSidPidResponse(response); + System.arraycopy(response, 3, initResponse, i, 4); + // Check lsb to see if next PID group is supported + if ((response[response[0]] & 0x01) == 0) { + test_grp = false; + } + } + i = i + 4; + } + sid = (byte) 0x22; + final byte[] highBytes = { + (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, + (byte) 0x15}; + for (byte hb : highBytes) { + if (hb == (byte) 0x13) { + test_grp = true; + for (byte pid : supportedPidsPid) { + if (test_grp) { + request = protocol.constructReadSidPidRequest( + module, sid, new byte[]{hb, pid}); + LOGGER.debug(String.format("%s SID %02X, PID Group %02X%02X Request ---> %s", + module, sid, hb, pid, asHex(request))); + response = new byte[9]; + manager.send(request, response, new PollingStateImpl()); + LOGGER.debug(String.format("%s SID %02X, PID Group %02X%02X Response <--- %s", + module, sid ,hb, pid, asHex(response))); + // Validate response + protocol.processReadSidPidResponse(response); + // Check lsb to see if next PID group is supported + if ((response[response[0]] & 0x01) == 0) { + test_grp = false; + } + final short[] supported = new short[2]; + for (int j = 0; j < 2; j++) { + supported[j] = (short) ((short)(response[j*2+4] << 8) + ((short)response[j*2+5] & 0x00FF)); + } + for (int k = 0; k < supported.length; k++) { + // ex: 7FFC2000 + for (int shift = 15; shift > -1; shift--) { + if (((1 << shift) & supported[k]) > 0) { + byte cid = (byte) ((16 - shift) + (k * 16)); + request = protocol.constructReadSidPidRequest( + module, sid, new byte[]{hb, cid}); + LOGGER.debug(String.format("%s SID %02X, PID %02X%02X Request ---> %s", + module, sid, hb, cid, asHex(request))); + response = new byte[7]; + manager.send(request, response, new PollingStateImpl()); + LOGGER.debug(String.format("%s SID %02X, PID %02X%02X Response <--- %s", + module, sid ,hb, cid, asHex(response))); + // Validate response + protocol.processReadSidPidResponse(response); + System.arraycopy(response, 5, initResponse, i, 1); + } + i++; + } + } + } + else { + i = i + 32; + } + } + i--; // move back one unused index byte position + } + else { + test_grp = true; + for (byte pid : supportedPidsPid) { + if (test_grp) { + request = protocol.constructReadSidPidRequest( + module, sid, new byte[]{hb, pid}); + LOGGER.debug(String.format("%s SID %02X, PID Group %02X%02X Request ---> %s", + module, sid, hb, pid, asHex(request))); + response = new byte[9]; + manager.send(request, response, new PollingStateImpl()); + LOGGER.debug(String.format("%s SID %02X, PID Group %02X%02X Response <--- %s", + module, sid ,hb, pid, asHex(response))); + // Validate response + protocol.processReadSidPidResponse(response); + System.arraycopy(response, 4, initResponse, i, 4); + // Check lsb to see if next PID group is supported + if ((response[response[0]] & 0x01) == 0) { + test_grp = false; + } + } + i = i + 4; + } + } + } + LOGGER.debug(String.format("%s Init Response <--- %s", + module, asHex(initResponse))); // contains ECUID + protocol.processEcuInitResponse(callback, initResponse); + } + + @Override + public final void sendAddressReads( + Collection queries, + Module module, + PollingState pollState) { + + if (queries.size() != queryCount + || pollState.isNewQuery()) { + // max data bytes 255 including TX loopback + int dataLength = 0; + for (EcuQuery query : queries) { + dataLength += query.getBytes().length; + } + // if length is too big then notify user to un-select some parameters + if (dataLength > 60) { + throw new SerialCommunicationException( + "Request message too large, un-select some parameters"); + } + final byte[] request = protocol.constructLoadAddressRequest(queries); + LOGGER.debug(String.format("Mode:%s %s Load address request ---> %s", + pollState.getCurrentState(), module, asHex(request))); + + final byte[] response = new byte[4]; + manager.send(request, response, pollState); + LOGGER.debug(String.format("Mode:%s %s Load address response <--- %s", + pollState.getCurrentState(), module, asHex(response))); + protocol.validateLoadAddressResponse(response); + queryCount = queries.size(); + } + final byte[] request = protocol.constructReadAddressRequest( + module, queries, pollState); + if (pollState.getCurrentState() == PollingState.State.STATE_0) { + LOGGER.debug(String.format("Mode:%s %s Read request ---> %s", + pollState.getCurrentState(), module, asHex(request))); + pollState.setLastState(PollingState.State.STATE_0); + } + final byte[] response = protocol.constructReadAddressResponse( + queries, pollState); + manager.send(request, response, pollState); + LOGGER.debug(String.format("Mode:%s %s Read response <--- %s", + pollState.getCurrentState(), module, asHex(response))); + protocol.processReadAddressResponses( + queries, response, pollState); + } + + public void clearQueryCount() { + queryCount = -1; + } + + @Override + public void clearLine() { + clearQueryCount(); + manager.clearLine(); + } + + @Override + public void close() { + clearQueryCount(); + manager.close(); + } + + @Override + public void sendAddressWrites(Map writeQueries, Module module) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/io/protocol/LoggerProtocolNCS.java b/src/main/java/com/romraider/logger/ecu/comms/io/protocol/LoggerProtocolNCS.java new file mode 100644 index 00000000..297c5601 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/io/protocol/LoggerProtocolNCS.java @@ -0,0 +1,43 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.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 LoggerProtocolNCS extends LoggerProtocol { + + byte[] constructEcuFastInitRequest(Module module); + + byte[] constructReadSidPidRequest(Module module, byte sid, byte[] pid); + + byte[] constructLoadAddressRequest(Collection queries); + + void validateLoadAddressResponse(byte[] response); + + void processReadSidPidResponse(byte[] response); + + byte[] constructReadAddressRequest(Module module, + Collection queries, PollingState pollState); +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/learning/NCSLearningTableValues.java b/src/main/java/com/romraider/logger/ecu/comms/learning/NCSLearningTableValues.java new file mode 100644 index 00000000..280be944 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/learning/NCSLearningTableValues.java @@ -0,0 +1,620 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.logger.ecu.comms.learning; + +import static com.romraider.logger.ecu.comms.io.connection.LoggerConnectionFactory.getConnection; +import static javax.swing.JOptionPane.ERROR_MESSAGE; +import static javax.swing.JOptionPane.WARNING_MESSAGE; +import static javax.swing.JOptionPane.showMessageDialog; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.swing.SwingWorker; + +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import com.romraider.Settings; +import com.romraider.logger.ecu.EcuLogger; +import com.romraider.logger.ecu.comms.io.connection.LoggerConnection; +import com.romraider.logger.ecu.comms.learning.parameter.NCSParameter; +import com.romraider.logger.ecu.comms.learning.parameter.NCSParameterCrossReference; +import com.romraider.logger.ecu.comms.learning.parameter.ParameterIdComparator; +import com.romraider.logger.ecu.comms.learning.tableaxis.NCSTableAxisQueryParameterSet; +import com.romraider.logger.ecu.comms.learning.tables.NCSLtftTableQueryBuilder; +import com.romraider.logger.ecu.comms.manager.PollingStateImpl; +import com.romraider.logger.ecu.comms.query.EcuQuery; +import com.romraider.logger.ecu.comms.query.EcuQueryImpl; +import com.romraider.logger.ecu.definition.EcuData; +import com.romraider.logger.ecu.definition.EcuDefinition; +import com.romraider.logger.ecu.definition.Module; +import com.romraider.logger.ecu.definition.Transport; +import com.romraider.logger.ecu.definition.xml.EcuDefinitionDocumentLoader; +import com.romraider.logger.ecu.definition.xml.EcuDefinitionInheritanceList; +import com.romraider.logger.ecu.definition.xml.EcuTableDefinitionHandler; +import com.romraider.logger.ecu.ui.MessageListener; +import com.romraider.logger.ecu.ui.paramlist.ParameterListTableModel; +import com.romraider.logger.ecu.ui.paramlist.ParameterRow; +import com.romraider.logger.ecu.ui.swing.tools.NCSLearningTableValuesResultsPanel; +import com.romraider.util.ParamChecker; + + +/** + * This class manages the building of ECU queries and retrieving the data to + * populate the table models which will be used by the Learning Table Values + * display panel. + */ +public final class NCSLearningTableValues extends SwingWorker + implements LearningTableValues { + + private static final Logger LOGGER = + Logger.getLogger(NCSLearningTableValues.class); + private static final List AF_TABLE_NAMES = Arrays.asList( + "A/F Learning #1 Airflow Ranges", + "A/F Learning #1 Airflow Ranges ", + "A/F Learning Airflow Ranges"); + // LTFT table column and row names can be overridden in the + // ./customize/ncslearning.properties file + private static List LTFT_TABLE_COLUMN_NAMES = Arrays.asList( + "Learning map TP shaft lattice point table"); + private static List LTFT_TABLE_ROW_NAMES = Arrays.asList( + "Learning map N shaft lattice point table"); + private final Map vehicleInfo = + new LinkedHashMap(); + private final List> afLearning = new ArrayList>(); + private EcuLogger logger; + private Settings settings; + private MessageListener messageListener; + private ParameterListTableModel parmeterList; + private EcuDefinition ecuDef; + private ParameterRow ltftTrim; + private int ltftTrimAddr; + private ParameterRow ltftCnt; + private int ltftCntAddr; + + public NCSLearningTableValues() {} + + public void init( + EcuLogger logger, + ParameterListTableModel dataTabParamListTableModel, + EcuDefinition ecuDef) { + + ParamChecker.checkNotNull(logger, "EcuLogger"); + ParamChecker.checkNotNull(dataTabParamListTableModel, + "ParameterListTableModel"); + this.logger = logger; + this.settings = logger.getSettings(); + this.messageListener = logger; + this.parmeterList = dataTabParamListTableModel; + this.ecuDef = ecuDef; + this.ltftTrim = null; + this.ltftTrimAddr = 0; + this.ltftCnt = null; + this.ltftCntAddr = 0; + loadProperties(); + } + + @Override + public final Void doInBackground() { + Document document = null; + if (ecuDef.getEcuDefFile() == null) { + showMessageDialog(logger, + "ECU definition file not found or undefined. Learning\n" + + "Table Values cannot be properly retrieved until an ECU\n" + + "defintion is defined in the Editor's Definition Manager.", + "ECU Defintion Missing", WARNING_MESSAGE); + return null; + } + else { + document = EcuDefinitionDocumentLoader.getDocument(ecuDef); + } + + final String transport = settings.getTransportProtocol(); + final Module module = settings.getDestinationTarget(); + if (settings.isCanBus()) { + settings.setTransportProtocol("ISO14230"); + final Module ecuModule = getModule("ECU"); + settings.setDestinationTarget(ecuModule); + } + final boolean logging = logger.isLogging(); + if (logging) logger.stopLogging(); + + String message = "Retrieving vehicle info & LTFT values..."; + messageListener.reportMessage(message); + buildVehicleInfoMap(ecuDef); + + try { + LoggerConnection connection = getConnection( + settings.getLoggerProtocol(), settings.getLoggerPort(), + settings.getLoggerConnectionProperties()); + try { + Collection queries = buildLearningQueries(); + // Break queries into two sets to avoid the ECU packet limit + int setSize = queries.size() / 2; + Collection querySet1 = new ArrayList(); + Collection querySet2 = new ArrayList(); + int s = 0; + for (EcuQuery q : queries) { + if (s < setSize) { + querySet1.add(q); + } + else { + querySet2.add(q); + } + s++; + } + LOGGER.trace( + String.format( + "Queries:%d, Set size:%d, Set 1 size:%d, Set 2 size:%d", + queries.size(), setSize, querySet1.size(), querySet2.size())); + + LOGGER.info(message); + connection.sendAddressReads( + querySet1, + settings.getDestinationTarget(), + new PollingStateImpl()); + connection.sendAddressReads( + querySet2, + settings.getDestinationTarget(), + new PollingStateImpl()); + LOGGER.info("Current vehicle info & LTFT values retrieved."); + + Collections.sort( + (List)queries, new ParameterIdComparator()); + + processEcuQueryResponses((List) queries); + + //TODO: afRanges to be replaced with Knock tables once known how they operate + String[] afRanges = new String[0]; + + message = "Retrieving LTFT column ranges..."; + messageListener.reportMessage(message); + String[] ltftCol = new String[0]; + queries.clear(); + queries = getTableAxisRanges(document, ecuDef, LTFT_TABLE_COLUMN_NAMES); + if (queries != null && !queries.isEmpty()) { + LOGGER.info(message); + connection.sendAddressReads( + queries, + settings.getDestinationTarget(), + new PollingStateImpl()); + LOGGER.info("LTFT column ranges retrieved."); + ltftCol = formatRanges(queries, "%.2f"); + } + + message = "Retrieving LTFT row ranges..."; + messageListener.reportMessage(message); + String[] ltftRow = new String[0]; + queries.clear(); + queries = getTableAxisRanges(document, ecuDef, LTFT_TABLE_ROW_NAMES); + if (queries != null && !queries.isEmpty()) { + LOGGER.info(message); + connection.sendAddressReads( + queries, + settings.getDestinationTarget(), + new PollingStateImpl()); + LOGGER.info("LTFT row ranges retrieved."); + ltftRow = formatRpmRanges(queries); + } + + List>> ltftQueryTables = new ArrayList>>(); + List> ltftQueryGroups = new ArrayList>(); + if (ltftTrim != null) { + for (int k = 0; k < 256; k += 128) { + ltftQueryGroups = new NCSLtftTableQueryBuilder().build( + ltftTrim, + ltftTrimAddr + k, + ltftRow.length, + ltftCol.length - 1); + ltftQueryTables.add(ltftQueryGroups); + + for (int i = 0; i < ltftQueryGroups.size(); i++) { + queries.clear(); + for (int j = 0; j < ltftQueryGroups.get(i).size(); j++) { + if (ltftQueryGroups.get(i).get(j) != null) { + queries.add(ltftQueryGroups.get(i).get(j)); + } + } + message = String.format( + "Retrieving Table %d LTFT row %d values...", + (k/128+1), i); + messageListener.reportMessage(message); + LOGGER.info(message); + setSize = queries.size() / 2; + querySet1 = new ArrayList(); + querySet2 = new ArrayList(); + s = 0; + for (EcuQuery q : queries) { + if (s < setSize) { + querySet1.add(q); + } + else { + querySet2.add(q); + } + s++; + } + LOGGER.trace( + String.format( + "Queries:%d, Set size:%d, Set 1 size:%d, Set 2 size:%d", + queries.size(), setSize, querySet1.size(), querySet2.size())); + + LOGGER.info(message); + connection.sendAddressReads( + querySet1, + settings.getDestinationTarget(), + new PollingStateImpl()); + connection.sendAddressReads( + querySet2, + settings.getDestinationTarget(), + new PollingStateImpl()); + LOGGER.info(String.format( + "Table %d row %d LTFT values retrieved.", + (k/128+1), i)); + queries.clear(); + } + } + } + else { + message = String.format( + "Error retrieving LTFT data values, missing LTFT reference"); + messageListener.reportMessage(message); + LOGGER.error(message); + } + if (ltftCnt != null) { + for (int k = 0; k < 128; k += 64) { + ltftQueryGroups = new NCSLtftTableQueryBuilder().build( + ltftCnt, + ltftCntAddr + k, + ltftRow.length, + ltftCol.length - 1); + ltftQueryTables.add(ltftQueryGroups); + + for (int i = 0; i < ltftQueryGroups.size(); i++) { + queries.clear(); + for (int j = 0; j < ltftQueryGroups.get(i).size(); j++) { + if (ltftQueryGroups.get(i).get(j) != null) { + queries.add(ltftQueryGroups.get(i).get(j)); + } + } + message = String.format("Retrieving Table %d LTFT row %d values...", + (k/64+3), i); + messageListener.reportMessage(message); + LOGGER.info(message); + setSize = queries.size() / 2; + querySet1 = new ArrayList(); + querySet2 = new ArrayList(); + s = 0; + for (EcuQuery q : queries) { + if (s < setSize) { + querySet1.add(q); + } + else { + querySet2.add(q); + } + s++; + } + LOGGER.trace( + String.format("Queries:%d, Set size:%d, Set 1 size:%d, Set 2 size:%d", + queries.size(), setSize, querySet1.size(), querySet2.size())); + + LOGGER.info(message); + connection.sendAddressReads( + querySet1, + settings.getDestinationTarget(), + new PollingStateImpl()); + connection.sendAddressReads( + querySet2, + settings.getDestinationTarget(), + new PollingStateImpl()); + LOGGER.info(String.format("Table %d row %d LTFT values retrieved.", + (k/64+3), i)); + queries.clear(); + } + } + } + else { + message = String.format("Error retrieving LTFT data values, missing LTFT reference"); + messageListener.reportMessage(message); + LOGGER.error(message); + } + + messageListener.reportMessage( + "Learning Table Values retrieved successfully."); + final NCSLearningTableValuesResultsPanel results = + new NCSLearningTableValuesResultsPanel( + logger, vehicleInfo, + afRanges, afLearning, + ltftCol, ltftRow, ltftQueryTables); + results.displayLearningResultsPanel(); + } + finally { + connection.close(); + settings.setTransportProtocol(transport); + settings.setDestinationTarget(module); + if (logging) logger.startLogging(); + } + } + catch (Exception e) { + messageListener.reportError( + "Unable to retrieve current ECU learning values"); + LOGGER.error(message + " Error retrieving values", e); + showMessageDialog(logger, + message + + "\nError performing Learning Table Values read.\n" + + "Check the following:\n" + + "* Logger has successfully conencted to the ECU\n" + + "* Correct COM port is selected (if not Openport 2)\n" + + "* Cable is connected properly\n* Ignition is ON\n", + "Learning Table Values", + ERROR_MESSAGE); + } + return null; + } + + /** + * Build a collection of queries based on the initialized values of + * parameters defined for this ECU. Also identify the LTFT Trim and Count + * parameters used to locate the LTFT tables. + * @return the supported parameter list filtered for only the Learning Table + * Value parameters needed. + */ + private final Collection buildLearningQueries() { + final Collection query = new ArrayList(); + final List parameterRows = parmeterList.getParameterRows(); + if (!ParamChecker.isNullOrEmpty(parameterRows)) { + for (ParameterRow parameterRow : parameterRows) { + final NCSParameter parameterId = + NCSParameter.fromValue(parameterRow.getLoggerData().getId()); + if (parameterId != null) { + switch (parameterId) { + case E173: + ltftTrimAddr = getParameterAddr(parameterRow); + ltftTrim = parameterRow; + break; + case E174: + ltftCntAddr = getParameterAddr(parameterRow); + ltftCnt = parameterRow; + break; + default: + query.add(buildEcuQuery(parameterRow)); + break; + } + } + } + } + return query; + } + + /** + * Build a query object for a parameter item. + * @return a new EcuQuery. + */ + private final EcuQuery buildEcuQuery(ParameterRow parameterRow) { + final EcuQuery ecuQuery = + new EcuQueryImpl((EcuData) parameterRow.getLoggerData()); + return ecuQuery; + } + + /** + * Return the parameter's integer address. + */ + private final int getParameterAddr(ParameterRow parameterRow) { + final EcuData ecudata = (EcuData) parameterRow.getLoggerData(); + final String addrStr = ecudata.getAddress().getAddresses()[0]; + final String addrHexStr = addrStr.replaceAll("0x", ""); + return Integer.parseInt(addrHexStr, 16); + } + + /** + * Start populating the vehicle information map with passed values. + */ + private final void buildVehicleInfoMap(EcuDefinition ecuDef) { + vehicleInfo.put("CAL ID", ecuDef.getCalId()); + vehicleInfo.put("ECU ID", ecuDef.getEcuId()); + vehicleInfo.put("Description", + ecuDef.getCarString().replaceAll("Nissan ", "")); + } + + /** + * Retrieve the table axis values from the ECU definition. First try the + * 4-cyl table names, if still empty try the 6-cyl table name. + */ + private final List getTableAxisRanges( + Document document, + EcuDefinition ecuDef, + List tableNames) { + + List tableAxis = new ArrayList(); + for (String tableName : tableNames) { + tableAxis = loadTable(document, ecuDef, tableName); + if (!tableAxis.isEmpty()) { + break; + } + } + return tableAxis; + } + + /** + * Once values from the ECU have been populated add the values to the + * table models datasets. + */ + private final void processEcuQueryResponses(List queries) { + final NCSParameterCrossReference parameterMap = new NCSParameterCrossReference(); + + for (EcuQuery query : queries) { + final NCSParameter parameterId = + NCSParameter.fromValue(query.getLoggerData().getId()); + final String paramDesc = parameterMap.getValue(parameterId); + String result = String.format("%.2f %s", + query.getResponse(), + query.getLoggerData().getSelectedConvertor().getUnits()); + switch (parameterId) { + default: + vehicleInfo.put(paramDesc, result); + break; + } + } + } + + /** + * Build a List of EcuQueries to retrieve the axis and scaling of a table. + * A table is found when the storageaddress parameter has been identified. + */ + private final List loadTable( + Document document, + EcuDefinition ecuDef, + String tableName) { + + final List inheritanceList = + EcuDefinitionInheritanceList.getInheritanceList(document, ecuDef); + final Map tableMap = + EcuTableDefinitionHandler.getTableDefinition( + document, + inheritanceList, + tableName); + List tableAxisQuery = new ArrayList(); + if (tableMap.containsKey("storageaddress")) { + tableAxisQuery = NCSTableAxisQueryParameterSet.build( + tableMap.get("storageaddress"), + tableMap.get("storagetype"), + tableMap.get("expression"), + tableMap.get("units"), + tableMap.get("sizey") + ); + } + return tableAxisQuery; + } + + /** + * Format the range data to be used as table column header values. + */ + private final String[] formatRanges( + Collection axisRanges, + String numberFormat) { + + final List ranges = new ArrayList(); + ranges.add(" "); + double value = 0; + for (EcuQuery ecuQuery : axisRanges) { + value = ecuQuery.getResponse(); + final String range = String.format( + numberFormat, + value); + ranges.add(range); + } + return ranges.toArray(new String[0]); + } + + /** + * Format the RPM range data to be used as FLKC table row header values. + */ + private final String[] formatRpmRanges(Collection axisRanges) { + + final List ranges = new ArrayList(); + double value = 0; + for (EcuQuery ecuQuery : axisRanges) { + value = ecuQuery.getResponse(); + final String range = String.format( + "%.0f", value); + ranges.add(range); + } + return ranges.toArray(new String[0]); + } + + /** + * Return a Transport based on its String ID. + */ + private Transport getTransportById(String id) { + for (Transport transport : getTransportMap().keySet()) { + if (transport.getId().equalsIgnoreCase(id)) + return transport; + } + return null; + } + + /** + * Return a Map of Transport and associated Modules for the current protocol. + */ + private Map> getTransportMap() { + return logger.getProtocolList().get(settings.getLoggerProtocol()); + } + + /** + * Return a Module based on its String name. + */ + private Module getModule(String name) { + final Collection modules = getTransportMap().get( + getTransportById(settings.getTransportProtocol())); + for (Module module: modules) { + if (module.getName().equalsIgnoreCase(name)) + return module; + } + return null; + } + + /** + * Load ECU def table names from a user customized properties file. + * The file will replace the Class defined names if it is present. + * Names in the file should be separated by the : character + * @exception FileNotFoundException if the directory or file is not present + * @exception IOException if there's some kind of IO error + */ + private void loadProperties() { + final Properties learning = new Properties(); + FileInputStream propFile; + try { + propFile = new FileInputStream("./customize/ncslearning.properties"); + learning.load(propFile); + final String ltft_table_column_names = + learning.getProperty("ltft_table_column_names"); + String[] names = ltft_table_column_names.split(":", 0); + LTFT_TABLE_COLUMN_NAMES = new ArrayList(); + for (String name : names) { + if (ParamChecker.isNullOrEmpty(name)) continue; + LTFT_TABLE_COLUMN_NAMES.add(name); + } + final String ltft_table_row_names = + learning.getProperty("ltft_table_row_names"); + names = ltft_table_row_names.split(":", 0); + LTFT_TABLE_ROW_NAMES = new ArrayList(); + for (String name : names) { + if (ParamChecker.isNullOrEmpty(name)) continue; + LTFT_TABLE_ROW_NAMES.add(name); + } + propFile.close(); + LOGGER.info("NCSLearningTableValues loaded table names from file: ./customize/ncslearning.properties"); + } catch (FileNotFoundException e) { + LOGGER.error("NCSLearningTableValues properties file: " + e.getLocalizedMessage()); + } catch (IOException e) { + LOGGER.error("NCSLearningTableValues IOException: " + e.getLocalizedMessage()); + } + } +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/learning/parameter/NCSParameter.java b/src/main/java/com/romraider/logger/ecu/comms/learning/parameter/NCSParameter.java new file mode 100644 index 00000000..e644ded8 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/learning/parameter/NCSParameter.java @@ -0,0 +1,68 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.logger.ecu.comms.learning.parameter; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +/** + * This Emun defines all the possible parameters used to query the Learning + * Table Values of an ECU. + */ +public enum NCSParameter { + P2("P2"), // ECT + P11("P11"), // IAT + P17("P17"), // Battery Volts + E173("E173"), // mixed ratio learning map trim + E174("E174"); // mixed ratio learning map count + + private static final Map lookup + = new HashMap(); + + static { + for(NCSParameter s : EnumSet.allOf(NCSParameter.class)) + lookup.put(s.toString(), s); + } + + private NCSParameter(final String text) { + this.text = text; + } + + private final String text; + + @Override + public final String toString() { + return text; + } + + /** + * Retrieve the Parameter that has the given value. + * @param value - the value of the Parameter in String format + * @return the Parameter that has the given value or null if undefined. + */ + public static NCSParameter fromValue(String value) { + NCSParameter result = null; + if (lookup.containsKey(value)) { + result = lookup.get(value); + } + return result; + } +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/learning/parameter/NCSParameterCrossReference.java b/src/main/java/com/romraider/logger/ecu/comms/learning/parameter/NCSParameterCrossReference.java new file mode 100644 index 00000000..b31e98f4 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/learning/parameter/NCSParameterCrossReference.java @@ -0,0 +1,50 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.logger.ecu.comms.learning.parameter; + +import static com.romraider.logger.ecu.comms.learning.parameter.NCSParameter.P11; +import static com.romraider.logger.ecu.comms.learning.parameter.NCSParameter.P17; +import static com.romraider.logger.ecu.comms.learning.parameter.NCSParameter.P2; + +import java.util.HashMap; +import java.util.Map; + +/** + * A Map of Parameter and value specific to the Vehicle Information Table. + */ +public class NCSParameterCrossReference { + final Map map; + + public NCSParameterCrossReference() { + map = new HashMap(); + map.put(P17, "Battery"); + map.put(P11, "IAT"); + map.put(P2, "ECT"); + } + +/** + * Retrieve the string value associated with the supplied Parameter. + * @param parameter - Parameter to lookup value for. + * @return the value of the Parameter. + */ + public final String getValue(NCSParameter parameter) { + return map.get(parameter); + } +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/learning/tableaxis/NCSTableAxisQueryParameterSet.java b/src/main/java/com/romraider/logger/ecu/comms/learning/tableaxis/NCSTableAxisQueryParameterSet.java new file mode 100644 index 00000000..f298aba9 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/learning/tableaxis/NCSTableAxisQueryParameterSet.java @@ -0,0 +1,91 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2015 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.logger.ecu.comms.learning.tableaxis; + +import static com.romraider.logger.ecu.definition.xml.ConverterMaxMinDefaults.getDefault; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.romraider.Settings; +import com.romraider.logger.ecu.comms.query.EcuQuery; +import com.romraider.logger.ecu.comms.query.EcuQueryData; +import com.romraider.logger.ecu.comms.query.EcuQueryImpl; +import com.romraider.logger.ecu.definition.EcuAddress; +import com.romraider.logger.ecu.definition.EcuAddressImpl; +import com.romraider.logger.ecu.definition.EcuData; +import com.romraider.logger.ecu.definition.EcuDataConvertor; +import com.romraider.logger.ecu.definition.EcuParameterConvertorImpl; +import com.romraider.logger.ecu.definition.EcuParameterImpl; +import com.romraider.util.HexUtil; + +/** + * Build a List of ECU Queries to retrieve a Table's axis values. + */ +public class NCSTableAxisQueryParameterSet { + + private NCSTableAxisQueryParameterSet() { + } + + /** + * Build a List of ECU Queries. + * @param storageAddress - the starting address of the Table. + * @param storageType - the data storage type (big endian assumed). + * @param expression - the equation to convert byte data to a real number. + * @param units - the value's unit of measure. + * @param size - the length of the Table's axis. + * @param endian - the data endian. + * @return a List of ECU Query items. + */ + public static final List build( + String storageAddress, + String storageType, + String expression, + String units, + String size) { + + final List tableAxisQuery = new ArrayList(); + final String tableAddrStr = storageAddress.replaceAll("0x", ""); + final int tableAddrBase = Integer.parseInt(tableAddrStr, 16); + + int dataSize = EcuQueryData.getDataLength(storageType); + + final int count = Integer.parseInt(size, 10); + for (int i = 0; i < count; i++) { + final String addrStr = + HexUtil.intToHexString(tableAddrBase + (i * dataSize)); + final String id = addrStr + "-" + i; + final EcuAddress ea = new EcuAddressImpl(addrStr, dataSize, -1); + final EcuParameterImpl epi = + new EcuParameterImpl(id, addrStr, id, ea, null, null, null, + new EcuDataConvertor[] { + new EcuParameterConvertorImpl( + units, expression, "0.000", -1, storageType, + Settings.ENDIAN_BIG, new HashMap(), + getDefault() + ) + } + ); + tableAxisQuery.add(new EcuQueryImpl((EcuData) epi)); + } + return tableAxisQuery; + } +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/learning/tables/NCSLtftTableQueryBuilder.java b/src/main/java/com/romraider/logger/ecu/comms/learning/tables/NCSLtftTableQueryBuilder.java new file mode 100644 index 00000000..10ee7689 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/learning/tables/NCSLtftTableQueryBuilder.java @@ -0,0 +1,103 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.logger.ecu.comms.learning.tables; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import com.romraider.logger.ecu.comms.query.EcuQuery; +import com.romraider.logger.ecu.comms.query.EcuQueryData; +import com.romraider.logger.ecu.comms.query.EcuQueryImpl; +import com.romraider.logger.ecu.definition.EcuAddress; +import com.romraider.logger.ecu.definition.EcuAddressImpl; +import com.romraider.logger.ecu.definition.EcuData; +import com.romraider.logger.ecu.definition.EcuDataConvertor; +import com.romraider.logger.ecu.definition.EcuParameterImpl; +import com.romraider.logger.ecu.ui.paramlist.ParameterRow; +import com.romraider.util.HexUtil; + +/** + * Build an EcuQuery for each of the cells in the FLKC RAM table. + */ +public class NCSLtftTableQueryBuilder { + private static final Logger LOGGER = + Logger.getLogger(NCSLtftTableQueryBuilder.class); + + public NCSLtftTableQueryBuilder() { + } + + /** + * Build an EcuQuery for each cell of the LTFT RAM table. Note this + * returns an extra null query for column 0 of each row which is later + * populated with the row header (RPM Ranges) data. + * @param ltft - a ParameterRow item that helps to identify the + * ECU bitness and provide a Converter for the raw data. + * @param ltftAddr - the address in RAM of the start of the table. + * @param rows - the number of rows in the table. + * @param columns - the number of columns in the table. + * @return EcuQueries divided into groups to query each row separately to + * avoid maxing out the ECU send/receive buffer. + */ + public final List> build( + ParameterRow ltft, + int ltftAddr, + int rows, + int columns) { + + final List> ltftQueryRows = new ArrayList>(); + final EcuData parameter = (EcuData) ltft.getLoggerData(); + int dataSize = EcuQueryData.getDataLength(parameter); + LOGGER.debug( + String.format( + "LTFT Data format rows:%d col:%d " + + "dataSize:%d LTFT:%s", + rows, columns, dataSize, + ltft.getLoggerData().getId())); + + int i = 0; + for (int j = 0; j < rows; j++) { + final List ltftQueryCols = new ArrayList(); + ltftQueryCols.add(null); + for (int k = 0; k < columns; k++) { + String id = "flkc-r" + j + "c" + k; + final String addrStr = + HexUtil.intToHexString( + ltftAddr + (i * dataSize)); + LOGGER.debug( + String.format( + "LTFT Data row:%d col:%d addr:%s", + j, k, addrStr)); + final EcuAddress ea = new EcuAddressImpl(addrStr, dataSize, -1); + final EcuParameterImpl epi = + new EcuParameterImpl(id, addrStr, id, ea, null, null, null, + new EcuDataConvertor[] { + ltft.getLoggerData().getSelectedConvertor() + } + ); + ltftQueryCols.add(new EcuQueryImpl((EcuData) epi)); + i++; + } + ltftQueryRows.add(ltftQueryCols); + } + return ltftQueryRows; + } +} diff --git a/src/main/java/com/romraider/logger/ecu/comms/query/NCSEcuInit.java b/src/main/java/com/romraider/logger/ecu/comms/query/NCSEcuInit.java new file mode 100644 index 00000000..3fb90b10 --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/comms/query/NCSEcuInit.java @@ -0,0 +1,44 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.logger.ecu.comms.query; + +import static com.romraider.util.ParamChecker.checkNotNullOrEmpty; +import static java.lang.System.arraycopy; + +public final class NCSEcuInit implements EcuInit { + private byte[] ecuInitBytes; + private String ecuId; + + public NCSEcuInit(byte[] ecuInitBytes) { + checkNotNullOrEmpty(ecuInitBytes, "ecuInitBytes"); + this.ecuInitBytes = ecuInitBytes; + final byte[] ecuIdBytes = new byte[5]; + arraycopy(ecuInitBytes, 3, ecuIdBytes, 0, 5); + ecuId = new String(ecuIdBytes); + } + + public String getEcuId() { + return ecuId; + } + + public byte[] getEcuInitBytes() { + return ecuInitBytes; + } +} diff --git a/src/main/java/com/romraider/logger/ecu/ui/swing/tools/NCSLearningTableValuesResultsPanel.java b/src/main/java/com/romraider/logger/ecu/ui/swing/tools/NCSLearningTableValuesResultsPanel.java new file mode 100644 index 00000000..a0e4bebe --- /dev/null +++ b/src/main/java/com/romraider/logger/ecu/ui/swing/tools/NCSLearningTableValuesResultsPanel.java @@ -0,0 +1,409 @@ +/* + * RomRaider Open-Source Tuning, Logging and Reflashing + * Copyright (C) 2006-2018 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.logger.ecu.ui.swing.tools; + +import static com.romraider.Settings.COMMA; +import static javax.swing.JOptionPane.ERROR_MESSAGE; +import static javax.swing.JOptionPane.INFORMATION_MESSAGE; +import static javax.swing.JOptionPane.showMessageDialog; + +import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; +import javax.swing.border.TitledBorder; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableColumn; + +import com.romraider.logger.ecu.EcuLogger; +import com.romraider.logger.ecu.comms.query.EcuQuery; +import com.romraider.logger.ecu.ui.swing.tools.tablemodels.FineLearningKnockCorrectionTableModel; +import com.romraider.logger.ecu.ui.swing.tools.tablemodels.VehicleInformationTableModel; +import com.romraider.logger.ecu.ui.swing.tools.tablemodels.renderers.CentreRenderer; +import com.romraider.logger.ecu.ui.swing.tools.tablemodels.renderers.LtvCellRenderer; +import com.romraider.logger.ecu.ui.swing.vertical.VerticalLabelUI; +import com.romraider.swing.SetFont; +import com.romraider.util.FormatFilename; +import com.romraider.util.SettingsManager; + +/** + * This class is used to build and display the Learning Table Values + * retrieved from the ECU. + */ +public class NCSLearningTableValuesResultsPanel extends JDialog { + private static final long serialVersionUID = 6716454297236022709L; + private final String DIALOG_TITLE = "Learning Table Values"; + private final String DT_FORMAT = "%1$tY%1$tm%1$td-%1$tH%1$tM%1$tS"; + private final int LTV_WIDTH = 720; + private final int LTV_HEIGHT = 450; + private final JPanel contentPanel = new JPanel(); + private JTable vehicleInfoTable; + //TODO: replace AF learning with a knock table + private JTable afLearningTable; + private List ltftTables = new ArrayList(); + private final String LTFT_NAME = "Long Term Fuel Trim Tables"; + + public NCSLearningTableValuesResultsPanel( + EcuLogger logger, + Map vehicleInfo, + String[] afRanges, + List> afLearning, + String[] ltftCol, + String[] ltftRow, + List>> ltftData) { + + super(logger, false); + setIconImage(logger.getIconImage()); + setTitle(DIALOG_TITLE); + setBounds( + (logger.getWidth() > LTV_WIDTH) ? + logger.getX() + (logger.getWidth() - LTV_WIDTH) / 2 : 0, + (logger.getHeight() > LTV_HEIGHT) ? + logger.getY() + ((logger.getHeight() - LTV_HEIGHT) / 2) : 0, + LTV_WIDTH, + LTV_HEIGHT); + getContentPane().setLayout(new BorderLayout()); + + contentPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); + contentPanel.setLayout(null); + contentPanel.add(buildVehicleInfoPanel(vehicleInfo)); +// contentPanel.add(buildAfLearningPanel(afRanges, afLearning)); + contentPanel.add(buildLtftPanel(ltftCol, ltftRow, ltftData)); + + getContentPane().add(contentPanel, BorderLayout.CENTER); + getContentPane().add(buildSaveReultsPanel(), BorderLayout.SOUTH); + } + + /** + * This method is called to display the Learning Table Values + * retrieved from the ECU. + */ + public final void displayLearningResultsPanel() { + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setVisible(true); + } + + private final JPanel buildVehicleInfoPanel( + Map vehicleInfo) { + + final JPanel vehicleInfoTitlePanel = new JPanel(); + vehicleInfoTitlePanel.setBorder( + BorderFactory.createTitledBorder("Vehicle Information")); + vehicleInfoTitlePanel.setBounds(10, 2, 692, 70); + vehicleInfoTitlePanel.setLayout(new BorderLayout(0, 0)); + + JPanel vehicleInfoTablePanel = new JPanel(); + vehicleInfoTablePanel.setBorder( + new EtchedBorder(EtchedBorder.LOWERED, null, null)); + vehicleInfoTablePanel.setLayout(new BorderLayout(0, 0)); + + final VehicleInformationTableModel tableModel = + new VehicleInformationTableModel(); + tableModel.setVehicleInfo(vehicleInfo); + vehicleInfoTable = new JTable(tableModel); + setTableBehaviour(vehicleInfoTable); + + TableColumn column = null; + for (int i = 0; i < vehicleInfoTable.getColumnCount(); i++) { + column = vehicleInfoTable.getColumnModel().getColumn(i); + if (i == 0 || i ==1) { + column.setPreferredWidth(85); + } + else if (i == 2) { + column.setPreferredWidth(200); + } + } + + vehicleInfoTablePanel.add( + formatTableHeader(vehicleInfoTable), + BorderLayout.PAGE_START); + vehicleInfoTablePanel.add(vehicleInfoTable); + + vehicleInfoTitlePanel.add(vehicleInfoTablePanel, BorderLayout.CENTER); + return vehicleInfoTitlePanel; + } + + private final JPanel buildLtftPanel( + String[] ltftLoad, + String[] ltftRpm, + List>> ltftQueryTables) { + + final JPanel ltftTitlePanel = new JPanel(); + ltftTitlePanel.setBorder( + new TitledBorder(null, + LTFT_NAME, + TitledBorder.LEADING, + TitledBorder.TOP, null, null)); + ltftTitlePanel.setBounds(10, 72, 692, 226); + ltftTitlePanel.setLayout(new BorderLayout(0, 0)); + + final JLabel xLabel = new JLabel("Injector Pule Width (msec)"); + SetFont.plain(xLabel); + xLabel.setHorizontalAlignment(SwingConstants.CENTER); + ltftTitlePanel.add(xLabel, BorderLayout.NORTH); + + final JLabel yLabel = new JLabel("Engine Speed (RPM)"); + SetFont.plain(yLabel); + yLabel.setUI(new VerticalLabelUI(false)); + ltftTitlePanel.add(yLabel, BorderLayout.WEST); + + final JTabbedPane tabs = new JTabbedPane(); + for (int i = 0; i < ltftQueryTables.size(); i++) { + final FineLearningKnockCorrectionTableModel tableModel = + new FineLearningKnockCorrectionTableModel(); + tableModel.setColumnHeadings(ltftLoad); + tableModel.setRomHeadings(ltftRpm); + tableModel.setFlkcData(ltftQueryTables.get(i)); + final JTable ltftTable = new JTable(tableModel); + ltftTables.add(ltftTable); + setTableBehaviour(ltftTable); + formatTableHeader(ltftTable); + final JScrollPane ltftTablePanel = new JScrollPane(ltftTable); + ltftTablePanel.setBorder( + new EtchedBorder(EtchedBorder.LOWERED, null, null)); + switch (i) { + case 0: + tabs.addTab("Left Bank - Trim (%)", ltftTablePanel); + break; + case 1: + tabs.addTab("Right Bank - Trim (%)", ltftTablePanel); + break; + case 2: + tabs.addTab("Left Bank - Count", ltftTablePanel); + break; + case 3: + tabs.addTab("Right Bank - Count", ltftTablePanel); + break; + } + } + if (ltftQueryTables.size() > 0) { + ltftTitlePanel.add(tabs, BorderLayout.CENTER); + } + else { + ltftTitlePanel.removeAll(); + ltftTitlePanel.add(new JLabel( + " No data - Knock Adaptation parameter ID not defined"), + BorderLayout.CENTER); + } + return ltftTitlePanel; + } + + private final void setTableBehaviour(JTable table) { + table.setBorder(null); + table.setColumnSelectionAllowed(false); + table.setRowSelectionAllowed(false); + table.setFont(new Font("Tahoma", Font.PLAIN, 11)); + table.setFillsViewportHeight(true); + table.setDefaultRenderer(Double.class, new LtvCellRenderer()); + table.setDefaultRenderer(String.class, new LtvCellRenderer()); + } + + private final JTableHeader formatTableHeader(JTable table) { + final JTableHeader th = table.getTableHeader(); + th.setReorderingAllowed(false); + th.setDefaultRenderer(new CentreRenderer(table)); + SetFont.bold(th, 11); + return th; + } + + private final JPanel buildSaveReultsPanel() { + + final JPanel controlPanel = new JPanel(); + final JButton toFile = new JButton("Save to File"); + toFile.setToolTipText("Save tables to a text file"); + toFile.setMnemonic(KeyEvent.VK_F); + toFile.addActionListener(new ActionListener() { + @Override + public final void actionPerformed(ActionEvent actionEvent) { + saveTableText(); + } + }); + final JButton toImage = new JButton("Save as Image"); + toImage.setToolTipText("Save tables as an image"); + toImage.setMnemonic(KeyEvent.VK_I); + toImage.addActionListener(new ActionListener() { + @Override + public final void actionPerformed(ActionEvent actionEvent) { + saveTableImage(); + } + }); + controlPanel.add(toFile); + controlPanel.add(toImage); + return controlPanel; + } + + private final void saveTableText() { + final String nowStr = String.format(DT_FORMAT, System.currentTimeMillis()); + final String fileName = String.format("%s%sromraiderLTV_%s.csv", + SettingsManager.getSettings().getLoggerOutputDirPath(), + File.separator, + nowStr); + try { + final File csvFile = new File(fileName); + final String EOL = System.getProperty("line.separator"); + final BufferedWriter bw = new BufferedWriter( + new FileWriter(csvFile)); + bw.write("Learning Table Values" + EOL); + Object result = 0; + int columnCount = vehicleInfoTable.getColumnCount(); + for (int i = 0; i < columnCount; i++ ) { + result = vehicleInfoTable.getTableHeader().getColumnModel(). + getColumn(i).getHeaderValue(); + bw.append(result.toString()); + bw.append(COMMA); + } + bw.append(EOL); + for (int i = 0; i < columnCount; i++ ) { + result = vehicleInfoTable.getValueAt(0, i); + bw.append(result.toString()); + bw.append(COMMA); + } + bw.append(EOL + EOL); +// bw.write("A/F Adaptation (Stored)" + EOL); +// columnCount = afLearningTable.getColumnCount(); +// int rowCount = afLearningTable.getRowCount(); +// for (int i = 0; i < columnCount; i++) { +// result = afLearningTable.getTableHeader().getColumnModel(). +// getColumn(i).getHeaderValue(); +// bw.append(result.toString()); +// bw.append(COMMA); +// } +// bw.append(EOL); +// for (int i = 0; i < rowCount; i++) { +// for (int j = 0; j < columnCount; j++) { +// result = afLearningTable.getValueAt(i, j); +// bw.append(result.toString()); +// bw.append(COMMA); +// } +// bw.append(EOL); +// } + bw.append(EOL); + bw.write(LTFT_NAME + EOL); + int k = 1; + for (JTable ltftTable : ltftTables) { + columnCount = ltftTable.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + result = ltftTable.getTableHeader().getColumnModel(). + getColumn(i).getHeaderValue(); + if (result.toString().equals(" ")) { + switch (k) { + case 1: + result = "Left Bank - Trim (%)"; + break; + case 2: + result = "Right Bank - Trim (%)"; + break; + case 3: + result = "Left Bank - Count"; + break; + case 4: + result = "Right Bank - Count"; + break; + } + } + bw.append(result.toString()); + bw.append(COMMA); + } + k++; + } + bw.append(EOL); + int rowCount = ltftTables.get(0).getRowCount(); + for (int i = 0; i < rowCount; i++) { + for (JTable ltftTable : ltftTables) { + columnCount = ltftTable.getColumnCount(); + for (int j = 0; j < columnCount; j++) { + result = ltftTable.getValueAt(i, j); + bw.append(result.toString()); + bw.append(COMMA); + } + } + bw.append(EOL); + } + bw.close(); + final String shortName = FormatFilename.getShortName(fileName); + showMessageDialog( + null, + "Table's text saved to: " + shortName, + "Save Success", + INFORMATION_MESSAGE); + } + catch (Exception e) { + showMessageDialog( + null, + "Failed to save tables, check path:\n" + fileName, + "Save Failed", + ERROR_MESSAGE); + } + } + + private final void saveTableImage() { + final BufferedImage resultsImage = new BufferedImage( + contentPanel.getWidth(), + contentPanel.getHeight(), + BufferedImage.TYPE_INT_ARGB); + contentPanel.paint(resultsImage.createGraphics()); + final String nowStr = String.format(DT_FORMAT, System.currentTimeMillis()); + final String fileName = String.format("%s%sromraiderLTV_%s.png", + SettingsManager.getSettings().getLoggerOutputDirPath(), + File.separator, + nowStr); + final String shortName = FormatFilename.getShortName(fileName); + try { + final File imageFile = new File(fileName); + ImageIO.write( + resultsImage, + "png", + imageFile); + showMessageDialog( + null, + "Learning Table Values image saved to: " + shortName, + "Save Success", + INFORMATION_MESSAGE); + } + catch (Exception e) { + showMessageDialog( + null, + "Failed to save image, check path:\n" + fileName, + "Save Failed", + ERROR_MESSAGE); + } + } +}