From 5df3442523ad95f15a211c1131536ff7305620be Mon Sep 17 00:00:00 2001 From: HVBT Dev Date: Tue, 28 Jul 2026 01:20:56 +0530 Subject: [PATCH] Skip S300 placeholders after raw receipt --- lib/core/bluetooth/bt_poller.dart | 33 ------------------------- lib/core/protocol/s300_parser.dart | 27 ++++++++++++++++++++ lib/core/providers/sensor_provider.dart | 16 +++++++----- test/s300_parser_test.dart | 30 ++++++++++++++++++++++ 4 files changed, 67 insertions(+), 39 deletions(-) diff --git a/lib/core/bluetooth/bt_poller.dart b/lib/core/bluetooth/bt_poller.dart index 616db1d..c546799 100644 --- a/lib/core/bluetooth/bt_poller.dart +++ b/lib/core/bluetooth/bt_poller.dart @@ -87,12 +87,6 @@ class BtPoller { final Uint8List frame = Uint8List.fromList(_rxBuf.sublist(startIdx, startIdx + 128)); - if (ecuType == EcuType.s300 && _isS300PlaceholderFrame(frame)) { - droppedFrames++; - _rxBuf.removeRange(0, startIdx + 128); - continue; - } - final checksumOk = validateFrame(frame); final canUseFrame = checksumOk || ecuType == EcuType.s300; @@ -125,31 +119,4 @@ class BtPoller { stop(); _frameController.close(); } - - bool _isS300PlaceholderFrame(Uint8List frame) { - if (frame.length != 128) return false; - - // The S300 Bluetooth stream can interleave a valid-checksum placeholder - // frame that is not the SManager live sensor frame. It has a fixed header - // and zero/overflow values for speed, TPS, injection and ignition, then it - // overwrites the dashboard with stale/empty values. Filter only this exact - // signature so true idle/stopped live frames can still pass. - return frame[0] == 0x1B && - frame[1] == 0x00 && - frame[2] == 0x14 && - frame[3] == 0x00 && - frame[4] == 0x00 && - frame[5] == 0xFF && - frame[6] == 0xFF && - frame[7] == 0xF2 && - frame[8] == 0x03 && - frame[9] == 0x18 && - frame[10] == 0x00 && - frame[11] == 0x00 && - frame[12] == 0x10 && - frame[13] == 0x00 && - frame[14] == 0x00 && - frame[15] == 0x00 && - frame[16] == 0xC4; - } } diff --git a/lib/core/protocol/s300_parser.dart b/lib/core/protocol/s300_parser.dart index 4f1bc14..99039c6 100644 --- a/lib/core/protocol/s300_parser.dart +++ b/lib/core/protocol/s300_parser.dart @@ -3,6 +3,33 @@ import 'sensor_state.dart'; import 'temp_table.dart'; import 'neg8.dart'; +bool isS300PlaceholderFrame(Uint8List frame) { + if (frame.length != 128) return false; + + // The S300 Bluetooth stream can interleave a valid-checksum placeholder + // frame that is not the SManager live sensor frame. It has a fixed header + // and zero/overflow values for speed, TPS, injection and ignition, then it + // overwrites the dashboard with stale/empty values. Keep this check near the + // parser so Bluetooth diagnostics can still receive and count raw frames. + return frame[0] == 0x1B && + frame[1] == 0x00 && + frame[2] == 0x14 && + frame[3] == 0x00 && + frame[4] == 0x00 && + frame[5] == 0xFF && + frame[6] == 0xFF && + frame[7] == 0xF2 && + frame[8] == 0x03 && + frame[9] == 0x18 && + frame[10] == 0x00 && + frame[11] == 0x00 && + frame[12] == 0x10 && + frame[13] == 0x00 && + frame[14] == 0x00 && + frame[15] == 0x00 && + frame[16] == 0xC4; +} + /// 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. diff --git a/lib/core/providers/sensor_provider.dart b/lib/core/providers/sensor_provider.dart index 3303508..e7bc171 100644 --- a/lib/core/providers/sensor_provider.dart +++ b/lib/core/providers/sensor_provider.dart @@ -7,18 +7,22 @@ import '../protocol/sensor_state.dart'; import 'bt_provider.dart'; import 'settings_provider.dart'; -/// Emits a parsed [SensorState] for every valid frame received from the ECU. +/// Emits a parsed [SensorState] for every live data frame received from the ECU. /// Automatically picks S300 or KPro parser based on [settingsProvider]. -final sensorStateProvider = StreamProvider((ref) { +final sensorStateProvider = StreamProvider((ref) async* { final ecuType = ref.watch(settingsProvider).ecuType; final btNotifier = ref.watch(btProvider.notifier); - return btNotifier.frameStream.map((frame) { + await for (final frame in btNotifier.frameStream) { if (ecuType == EcuType.s300) { - return parseS300(frame, validateChecksum: false); + if (isS300PlaceholderFrame(frame)) { + continue; + } + yield parseS300(frame, validateChecksum: false); + } else { + yield parseKPro(frame); } - return parseKPro(frame); - }); + } }); /// Last successfully parsed sensor state (never null after first frame). diff --git a/test/s300_parser_test.dart b/test/s300_parser_test.dart index 3cf1d32..50367d4 100644 --- a/test/s300_parser_test.dart +++ b/test/s300_parser_test.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; +import 'package:hvbt_dash/core/protocol/neg8.dart'; import 'package:hvbt_dash/core/protocol/s300_parser.dart'; import 'package:hvbt_dash/core/protocol/temp_table.dart'; @@ -159,5 +160,34 @@ void main() { final state = parseS300(frame, validateChecksum: false); expect(state.rpm, equals(1000.0)); }); + + test('detects known S300 placeholder frame', () { + final frame = Uint8List(128); + final signature = [ + 0x1B, + 0x00, + 0x14, + 0x00, + 0x00, + 0xFF, + 0xFF, + 0xF2, + 0x03, + 0x18, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0xC4, + ]; + for (var i = 0; i < signature.length; i++) { + frame[i] = signature[i]; + } + frame[127] = calculateNeg8(frame.sublist(0, 127)); + + expect(isS300PlaceholderFrame(frame), isTrue); + }); }); }