/*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2012 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.external.innovate.generic.serial.io;
import com.romraider.io.connection.ConnectionProperties;
import com.romraider.io.serial.connection.SerialConnection;
import com.romraider.io.serial.connection.SerialConnectionImpl;
import com.romraider.logger.external.core.DataListener;
import com.romraider.logger.external.core.Stoppable;
import static com.romraider.util.ByteUtil.matchOnes;
import static com.romraider.util.ByteUtil.matchZeroes;
import static com.romraider.util.HexUtil.asHex;
import static com.romraider.util.ParamChecker.checkNotNullOrEmpty;
import static java.lang.System.arraycopy;
import org.apache.log4j.Logger;
import static org.apache.log4j.Logger.getLogger;
public final class InnovateRunner implements Stoppable {
private static final Logger LOGGER = getLogger(InnovateRunner.class);
private static final double MAX_AFR = 20.33;
private final SerialConnection connection;
private final DataListener listener;
private boolean stop;
public InnovateRunner(String port, DataListener listener) {
checkNotNullOrEmpty(port, "port");
this.connection = serialConnection(port);
// LC-1 & LM-2
// this.connection = new TestInnovateConnection("13036B00000000000000000000B2874313036B00000000000000000000B28743");
// LM-1
// this.connection = new TestInnovateConnection("8113037C1E66012600720049003B003B");
// this.connection = new TestInnovateConnection("B28242310024B28242310000"); // bad data?
this.listener = listener;
}
public void run() {
try {
while (!stop) {
byte b0 = nextByte();
if (isHeaderHighByte(b0)) {
byte b1 = nextByte();
if (isHeaderLowByte(b1)) {
int numWords = numWords(b0, b1);
byte[] bytes = new byte[numWords * 2];
connection.read(bytes);
LOGGER.trace("Innovate response: " + packet(b0, b1, bytes));
process(bytes);
} else {
LOGGER.trace("Innovate discarded: " + asHex(b1));
}
} else if (isLm1HighByte(b0)) {
byte b1 = nextByte();
if (isLm1LowByte(b1)) {
byte[] rest = new byte[14];
connection.read(rest);
byte[] bytes = new byte[16];
bytes[0] = b0;
bytes[1] = b1;
arraycopy(rest, 0, bytes, 2, rest.length);
LOGGER.trace("Innovate response: " + asHex(bytes));
process(bytes);
} else {
LOGGER.trace("Innovate discarded: " + asHex(b1));
}
} else {
LOGGER.trace("Innovate discarded: " + asHex(b0));
}
}
connection.close();
} catch (Throwable t) {
LOGGER.error("Error occurred", t);
} finally {
connection.close();
}
}
public void stop() {
stop = true;
}
private void process(byte[] bytes) {
if (isError(bytes)) {
double error = -1d * getLambda(bytes);
LOGGER.error("Innovate error: " + error);
listener.setData(error);
} else if (isOk(bytes)) {
double afr = getAfr(bytes);
LOGGER.trace("Innovate AFR: " + afr);
listener.setData(afr > MAX_AFR ? MAX_AFR : afr);
}
}
private SerialConnectionImpl serialConnection(String port) {
ConnectionProperties properties = new InnovateConnectionProperties();
return new SerialConnectionImpl(port, properties);
}
private byte nextByte() {
return (byte) connection.read();
}
// 1x11xx1x
private boolean isHeaderHighByte(byte b) {
return matchOnes(b, 178);
}
// 1xxxxxxx
private boolean isHeaderLowByte(byte b) {
return matchOnes(b, 128);
}
// 1x0xxx0x
private boolean isLm1HighByte(byte b) {
return matchOnes(b, 128) && matchZeroes(b, 34);
}
// 0xxxxxxx
private boolean isLm1LowByte(byte b) {
return matchZeroes(b, 128);
}
private double getAfr(byte[] bytes) {
return (getLambda(bytes) + 500) * getAf(bytes) / 10000.0;
}
private int getAf(byte[] bytes) {
return ((bytes[0] & 1) << 7) | bytes[1];
}
// xxx000xx
private boolean isOk(byte[] bytes) {
return matchZeroes(bytes[0], 28);
}
// xxx110xx
private boolean isError(byte[] bytes) {
return matchOnes(bytes[0], 24) && matchZeroes(bytes[0], 4);
}
// 01xxxxxx 0xxxxxxx
private int getLambda(byte[] bytes) {
return ((bytes[2] & 63) << 7) | bytes[3];
}
private int numWords(byte b0, byte b1) {
int result = 0;
if (matchOnes(b0, 1)) result |= 128;
if (matchOnes(b1, 64)) result |= 64;
if (matchOnes(b1, 32)) result |= 32;
if (matchOnes(b1, 16)) result |= 16;
if (matchOnes(b1, 8)) result |= 8;
if (matchOnes(b1, 4)) result |= 4;
if (matchOnes(b1, 2)) result |= 2;
if (matchOnes(b1, 1)) result |= 1;
return result;
}
private String packet(byte b0, byte b1, byte[] bytes) {
byte[] result = new byte[bytes.length + 2];
result[0] = b0;
result[1] = b1;
arraycopy(bytes, 0, result, 2, bytes.length);
return asHex(result);
}
}