Files

81 lines
3.0 KiB
Java

package com.chaegeon.niro;
/** Receive-only V038 Can2A20HEV ALL_C / EMS1 decoder. No Android or transport I/O. */
final class HscanRpmDecoder {
interface Listener { void onRpm(int rpm); }
// Observed hscand envelope: FF 0B 8E 01 8B C7 01 00, 255 data bytes + XOR.
private static final int FRAME_SIZE = 264;
private static final int[] HEADER = {255, 11, 142, 1, 139, 199, 1, 0};
private final byte[] frame = new byte[FRAME_SIZE];
private final Listener listener;
private int used;
long validFrames;
long rejectedFrames;
long samples;
HscanRpmDecoder(Listener listener) { this.listener = listener; }
void accept(byte[] bytes, int offset, int length) {
if (offset < 0 || length < 0 || offset > bytes.length - length)
throw new IndexOutOfBoundsException();
for (int i = offset; i < offset + length; i++) {
frame[used++] = bytes[i];
// Find the exact bounded header again after garbage or corrupt framing.
while (used > 0 && !headerMatches()) discardFirst();
if (used == FRAME_SIZE) {
if (validate()) {
validFrames++;
emit();
used = 0;
} else {
rejectedFrames++;
discardFirst();
while (used > 0 && !headerMatches()) discardFirst();
}
}
}
}
private boolean headerMatches() {
for (int i = 0; i < Math.min(used, HEADER.length); i++)
if ((frame[i] & 255) != HEADER[i]) return false;
return true;
}
private void discardFirst() {
System.arraycopy(frame, 1, frame, 0, --used);
}
private boolean validate() {
int xor = 0;
for (byte value : frame) xor ^= value & 255;
if (xor != 0 || (frame[8] & 255) != 0xc7) return false;
int count = (frame[9] & 255) | ((frame[10] & 255) << 8);
int end = FRAME_SIZE - 1, p = 11;
if (count > (end - p) / 3) return false;
for (int i = 0; i < count; i++) {
if (p + 2 > end) return false;
int fn = frame[p++] & 255, size = frame[p++] & 255;
if (size < 1 || size > 8 || p + size > end) return false;
// A different profile must never reinterpret a short record as EMS1.
if (fn == 0x16 && size != 8) return false;
p += size;
}
while (p < end) if (frame[p++] != 0) return false;
return true;
}
private void emit() {
int count = (frame[9] & 255) | ((frame[10] & 255) << 8), p = 11;
for (int i = 0; i < count; i++) {
int fn = frame[p++] & 255, size = frame[p++] & 255;
if (fn == 0x16) {
// CAN 0x316 EMS1: same LE16 * 0.25 and int truncation as stock HEV.
int raw = (frame[p + 2] & 255) | ((frame[p + 3] & 255) << 8);
if (raw < 0xfffe) { samples++; listener.onRpm(raw / 4); }
}
p += size;
}
}
}