diff --git a/lib/core/protocol/s300_parser.dart b/lib/core/protocol/s300_parser.dart index 78187ad..5665e13 100644 --- a/lib/core/protocol/s300_parser.dart +++ b/lib/core/protocol/s300_parser.dart @@ -39,11 +39,14 @@ SensorState parseS300(Uint8List frame, {bool validateChecksum = true}) { throw ArgumentError('S300 frame NEG8 checksum failed'); } - // 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(); + // Some S300 Bluetooth frames carry live RPM in the tail payload at 82..83, + // while the header bytes stay fixed at 1b 00 14 00 and decode to a bogus + // 4864 rpm. Prefer the tail value when it looks like real engine speed. + final bool hasTailPayload = _hasS300TailPayload(frame); + final int rpmRaw = + hasTailPayload ? _readUint16LE(frame, 82) : _readUint16BE(frame, 2); + final double rpm = + (hasTailPayload ? rpmRaw : rpmRaw - 256).clamp(0, 12000).toDouble(); // VSS: bytes 5..6 little-endian pulse period. final int vssRaw = _readUint16LE(frame, 5); @@ -180,3 +183,9 @@ int _readUint16BE(Uint8List frame, int offset) { int _readUint16LE(Uint8List frame, int offset) { return frame[offset] | (frame[offset + 1] << 8); } + +bool _hasS300TailPayload(Uint8List frame) { + if (!isS300PlaceholderFrame(frame)) return false; + final rpm = _readUint16LE(frame, 82); + return rpm > 300 && rpm < 12000; +} diff --git a/test/s300_parser_test.dart b/test/s300_parser_test.dart index 33af8c2..3de83f2 100644 --- a/test/s300_parser_test.dart +++ b/test/s300_parser_test.dart @@ -189,5 +189,37 @@ void main() { expect(isS300PlaceholderFrame(frame), isTrue); }); + + test('uses tail payload RPM for S300 Bluetooth prefix frames', () { + final frame = Uint8List(128); + final prefix = [ + 0x1B, + 0x00, + 0x14, + 0x00, + 0x00, + 0xFF, + 0xFF, + 0xF2, + 0x03, + 0x18, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0xC4, + ]; + for (var i = 0; i < prefix.length; i++) { + frame[i] = prefix[i]; + } + frame[82] = 0x32; + frame[83] = 0x0C; + frame[127] = calculateNeg8(frame.sublist(0, 127)); + + final state = parseS300(frame); + expect(state.rpm, equals(3122.0)); + }); }); }