/*
* 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.io.protocol.ssm.iso9141;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.romraider.io.connection.ConnectionProperties;
import com.romraider.io.protocol.Protocol;
import static com.romraider.io.protocol.ssm.iso9141.SSMChecksumCalculator.calculateChecksum;
import com.romraider.logger.ecu.comms.manager.PollingState;
import com.romraider.logger.ecu.comms.manager.PollingStateImpl;
import com.romraider.logger.ecu.comms.query.EcuInit;
import com.romraider.logger.ecu.comms.query.SSMEcuInit;
import com.romraider.logger.ecu.definition.Module;
import com.romraider.logger.ecu.exception.InvalidResponseException;
import static com.romraider.util.ByteUtil.asByte;
import static com.romraider.util.HexUtil.asHex;
import static com.romraider.util.ParamChecker.checkGreaterThanZero;
import static com.romraider.util.ParamChecker.checkNotNull;
import static com.romraider.util.ParamChecker.checkNotNullOrEmpty;
public final class SSMProtocol implements Protocol {
public static final byte HEADER = (byte) 0x80;
public static final byte READ_ADDRESS_ONCE = (byte) 0x00;
public static final byte READ_ADDRESS_CONTINUOUS = (byte) 0x01;
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 READ_ADDRESS_COMMAND = (byte) 0xA8;
public static final byte READ_ADDRESS_RESPONSE = (byte) 0xE8;
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 ECU_INIT_COMMAND = (byte) 0xBF;
public static final byte ECU_INIT_RESPONSE = (byte) 0xFF;
public static final int ADDRESS_SIZE = 3;
public static final int DATA_SIZE = 1;
public static final int RESPONSE_NON_DATA_BYTES = 6;
public static final int REQUEST_NON_DATA_BYTES = 7;
public static Module module;
private final PollingState pollState = new PollingStateImpl();
private final ByteArrayOutputStream bb = new ByteArrayOutputStream(255);
public byte[] constructEcuInitRequest(Module module) {
checkNotNull(module, "module");
SSMProtocol.module = module;
// 0x80 0x10 0xF0 0x01 0xBF 0x40
return buildRequest(ECU_INIT_COMMAND, false, new byte[0]);
}
public byte[] constructWriteMemoryRequest(Module module, byte[] address, byte[] values) {
checkNotNull(module, "module");
checkNotNullOrEmpty(address, "address");
checkNotNullOrEmpty(values, "values");
SSMProtocol.module = module;
// 0x80 0x10 0xF0 data_length 0xB0 from_address value1 value2 ... valueN checksum
return buildRequest(WRITE_MEMORY_COMMAND, false, address, values);
}
public byte[] constructWriteAddressRequest(
Module module, byte[] address, byte value) {
checkNotNull(module, "module");
checkNotNullOrEmpty(address, "address");
checkNotNull(value, "value");
SSMProtocol.module = module;
// 0x80 0x10 0xF0 data_length 0xB8 from_address value checksum
return buildRequest(WRITE_ADDRESS_COMMAND, false, address, new byte[]{value});
}
public byte[] constructReadMemoryRequest(Module module, byte[] address, int numBytes) {
checkNotNull(module, "module");
checkNotNullOrEmpty(address, "address");
checkGreaterThanZero(numBytes, "numBytes");
SSMProtocol.module = module;
// 0x80 0x10 0xF0 data_length 0xA0 padding from_address num_bytes-1 checksum
return buildRequest(READ_MEMORY_COMMAND, true, address, new byte[]{asByte(numBytes - 1)});
}
public byte[] constructReadAddressRequest(Module module, byte[][] addresses) {
checkNotNull(module, "module");
checkNotNullOrEmpty(addresses, "addresses");
SSMProtocol.module = module;
// 0x80 0x10 0xF0 data_length 0xA8 padding address1 address2 ... addressN checksum
return buildRequest(READ_ADDRESS_COMMAND, true, addresses);
}
public byte[] preprocessResponse(byte[] request, byte[] response, PollingState pollState) {
return SSMResponseProcessor.filterRequestFromResponse(request, response, pollState);
}
public byte[] parseResponseData(byte[] processedResponse) {
checkNotNullOrEmpty(processedResponse, "processedResponse");
return SSMResponseProcessor.extractResponseData(processedResponse);
}
public void checkValidEcuInitResponse(byte[] processedResponse) {
// response_header 3_unknown_bytes 5_ecu_id_bytes readable_params_switches... checksum
// 80F01039FF A21011315258400673FACB842B83FEA800000060CED4FDB060000F200000000000DC0000551E30C0F222000040FB00E10000000000000000 59
checkNotNullOrEmpty(processedResponse, "processedResponse");
SSMResponseProcessor.validateResponse(processedResponse);
byte responseType = processedResponse[4];
if (responseType != ECU_INIT_RESPONSE) {
throw new InvalidResponseException("Unexpected " + module.getName() +
" Init response type: " + asHex(new byte[]{responseType}));
}
}
public EcuInit parseEcuInitResponse(byte[] processedResponse) {
return new SSMEcuInit(parseResponseData(processedResponse));
}
public final byte[] constructEcuResetRequest(Module module, int resetCode) {
// 80 10 F0 05 B8 00 00 60 40 DD
final byte[] resetAddress = new byte[]{
(byte) 0x00, (byte) 0x00, (byte) 0x60};
return constructWriteAddressRequest(module, resetAddress, (byte) resetCode);
}
public void checkValidEcuResetResponse(byte[] processedResponse) {
// 80 F0 10 02 F8 40 BA
checkNotNullOrEmpty(processedResponse, "processedResponse");
SSMResponseProcessor.validateResponse(processedResponse);
byte responseType = processedResponse[4];
if (responseType != WRITE_ADDRESS_RESPONSE || processedResponse[5] != (byte) 0x40) {
throw new InvalidResponseException("Unexpected " + module.getName() +
" Reset response: " + asHex(processedResponse));
}
}
public void checkValidWriteResponse(byte[] data, byte[] processedResponse) {
checkNotNullOrEmpty(data, "data");
checkNotNullOrEmpty(processedResponse, "processedResponse");
// 80 F0 10 02 F8 data checksum
byte responseType = processedResponse[4];
if (responseType != WRITE_ADDRESS_RESPONSE ||
processedResponse[5] != (byte) data[0]) {
throw new InvalidResponseException(
"Unexpected " + module.getName() + " Write response: " +
asHex(processedResponse));
}
}
public ConnectionProperties getDefaultConnectionProperties() {
return new ConnectionProperties() {
public int getBaudRate() {
return 4800;
}
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 55;
}
};
}
private final byte[] buildRequest(byte command, boolean padContent,
byte[]... content) {
int length = 0;
for (byte[] tmp : content) {
length += tmp.length;
}
byte[] request = new byte[0];
try {
bb.reset();
bb.write(HEADER);
bb.write(module.getAddress());
bb.write(module.getTester());
bb.write(Integer.valueOf(length + (padContent ? 2 : 1)).byteValue());
bb.write(command);
if (padContent) {
bb.write((pollState.isFastPoll() ?
READ_ADDRESS_CONTINUOUS : READ_ADDRESS_ONCE));
}
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;
}
}