import 'dart:typed_data'; import 'sensor_state.dart'; import 'temp_table.dart'; import 'neg8.dart'; /// Parses a 128-byte KPro ECU response frame into a [SensorState]. /// Throws [ArgumentError] if frame length is wrong or NEG8 checksum fails. SensorState parseKPro(Uint8List frame) { if (frame.length != 128) { throw ArgumentError('KPro frame must be 128 bytes, got ${frame.length}'); } if (!validateFrame(frame)) { throw ArgumentError('KPro frame NEG8 checksum failed'); } // RPM: bytes 2..3 — raw / 4 final int rpmRaw = _readUint16BE(frame, 2); final double rpm = rpmRaw / 4.0; // VSS: byte 4 direct km/h final double vss = frame[4].toDouble(); // TPS: byte 5 direct % final double tps = frame[5].toDouble(); // MAP: bytes 6 (MAP1) and 33..34 (MAP2 L+H) final int map1Raw = frame[6]; final int map2Raw = _readUint16BE(frame, 33); final double map = (map2Raw != 0) ? map2Raw / 10.0 : map1Raw.toDouble(); // INJ: byte 11 raw final double inj = frame[11].toDouble(); // IGN: byte 13 raw degrees final double ign = frame[13].toDouble(); // O2: byte 14 raw final double o2 = frame[14].toDouble(); // KRtrd: byte 21 final int krtrd = frame[21]; // SW bitmaps final int sw1 = frame[31]; final int sw2 = frame[32]; // ECT: byte 49 final double ect = (tempXlt[frame[49]] + 40).toDouble(); // IAT: byte 50 final double iat = (tempXlt[frame[50]] + 40).toDouble(); // PA: byte 51 final double pa = frame[51].toDouble(); // BAT: byte 52 — raw / 10 final double bat = frame[52] / 10.0; // STRIM: byte 32 (SW2 shares offset 32, STRIM is byte 20 per spec offset +20) final double strim = frame[20].toDouble(); // LTRIM — not explicitly listed in KPro; use 0 const double ltrim = 0.0; // ETH — not in KPro spec; use 0 const double eth = 0.0; // AFR: byte 25 (AFCMD at offset +19, then raw) final double afr = frame[25].toDouble(); // ERR bytes: 18 bytes starting at offset 0x3E = 62 final List errBytes = List.generate(18, (i) => frame[62 + i]); // AIN0–AIN7: bytes 0x52..0x61 (offset 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(); // Gear — not in KPro frame, default 0 const int gear = 0; // Flags // SW2 bit2 = MIL final bool mil = (sw2 & 0x04) != 0; // SW1 bit6 = FLR (fuel cut relay) final bool fuelCut = (sw1 & 0x40) != 0; // SW1 bit0 = FANC final bool fanOut = (sw1 & 0x01) != 0; // SW2 bit1..0 = VTS (VTEC) final bool vtec = (sw2 & 0x03) != 0; // Knock = KRtrd > 0 final bool knock = krtrd > 0; // Rev limit — not directly mapped in KPro SW; false const bool revLimit = false; // Launch — not in KPro SW; false const bool launch = false; 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]; }