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 NEG8 checksum fails. SensorState parseS300(Uint8List frame) { if (frame.length != 128) { throw ArgumentError('S300 frame must be 128 bytes, got ${frame.length}'); } if (!validateFrame(frame)) { throw ArgumentError('S300 frame NEG8 checksum failed'); } // RPM: bytes 3..4 uint16 BE, used directly final int rpmRaw = _readUint16BE(frame, 3); final double rpm = rpmRaw.toDouble(); // VSS: bytes 5..6 final int vssRaw = _readUint16BE(frame, 5); final double vss = (vssRaw < 893 || vssRaw == 0xFFFF) ? 0.0 : 228480.0 / vssRaw; // MAP: bytes 7..8 — raw / 10 = kPa final int mapRaw = _readUint16BE(frame, 7); final double map = mapRaw / 10.0; // TPS: byte 9 final int tpsRaw = frame[9]; final double tps = (tpsRaw < 25) ? 0.0 : (tpsRaw * 51.0) / 46.0; // INJ: bytes 10..11 raw ms final int injRaw = _readUint16BE(frame, 10); final double inj = injRaw.toDouble(); // IGN: byte 12 — (raw + 120) / 2 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]; // +67 // Gear: byte 0x27 = +39 decimal... wait, +27 hex = decimal 39 final int gear = frame[0x27]; // Strim: byte 0x29 hex = 41 decimal final double strim = frame[0x29].toDouble(); // Ltrim: byte 0x2B hex = 43 decimal final double ltrim = frame[0x2B].toDouble(); // PA: byte 0x2C hex = 44 decimal final double pa = frame[0x2C].toDouble(); // ECT: byte 0x2D hex = 45 decimal final double ect = (tempXlt[frame[0x2D]] + 40).toDouble(); // IAT: byte 0x2E hex = 46 decimal final double iat = (tempXlt[frame[0x2E]] + 40).toDouble(); // BAT: byte 0x30 hex = 48 decimal final double bat = frame[0x30] * 26.0 / 270.0; // ERR bytes: 0x31..0x34 (4 bytes for S300) final List errBytes = [ frame[0x31], frame[0x32], frame[0x33], frame[0x34], ]; // Eth: byte 0x38 hex = 56 decimal final double eth = frame[0x38].toDouble(); // AFR: byte 0x34 overlaps with ERR03 in the spec — use it as raw AFR 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]; }