From 1424cafbb4f541b203e0c32cc701a84582575aca Mon Sep 17 00:00:00 2001 From: HVBT Dev Date: Sun, 26 Jul 2026 23:51:56 +0530 Subject: [PATCH] Fix S300 live data decoding --- lib/core/models/sensor_defs.dart | 86 ++++++++++++------- lib/core/protocol/s300_parser.dart | 49 ++++++----- .../screens/debug/raw_frame_debug_screen.dart | 32 ++++--- test/s300_parser_test.dart | 53 ++++++------ 4 files changed, 132 insertions(+), 88 deletions(-) diff --git a/lib/core/models/sensor_defs.dart b/lib/core/models/sensor_defs.dart index 61ad131..d18128f 100644 --- a/lib/core/models/sensor_defs.dart +++ b/lib/core/models/sensor_defs.dart @@ -3,39 +3,65 @@ import 'flag_def.dart'; /// Master list of all analog sensor definitions. const List sensorDefs = [ - SensorDef(id: 'rpm', displayName: 'RPM', unit: 'revs', min: 0, max: 9000), - SensorDef(id: 'vss', displayName: 'Speed', unit: 'km/h', min: 0, max: 280), - SensorDef(id: 'map', displayName: 'Manifold pressure', unit: 'kPa', min: 0, max: 300), - SensorDef(id: 'tps', displayName: 'Throttle pedal', unit: '%', min: 0, max: 100), - SensorDef(id: 'inj', displayName: 'Injector duration', unit: 'ms', min: 0, max: 20), - SensorDef(id: 'ign', displayName: 'Timing advance', unit: 'deg', min: -20, max: 60), - SensorDef(id: 'ect', displayName: 'Coolant temp', unit: '°C', min: -40, max: 150), - SensorDef(id: 'iat', displayName: 'Intake air temp', unit: '°C', min: -40, max: 150), - SensorDef(id: 'bat', displayName: 'Battery voltage', unit: 'V', min: 0, max: 20), - SensorDef(id: 'o2', displayName: 'O2 sensor', unit: 'V', min: 0, max: 1.5), - SensorDef(id: 'gear', displayName: 'Gear', unit: '—', min: 1, max: 6), - SensorDef(id: 'eth', displayName: 'Ethanol', unit: '%', min: 0, max: 100), - SensorDef(id: 'pa', displayName: 'Baro pressure', unit: 'kPa', min: 0, max: 120), - SensorDef(id: 'afr', displayName: 'AFR', unit: 'λ', min: 0.5, max: 2.0), - SensorDef(id: 'strim', displayName: 'Short trim', unit: '%', min: -30, max: 30), - SensorDef(id: 'ltrim', displayName: 'Long trim', unit: '%', min: -30, max: 30), - SensorDef(id: 'ain0', displayName: 'Analog input 0', unit: 'V', min: 0, max: 5), - SensorDef(id: 'ain1', displayName: 'Analog input 1', unit: 'V', min: 0, max: 5), - SensorDef(id: 'ain2', displayName: 'Analog input 2', unit: 'V', min: 0, max: 5), - SensorDef(id: 'ain3', displayName: 'Analog input 3', unit: 'V', min: 0, max: 5), - SensorDef(id: 'ain4', displayName: 'Analog input 4', unit: 'V', min: 0, max: 5), - SensorDef(id: 'ain5', displayName: 'Analog input 5', unit: 'V', min: 0, max: 5), - SensorDef(id: 'ain6', displayName: 'Analog input 6', unit: 'V', min: 0, max: 5), - SensorDef(id: 'ain7', displayName: 'Analog input 7', unit: 'V', min: 0, max: 5), + SensorDef(id: 'rpm', displayName: 'RPM', unit: 'revs', min: 0, max: 9000), + SensorDef(id: 'vss', displayName: 'Speed', unit: 'mph', min: 0, max: 180), + SensorDef( + id: 'map', + displayName: 'Manifold pressure', + unit: 'psi', + min: -30, + max: 30), + SensorDef( + id: 'tps', displayName: 'Throttle pedal', unit: '%', min: 0, max: 100), + SensorDef( + id: 'inj', displayName: 'Injector duration', unit: 'ms', min: 0, max: 20), + SensorDef( + id: 'ign', displayName: 'Timing advance', unit: 'deg', min: -20, max: 60), + SensorDef( + id: 'ect', displayName: 'Coolant temp', unit: '°C', min: -40, max: 150), + SensorDef( + id: 'iat', + displayName: 'Intake air temp', + unit: '°C', + min: -40, + max: 150), + SensorDef( + id: 'bat', displayName: 'Battery voltage', unit: 'V', min: 0, max: 20), + SensorDef(id: 'o2', displayName: 'O2 sensor', unit: 'V', min: 0, max: 1.5), + SensorDef(id: 'gear', displayName: 'Gear', unit: '—', min: 1, max: 6), + SensorDef(id: 'eth', displayName: 'Ethanol', unit: '%', min: 0, max: 100), + SensorDef( + id: 'pa', displayName: 'Baro pressure', unit: 'kPa', min: 0, max: 120), + SensorDef(id: 'afr', displayName: 'AFR', unit: 'λ', min: 0.5, max: 2.0), + SensorDef( + id: 'strim', displayName: 'Short trim', unit: '%', min: -30, max: 30), + SensorDef( + id: 'ltrim', displayName: 'Long trim', unit: '%', min: -30, max: 30), + SensorDef( + id: 'ain0', displayName: 'Analog input 0', unit: 'V', min: 0, max: 5), + SensorDef( + id: 'ain1', displayName: 'Analog input 1', unit: 'V', min: 0, max: 5), + SensorDef( + id: 'ain2', displayName: 'Analog input 2', unit: 'V', min: 0, max: 5), + SensorDef( + id: 'ain3', displayName: 'Analog input 3', unit: 'V', min: 0, max: 5), + SensorDef( + id: 'ain4', displayName: 'Analog input 4', unit: 'V', min: 0, max: 5), + SensorDef( + id: 'ain5', displayName: 'Analog input 5', unit: 'V', min: 0, max: 5), + SensorDef( + id: 'ain6', displayName: 'Analog input 6', unit: 'V', min: 0, max: 5), + SensorDef( + id: 'ain7', displayName: 'Analog input 7', unit: 'V', min: 0, max: 5), ]; /// Master list of boolean flag sensors. const List flagDefs = [ - FlagDef(id: 'mil', displayName: 'MIL'), - FlagDef(id: 'fuelcut', displayName: 'Fuel Cut'), - FlagDef(id: 'fanout', displayName: 'FAN Out'), - FlagDef(id: 'vtec', displayName: 'VTEC'), - FlagDef(id: 'knock', displayName: 'Knock'), + FlagDef(id: 'mil', displayName: 'MIL'), + FlagDef(id: 'fuelcut', displayName: 'Fuel Cut'), + FlagDef(id: 'fanout', displayName: 'FAN Out'), + FlagDef(id: 'vtec', displayName: 'VTEC'), + FlagDef(id: 'knock', displayName: 'Knock'), FlagDef(id: 'revlimit', displayName: 'Rev Limit'), - FlagDef(id: 'launch', displayName: 'Launch'), + FlagDef(id: 'launch', displayName: 'Launch'), ]; diff --git a/lib/core/protocol/s300_parser.dart b/lib/core/protocol/s300_parser.dart index 769de99..c911471 100644 --- a/lib/core/protocol/s300_parser.dart +++ b/lib/core/protocol/s300_parser.dart @@ -13,32 +13,35 @@ SensorState parseS300(Uint8List 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(); + // RPM: bytes 2..3 uint16 BE, used directly. + // The S300 live frame starts its sensor payload at byte 2; byte 0..1 are + // the protocol header. Reading from byte 3 shifts all high-rate values. + final int rpmRaw = _readUint16BE(frame, 2); + final double rpm = (rpmRaw - 256).clamp(0, 12000).toDouble(); - // VSS: bytes 5..6 - final int vssRaw = _readUint16BE(frame, 5); + // VSS: bytes 5..6 little-endian pulse period. + final int vssRaw = _readUint16LE(frame, 5); final double vss = - (vssRaw < 893 || vssRaw == 0xFFFF) ? 0.0 : 228480.0 / vssRaw; + (vssRaw < 893 || vssRaw == 0xFFFF) ? 0.0 : 144256.0 / vssRaw; - // MAP: bytes 7..8 — raw / 10 = kPa - final int mapRaw = _readUint16BE(frame, 7); - final double map = mapRaw / 10.0; + // MAP: bytes 7..8 little-endian. SManager displays this as gauge pressure + // in its "/psi column rather than raw absolute kPa. + final int mapRaw = _readUint16LE(frame, 7); + final double map = ((mapRaw / 10.0) - 103.8) * 0.1450377377; // TPS: byte 9 final int tpsRaw = frame[9]; - final double tps = (tpsRaw < 25) ? 0.0 : (tpsRaw * 51.0) / 46.0; + final double tps = ((tpsRaw - 25) * 100.0 / (239 - 25)).clamp(0.0, 100.0); - // INJ: bytes 10..11 raw ms - final int injRaw = _readUint16BE(frame, 10); - final double inj = injRaw.toDouble(); + // INJ: bytes 10..11 little-endian timer ticks. + final int injRaw = _readUint16LE(frame, 10); + final double inj = injRaw / 240.5; - // IGN: byte 12 — (raw + 120) / 2 - final double ign = (frame[12] + 120) / 2.0; + // IGN: byte 12 — SManager-aligned degrees. + final double ign = (frame[12] - 66) / 2.0; - // O2: byte 16 raw - final double o2 = frame[16].toDouble(); + // O2: byte 16, 0..5V scaled over 8-bit ADC range. + final double o2 = frame[16] * 5.0 / 256.0; // SW bitmaps final int sw1 = frame[0x11]; // +17 @@ -55,8 +58,8 @@ SensorState parseS300(Uint8List frame) { // Ltrim: byte 0x2B hex = 43 decimal final double ltrim = frame[0x2B].toDouble(); - // PA: byte 0x2C hex = 44 decimal - final double pa = frame[0x2C].toDouble(); + // PA: byte 0x3C hex = 60 decimal + final double pa = frame[0x3C].toDouble(); // ECT: byte 0x2D hex = 45 decimal final double ect = (tempXlt[frame[0x2D]] + 40).toDouble(); @@ -64,8 +67,8 @@ SensorState parseS300(Uint8List frame) { // 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; + // BAT: byte 0x24 hex = 36 decimal + final double bat = frame[0x24] * 26.0 / 270.0; // ERR bytes: 0x31..0x34 (4 bytes for S300) final List errBytes = [ @@ -147,3 +150,7 @@ SensorState parseS300(Uint8List frame) { 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); +} diff --git a/lib/ui/screens/debug/raw_frame_debug_screen.dart b/lib/ui/screens/debug/raw_frame_debug_screen.dart index d429e69..5ba4ba1 100644 --- a/lib/ui/screens/debug/raw_frame_debug_screen.dart +++ b/lib/ui/screens/debug/raw_frame_debug_screen.dart @@ -87,8 +87,8 @@ class RawFrameDebugScreen extends ConsumerWidget { List<_RowData> _decodedRows(SensorState s) => [ _RowData('RPM', s.rpm.toStringAsFixed(0)), - _RowData('Speed', s.vss.toStringAsFixed(2)), - _RowData('MAP', s.map.toStringAsFixed(2)), + _RowData('Speed mph', s.vss.toStringAsFixed(2)), + _RowData('MAP psi', s.map.toStringAsFixed(2)), _RowData('TPS', s.tps.toStringAsFixed(2)), _RowData('ECT', s.ect.toStringAsFixed(2)), _RowData('IAT', s.iat.toStringAsFixed(2)), @@ -98,8 +98,8 @@ class RawFrameDebugScreen extends ConsumerWidget { ]; List<_RowData> _s300CandidateRows(Uint8List f) { - final rpmBe = _u16be(f, 3); - final rpmLe = _u16le(f, 3); + final rpmBe = _u16be(f, 2); + final rpmShiftedBe = _u16be(f, 3); final vssBe = _u16be(f, 5); final vssLe = _u16le(f, 5); final mapBe = _u16be(f, 7); @@ -107,18 +107,24 @@ class RawFrameDebugScreen extends ConsumerWidget { return [ _RowData('Header bytes', '${_b(f, 0)} ${_b(f, 1)} ${_b(f, 2)}'), - _RowData('RPM @3 BE / LE', '$rpmBe / $rpmLe'), + _RowData('RPM @2 BE', rpmBe.toString()), + _RowData('RPM shifted @3 BE', rpmShiftedBe.toString()), _RowData('VSS raw @5 BE / LE', '$vssBe / $vssLe'), - _RowData('VSS current BE km/h', _s300Vss(vssBe).toStringAsFixed(2)), - _RowData('VSS alternate LE km/h', _s300Vss(vssLe).toStringAsFixed(2)), + _RowData('VSS current LE mph', _s300Vss(vssLe).toStringAsFixed(2)), _RowData('MAP raw @7 BE / LE', '$mapBe / $mapLe'), - _RowData('MAP /10 BE / LE', - '${(mapBe / 10).toStringAsFixed(2)} / ${(mapLe / 10).toStringAsFixed(2)}'), + _RowData('MAP current psi', _s300Map(mapLe).toStringAsFixed(2)), _RowData('TPS raw @9', f[9].toString()), _RowData('TPS current', _s300Tps(f[9]).toStringAsFixed(2)), + _RowData('INJ raw @10 LE', _u16le(f, 10).toString()), + _RowData('INJ current ms', (_u16le(f, 10) / 240.5).toStringAsFixed(2)), + _RowData('IGN raw @12', f[12].toString()), + _RowData('IGN current deg', ((f[12] - 66) / 2.0).toStringAsFixed(2)), + _RowData('O2 current V', (f[16] * 5.0 / 256.0).toStringAsFixed(2)), _RowData('ECT raw @45', f[0x2D].toString()), _RowData('IAT raw @46', f[0x2E].toString()), - _RowData('BAT raw @48', f[0x30].toString()), + _RowData('BAT raw @36', f[0x24].toString()), + _RowData('BAT current V', (f[0x24] * 26.0 / 270.0).toStringAsFixed(2)), + _RowData('PA raw @60', f[0x3C].toString()), _RowData('ERR bytes @49-52', '${_b(f, 0x31)} ${_b(f, 0x32)} ${_b(f, 0x33)} ${_b(f, 0x34)}'), ]; @@ -130,7 +136,11 @@ class RawFrameDebugScreen extends ConsumerWidget { f[o].toRadixString(16).padLeft(2, '0'); static double _s300Vss(int raw) { - return (raw < 893 || raw == 0xFFFF) ? 0.0 : 228480.0 / raw; + return (raw < 893 || raw == 0xFFFF) ? 0.0 : 144256.0 / raw; + } + + static double _s300Map(int raw) { + return ((raw / 10.0) - 103.8) * 0.1450377377; } static double _s300Tps(int raw) { diff --git a/test/s300_parser_test.dart b/test/s300_parser_test.dart index a81ba40..02d5a63 100644 --- a/test/s300_parser_test.dart +++ b/test/s300_parser_test.dart @@ -22,20 +22,21 @@ Uint8List _buildS300Frame({ // Header frame[0] = 0x1B; frame[1] = 0x00; - // RPM: bytes 3..4 - frame[3] = (rpm >> 8) & 0xFF; - frame[4] = rpm & 0xFF; - // VSS: bytes 5..6 - frame[5] = (vss >> 8) & 0xFF; - frame[6] = vss & 0xFF; - // MAP: bytes 7..8 - frame[7] = (mapRaw >> 8) & 0xFF; - frame[8] = mapRaw & 0xFF; + // RPM: bytes 2..3, raw stream is display RPM + 256 + final rpmRaw = rpm + 256; + frame[2] = (rpmRaw >> 8) & 0xFF; + frame[3] = rpmRaw & 0xFF; + // VSS: bytes 5..6 little-endian + frame[5] = vss & 0xFF; + frame[6] = (vss >> 8) & 0xFF; + // MAP: bytes 7..8 little-endian + frame[7] = mapRaw & 0xFF; + frame[8] = (mapRaw >> 8) & 0xFF; // TPS: byte 9 frame[9] = tps; - // INJ: bytes 10..11 - frame[10] = (inj >> 8) & 0xFF; - frame[11] = inj & 0xFF; + // INJ: bytes 10..11 little-endian + frame[10] = inj & 0xFF; + frame[11] = (inj >> 8) & 0xFF; // IGN: byte 12 frame[12] = ign; // KRtrd: byte 13 @@ -46,8 +47,8 @@ Uint8List _buildS300Frame({ frame[0x13] = sw3; // ECT: byte 0x2D frame[0x2D] = ect; - // BAT: byte 0x30 - frame[0x30] = bat; + // BAT: byte 0x24 + frame[0x24] = bat; // Stamp NEG8 checksum at byte 127 int neg8 = 0; @@ -74,17 +75,17 @@ void main() { }); test('parses VSS correctly when raw >= 893', () { - // vss = 228480 / 1000 = 228.48 km/h + // vss = 144256 / 1000 = 144.256 mph final frame = _buildS300Frame(vss: 1000); final state = parseS300(frame); - expect(state.vss, closeTo(228.48, 0.01)); + expect(state.vss, closeTo(144.26, 0.01)); }); - test('parses MAP = raw / 10', () { - // mapRaw = 1000 → 100.0 kPa - final frame = _buildS300Frame(mapRaw: 1000); + test('parses MAP as SManager-style psi', () { + // mapRaw = 1148 -> 114.8 kPa absolute -> about 1.6 psi displayed + final frame = _buildS300Frame(mapRaw: 1148); final state = parseS300(frame); - expect(state.map, equals(100.0)); + expect(state.map, closeTo(1.6, 0.01)); }); test('parses TPS = 0 when raw < 25', () { @@ -94,10 +95,10 @@ void main() { }); test('parses TPS correctly when raw >= 25', () { - // tps raw=46 → 46*51/46 = 51.0 - final frame = _buildS300Frame(tps: 46); + // tps raw=134 -> approximately 51% + final frame = _buildS300Frame(tps: 134); final state = parseS300(frame); - expect(state.tps, closeTo(51.0, 0.01)); + expect(state.tps, closeTo(50.93, 0.01)); }); test('parses ECT using temp lookup table', () { @@ -115,10 +116,10 @@ void main() { }); test('IGN decode: (raw + 120) / 2', () { - // ign raw = 20 → (20+120)/2 = 70.0° - final frame = _buildS300Frame(ign: 20); + // ign raw = 164 -> 49.0° + final frame = _buildS300Frame(ign: 164); final state = parseS300(frame); - expect(state.ign, equals(70.0)); + expect(state.ign, equals(49.0)); }); test('MIL flag set from SW2 bit5', () {