From 6b8d069ef4a162b608ecc33f8ea86f6b0a0913d8 Mon Sep 17 00:00:00 2001 From: metatroncubeswdev Date: Mon, 12 Oct 2026 23:47:12 -0400 Subject: [PATCH] O2 in volts, injector duty, Fuel Pump default flag, raw frame log Checked against the USB tool on the bench (3210 rpm): - O2: raw x 5/256 V (raw 195 -> 3.81 V, as the USB tool shows). - New derived sensor: injector duty = INJ ms x RPM / 1200 (16.67 ms @ 3210 rpm -> 44 %, as the USB tool shows). - Default dashboard flags: MIL, Fuel Pump, FAN Out, VTEC Out (the requirement's "FuelCut" is the fuel pump flag on the S300). - Log one raw frame (bytes 0-63) every 5 s so USB-tool readings can be matched to bytes; INJ, IGN, TPS, MIL, AF and PA still need that check. Co-Authored-By: Claude Opus 5.5 --- lib/core/models/sensor_defs.dart | 9 +++++-- lib/core/protocol/kpro_parser.dart | 3 ++- lib/core/protocol/s300_parser.dart | 4 +-- lib/core/providers/bt_provider.dart | 19 +++++++++++++ .../providers/sensor_history_provider.dart | 27 +++---------------- .../dashboard/dashboard_slots_provider.dart | 4 ++- test/units_and_flags_test.dart | 11 ++++++++ 7 files changed, 47 insertions(+), 30 deletions(-) diff --git a/lib/core/models/sensor_defs.dart b/lib/core/models/sensor_defs.dart index 5908b72..e94122e 100644 --- a/lib/core/models/sensor_defs.dart +++ b/lib/core/models/sensor_defs.dart @@ -32,9 +32,12 @@ const List sensorDefs = [ max: 150), SensorDef( id: 'bat', displayName: 'Battery voltage', unit: 'V', min: 0, max: 20), - // O2/AFR/AIN scales not yet bench-verified: shown as raw values. SensorDef( - id: 'o2', displayName: 'O2 sensor', unit: 'raw', min: 0, max: 255, decimals: 0), + id: 'o2', displayName: 'O2 sensor', unit: 'V', min: 0, max: 5, decimals: 2), + // Derived: injector duration × RPM (USB tool: 16.67 ms @ 3210 rpm = 44 %). + SensorDef( + id: 'duty', displayName: 'Injector duty', unit: '%', min: 0, max: 100, decimals: 0), + // AFR/AIN scales not yet bench-verified: shown as raw values. SensorDef( id: 'gear', displayName: 'Gear', unit: '', min: 0, max: 6, decimals: 0), SensorDef( @@ -81,6 +84,8 @@ double sensorValue(SensorState s, String id) { case 'iat': return s.iat; case 'bat': return s.bat; case 'o2': return s.o2; + // One injection per 2 revolutions: ms × rpm / 120000 × 100. + case 'duty': return s.inj * s.rpm / 1200; case 'gear': return s.gear.toDouble(); case 'eth': return s.eth; case 'pa': return s.pa; diff --git a/lib/core/protocol/kpro_parser.dart b/lib/core/protocol/kpro_parser.dart index 637029a..253871a 100644 --- a/lib/core/protocol/kpro_parser.dart +++ b/lib/core/protocol/kpro_parser.dart @@ -39,7 +39,8 @@ SensorState parseKPro(Uint8List frame) { final double ign = frame[13].toDouble(); // O2: byte 14 raw - final double o2 = frame[14].toDouble(); + // O2: volts, raw × 5/256 as on the S300. UNVERIFIED for KPro. + final double o2 = frame[14] * 5.0 / 256.0; // KRtrd: byte 21 final int krtrd = frame[21]; diff --git a/lib/core/protocol/s300_parser.dart b/lib/core/protocol/s300_parser.dart index afc7bdd..6c6a80f 100644 --- a/lib/core/protocol/s300_parser.dart +++ b/lib/core/protocol/s300_parser.dart @@ -97,8 +97,8 @@ SensorState parseS300(Uint8List frame, {bool validateChecksum = true}) { // IGN: formula from the PDF. UNVERIFIED (one live check disagreed). final double ign = (frame[12] + 120) / 2.0; - // O2: raw byte. UNVERIFIED scale — shown as raw. - final double o2 = frame[16].toDouble(); + // O2: volts, raw × 5/256 (USB tool: raw 195 → 3.81 V). + final double o2 = frame[16] * 5.0 / 256.0; final int sw1 = frame[17]; final int sw2 = frame[18]; diff --git a/lib/core/providers/bt_provider.dart b/lib/core/providers/bt_provider.dart index fae74fe..97e74c7 100644 --- a/lib/core/providers/bt_provider.dart +++ b/lib/core/providers/bt_provider.dart @@ -245,12 +245,31 @@ class BtNotifier extends StateNotifier { } else { _liveFrames++; _lastLiveFrameAt = now; + _maybeLogFrame(frame, now); _defaultsSince = null; _frameController.add(frame); } _publishStats(frame); } + DateTime? _lastFrameLogAt; + + /// Raw hex of one live frame every 5 s, so bench readings from the USB + /// tool can be matched to exact bytes (Debug → Copy app log, or logcat). + void _maybeLogFrame(Uint8List frame, DateTime now) { + final last = _lastFrameLogAt; + if (last != null && now.difference(last) < const Duration(seconds: 5)) { + return; + } + _lastFrameLogAt = now; + final hex = StringBuffer(); + for (var i = 0; i < 64; i++) { + if (i > 0) hex.write(i % 16 == 0 ? ' | ' : ' '); + hex.write(frame[i].toRadixString(16).padLeft(2, '0')); + } + AppLog.i('FRAME[0..63] $hex'); + } + void _publishStats([Uint8List? frame]) { final p = _poller; state = state.copyWith( diff --git a/lib/core/providers/sensor_history_provider.dart b/lib/core/providers/sensor_history_provider.dart index 6a38d71..0814ea1 100644 --- a/lib/core/providers/sensor_history_provider.dart +++ b/lib/core/providers/sensor_history_provider.dart @@ -23,30 +23,9 @@ class SensorHistory { if (list.length > _maxPoints) list.removeAt(0); } - push('rpm', state.rpm); - push('vss', state.vss); - push('map', state.map); - push('tps', state.tps); - push('inj', state.inj); - push('ign', state.ign); - push('ect', state.ect); - push('iat', state.iat); - push('bat', state.bat); - push('o2', state.o2); - push('gear', state.gear.toDouble()); - push('eth', state.eth); - push('pa', state.pa); - push('afr', state.afr); - push('strim', state.strim); - push('ltrim', state.ltrim); - push('ain0', state.ain0); - push('ain1', state.ain1); - push('ain2', state.ain2); - push('ain3', state.ain3); - push('ain4', state.ain4); - push('ain5', state.ain5); - push('ain6', state.ain6); - push('ain7', state.ain7); + for (final d in sensorDefs) { + push(d.id, sensorValue(state, d.id)); + } } List get(String id) => List.unmodifiable(_data[id] ?? []); diff --git a/lib/ui/screens/dashboard/dashboard_slots_provider.dart b/lib/ui/screens/dashboard/dashboard_slots_provider.dart index 6367776..dac9fd8 100644 --- a/lib/ui/screens/dashboard/dashboard_slots_provider.dart +++ b/lib/ui/screens/dashboard/dashboard_slots_provider.dart @@ -29,7 +29,9 @@ class DashboardConfig { 'vss', 'ect', 'iat', 'map', 'o2', // left 'tps', 'bat', 'ign', 'inj', 'eth', // right ], - flags: ['mil', 'fanout', 'vtec', 'knock'], + // Requirement picture: MIL, FuelCut, FAN Out, VTEC Out. On the S300 + // the fuel flag is the fuel pump relay (client). + flags: ['mil', 'fuelpump', 'fanout', 'vtec'], ); DashboardConfig copyWith({ diff --git a/test/units_and_flags_test.dart b/test/units_and_flags_test.dart index 3db7932..d453dfb 100644 --- a/test/units_and_flags_test.dart +++ b/test/units_and_flags_test.dart @@ -58,6 +58,17 @@ void main() { }); }); + group('values checked against the USB tool (bench, 3210 rpm)', () { + test('O2 volts = raw × 5/256 (raw 195 → 3.81 V)', () { + expect(parseS300(_s300({16: 195})).o2, closeTo(3.81, 0.005)); + }); + + test('injector duty = INJ ms × RPM / 1200 (16.67 ms @ 3210 → 44 %)', () { + final s = parseS300(_s300({})).copyWith(inj: 16.67, rpm: 3210); + expect(sensorValue(s, 'duty'), closeTo(44.6, 0.1)); + }); + }); + group('S300 flags', () { test('SW3 cut/launch bits (byte 19)', () { final s = parseS300(_s300({19: 0x20 | 0x08}));