166 lines
5.3 KiB
Dart
166 lines
5.3 KiB
Dart
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();
|
||
|
||
// AIN0–AIN7: 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);
|
||
}
|