hondavert-dev/lib/core/protocol/s300_parser.dart
2026-08-08 13:18:02 -04:00

166 lines
5.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:typed_data';
import 'sensor_state.dart';
import 'temp_table.dart';
import 'neg8.dart';
/// Parses a 128-byte S300 ECU response frame into a [SensorState].
/// Throws [ArgumentError] if frame length is wrong, or if [validateChecksum]
/// is true and the NEG8 checksum fails.
SensorState parseS300(Uint8List frame, {bool validateChecksum = true}) {
if (frame.length != 128) {
throw ArgumentError('S300 frame must be 128 bytes, got ${frame.length}');
}
if (validateChecksum && !validateFrame(frame)) {
throw ArgumentError('S300 frame NEG8 checksum failed');
}
// RPM: bytes +3..4, little-endian, use directly. Confirmed against a live
// capture: LE @ offset 3 matched a reference tool's RPM exactly, while
// REQUIREMENTS.md's blanket "all multi-byte values are Big Endian" claim
// did not hold for this field on real hardware.
final double rpm = _readUint16LE(frame, 3).toDouble();
// VSS: bytes +5..6, little-endian (confirmed against a live capture).
final int vssRaw = _readUint16LE(frame, 5);
final double vss =
(vssRaw < 893 || vssRaw == 0xFFFF) ? 0.0 : 228480.0 / vssRaw;
// MAP: bytes +7..8, little-endian (confirmed against a live capture — the
// big-endian read produced multi-thousand-kPa values, physically
// impossible for a 0-300 kPa sensor).
final int mapRaw = _readUint16LE(frame, 7);
final double map = mapRaw / 10.0;
// TPS: byte +9. Linear 0-255 -> 0-100%, confirmed against a live capture
// (raw 127 matched a reference tool's ~50% reading; the spec's
// "raw*51/46" formula produced impossible >100% values on the same byte).
final int tpsRaw = frame[9];
final double tps = (tpsRaw * 100.0 / 255.0).clamp(0.0, 100.0);
// INJ: bytes +10..11, BE, raw ms.
final double inj = _readUint16BE(frame, 10).toDouble();
// IGN: byte +12.
final double ign = (frame[12] + 120) / 2.0;
// O2: byte +16, raw.
final double o2 = frame[16].toDouble();
// SW bitmaps
final int sw1 = frame[0x11]; // +17
final int sw2 = frame[0x12]; // +18
final int sw3 = frame[0x13]; // +19
final int sw5 = frame[0x43]; // +43
// Gear: decimal offset 27 (confirmed against a live capture — hex 0x27/39
// read 0x00 there; decimal 27 matched a reference tool's gear exactly).
final int gear = frame[27];
// Strim: +29 hex = 41 decimal
final double strim = frame[0x29].toDouble();
// Ltrim: +2B hex = 43 decimal
final double ltrim = frame[0x2B].toDouble();
// PA: +2C hex = 44 decimal
final double pa = frame[0x2C].toDouble();
// ECT: +2D hex = 45 decimal. Table is tempXlt[raw] - 40, not +40: the
// spec's own sensor range (section 3: ECT/IAT -40..150 degC) is only
// reachable with subtraction, since tempXlt entries span 0..190. The
// written "+40" formula can only ever produce 40..230 degC, which
// contradicts that declared range and explains the ~80 degC-too-hot
// readings seen against real hardware.
final double ect = (tempXlt[frame[0x2D]] - 40).toDouble();
// IAT: +2E hex = 46 decimal
final double iat = (tempXlt[frame[0x2E]] - 40).toDouble();
// BAT: +30 hex = 48 decimal
final double bat = frame[0x30] * 26.0 / 270.0;
// ERR bytes: +31..+34 hex = 49..52 decimal
final List<int> errBytes = [
frame[0x31],
frame[0x32],
frame[0x33],
frame[0x34],
];
// Eth: +38 hex = 56 decimal
final double eth = frame[0x38].toDouble();
// AFR: spec overlaps +34 with ERR03; reuse that byte per the spec's own table.
final double afr = frame[0x34].toDouble();
// AIN0AIN7: decimal offsets 82..97 (uint16 BE each)
final double ain0 = _readUint16BE(frame, 82).toDouble();
final double ain1 = _readUint16BE(frame, 84).toDouble();
final double ain2 = _readUint16BE(frame, 86).toDouble();
final double ain3 = _readUint16BE(frame, 88).toDouble();
final double ain4 = _readUint16BE(frame, 90).toDouble();
final double ain5 = _readUint16BE(frame, 92).toDouble();
final double ain6 = _readUint16BE(frame, 94).toDouble();
final double ain7 = _readUint16BE(frame, 96).toDouble();
// Flags
// SW1 bit3 = REVL
final bool revLimit = (sw1 & 0x08) != 0;
// SW2 bit5 = MIL
final bool mil = (sw2 & 0x20) != 0;
// SW2 bit2 = Fuel (cut)
final bool fuelCut = (sw2 & 0x04) != 0;
// SW2 bit7 = ALTC → fanOut; also SW5 bit0 = FANC
final bool fanOut = (sw2 & 0x80) != 0 || (sw5 & 0x01) != 0;
// SW2 bit1..0 = VTS (VTEC solenoid — nonzero = active)
final bool vtec = (sw2 & 0x03) != 0;
// Knock = KRtrd > 0 (byte 13)
final bool knock = frame[13] > 0;
// SW3 bit7 = LnchC
final bool launch = (sw3 & 0x80) != 0;
return SensorState(
rpm: rpm,
vss: vss,
map: map,
tps: tps,
inj: inj,
ign: ign,
ect: ect,
iat: iat,
bat: bat,
o2: o2,
gear: gear,
eth: eth,
pa: pa,
afr: afr,
strim: strim,
ltrim: ltrim,
ain0: ain0,
ain1: ain1,
ain2: ain2,
ain3: ain3,
ain4: ain4,
ain5: ain5,
ain6: ain6,
ain7: ain7,
mil: mil,
fuelCut: fuelCut,
fanOut: fanOut,
vtec: vtec,
knock: knock,
revLimit: revLimit,
launch: launch,
errBytes: errBytes,
timestamp: DateTime.now(),
);
}
int _readUint16BE(Uint8List frame, int offset) {
return (frame[offset] << 8) | frame[offset + 1];
}
int _readUint16LE(Uint8List frame, int offset) {
return frame[offset] | (frame[offset + 1] << 8);
}