Handle S300 alternate tail RPM frames

This commit is contained in:
HVBT Dev 2026-07-28 02:31:02 +05:30
parent c2f92db217
commit 375c2510ae
3 changed files with 82 additions and 11 deletions

View File

@ -28,6 +28,10 @@ bool isS300PlaceholderFrame(Uint8List frame) {
frame[16] == 0xC4; frame[16] == 0xC4;
} }
bool hasS300TailRpmPayload(Uint8List frame) {
return _s300TailRpmRaw(frame) != null;
}
/// Parses a 128-byte S300 ECU response frame into a [SensorState]. /// Parses a 128-byte S300 ECU response frame into a [SensorState].
/// Throws [ArgumentError] if frame length is wrong, or if [validateChecksum] /// Throws [ArgumentError] if frame length is wrong, or if [validateChecksum]
/// is true and the NEG8 checksum fails. /// is true and the NEG8 checksum fails.
@ -39,13 +43,12 @@ SensorState parseS300(Uint8List frame, {bool validateChecksum = true}) {
throw ArgumentError('S300 frame NEG8 checksum failed'); throw ArgumentError('S300 frame NEG8 checksum failed');
} }
// Some S300 Bluetooth frames carry live RPM in the tail payload at 82..83, // Some S300 Bluetooth frames carry live RPM in the tail payload,
// while the header bytes stay fixed at 1b 00 14 00 and decode to a bogus // 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. // 4864 rpm. Prefer the tail value when it looks like real engine speed.
final bool hasTailPayload = _hasS300TailPayload(frame); final int? tailRpmRaw = _s300TailRpmRaw(frame);
final int rpmRaw = final int rpmRaw = tailRpmRaw ?? _readUint16BE(frame, 2);
hasTailPayload ? _readUint16LE(frame, 82) : _readUint16BE(frame, 2); final double rpm = (tailRpmRaw != null ? rpmRaw - 1089 : rpmRaw - 256)
final double rpm = (hasTailPayload ? rpmRaw - 1089 : rpmRaw - 256)
.clamp(0, 12000) .clamp(0, 12000)
.toDouble(); .toDouble();
@ -185,8 +188,11 @@ int _readUint16LE(Uint8List frame, int offset) {
return frame[offset] | (frame[offset + 1] << 8); return frame[offset] | (frame[offset + 1] << 8);
} }
bool _hasS300TailPayload(Uint8List frame) { int? _s300TailRpmRaw(Uint8List frame) {
if (!isS300PlaceholderFrame(frame)) return false; if (!isS300PlaceholderFrame(frame)) return null;
final rpm = _readUint16LE(frame, 82); for (final offset in const [82, 86]) {
return rpm > 300 && rpm < 12000; final rpm = _readUint16LE(frame, offset);
if (rpm > 300 && rpm < 12000) return rpm;
}
return null;
} }

View File

@ -15,6 +15,9 @@ final sensorStateProvider = StreamProvider<SensorState>((ref) async* {
await for (final frame in btNotifier.frameStream) { await for (final frame in btNotifier.frameStream) {
if (ecuType == EcuType.s300) { if (ecuType == EcuType.s300) {
if (isS300PlaceholderFrame(frame) && !hasS300TailRpmPayload(frame)) {
continue;
}
yield parseS300(frame, validateChecksum: false); yield parseS300(frame, validateChecksum: false);
} else { } else {
yield parseKPro(frame); yield parseKPro(frame);

View File

@ -190,8 +190,7 @@ void main() {
expect(isS300PlaceholderFrame(frame), isTrue); expect(isS300PlaceholderFrame(frame), isTrue);
}); });
test('uses calibrated tail payload RPM for S300 Bluetooth prefix frames', test('uses calibrated tail payload RPM at offset 82', () {
() {
final frame = Uint8List(128); final frame = Uint8List(128);
final prefix = [ final prefix = [
0x1B, 0x1B,
@ -222,5 +221,68 @@ void main() {
final state = parseS300(frame); final state = parseS300(frame);
expect(state.rpm, equals(2033.0)); expect(state.rpm, equals(2033.0));
}); });
test('uses calibrated tail payload RPM at offset 86', () {
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[86] = 0x31;
frame[87] = 0x0C;
frame[127] = calculateNeg8(frame.sublist(0, 127));
expect(hasS300TailRpmPayload(frame), isTrue);
final state = parseS300(frame);
expect(state.rpm, equals(2032.0));
});
test('detects empty 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[127] = calculateNeg8(frame.sublist(0, 127));
expect(isS300PlaceholderFrame(frame), isTrue);
expect(hasS300TailRpmPayload(frame), isFalse);
});
}); });
} }