diff --git a/lib/core/bluetooth/bt_poller.dart b/lib/core/bluetooth/bt_poller.dart index 17bc9b1..aeb42b2 100644 --- a/lib/core/bluetooth/bt_poller.dart +++ b/lib/core/bluetooth/bt_poller.dart @@ -85,7 +85,11 @@ class BtPoller { final Uint8List frame = Uint8List.fromList(_rxBuf.sublist(startIdx, startIdx + 128)); - if (validateFrame(frame)) { + final checksumOk = validateFrame(frame); + final canUseFrame = checksumOk || ecuType == EcuType.s300; + + if (canUseFrame) { + if (!checksumOk) droppedFrames++; validFrames++; _frameController.add(frame); _rxBuf.removeRange(0, startIdx + 128); diff --git a/lib/core/protocol/s300_parser.dart b/lib/core/protocol/s300_parser.dart index c911471..4f1bc14 100644 --- a/lib/core/protocol/s300_parser.dart +++ b/lib/core/protocol/s300_parser.dart @@ -4,12 +4,13 @@ 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) { +/// Throws [ArgumentError] if frame length is wrong, or if [validateChecksum] +/// is true and the NEG8 checksum fails. +SensorState parseS300(Uint8List frame, {bool validateChecksum = true}) { if (frame.length != 128) { throw ArgumentError('S300 frame must be 128 bytes, got ${frame.length}'); } - if (!validateFrame(frame)) { + if (validateChecksum && !validateFrame(frame)) { throw ArgumentError('S300 frame NEG8 checksum failed'); } diff --git a/lib/core/providers/sensor_provider.dart b/lib/core/providers/sensor_provider.dart index 8c50fc8..3303508 100644 --- a/lib/core/providers/sensor_provider.dart +++ b/lib/core/providers/sensor_provider.dart @@ -14,7 +14,9 @@ final sensorStateProvider = StreamProvider((ref) { final btNotifier = ref.watch(btProvider.notifier); return btNotifier.frameStream.map((frame) { - if (ecuType == EcuType.s300) return parseS300(frame); + if (ecuType == EcuType.s300) { + return parseS300(frame, validateChecksum: false); + } return parseKPro(frame); }); }); @@ -22,8 +24,6 @@ final sensorStateProvider = StreamProvider((ref) { /// Last successfully parsed sensor state (never null after first frame). /// Falls back to SensorState.zero() before any data arrives. final latestSensorProvider = Provider((ref) { - return ref - .watch(sensorStateProvider) - .whenData((s) => s) - .value ?? SensorState.zero(); + return ref.watch(sensorStateProvider).whenData((s) => s).value ?? + SensorState.zero(); }); diff --git a/lib/ui/screens/debug/raw_frame_debug_screen.dart b/lib/ui/screens/debug/raw_frame_debug_screen.dart index 5ba4ba1..9fa6c49 100644 --- a/lib/ui/screens/debug/raw_frame_debug_screen.dart +++ b/lib/ui/screens/debug/raw_frame_debug_screen.dart @@ -51,7 +51,10 @@ class RawFrameDebugScreen extends ConsumerWidget { _RowData('Dropped', bt.droppedFrames.toString()), _RowData('Last frame bytes', frame?.length.toString() ?? 'none'), if (frame != null) - _RowData('Checksum', validateFrame(frame) ? 'valid' : 'bad'), + _RowData( + 'Checksum', + validateFrame(frame) ? 'valid' : 'bad, accepted for S300', + ), ], ), const SizedBox(height: 12), diff --git a/test/s300_parser_test.dart b/test/s300_parser_test.dart index 02d5a63..3cf1d32 100644 --- a/test/s300_parser_test.dart +++ b/test/s300_parser_test.dart @@ -152,5 +152,12 @@ void main() { frame[127] = 0x00; // corrupt checksum expect(() => parseS300(frame), throwsArgumentError); }); + + test('can parse bad checksum frame when validation is disabled', () { + final frame = _buildS300Frame(rpm: 1000); + frame[127] = 0x00; // corrupt checksum + final state = parseS300(frame, validateChecksum: false); + expect(state.rpm, equals(1000.0)); + }); }); }