- Phase 1 core protocol: temp_table, neg8, sensor_state, s300_parser, kpro_parser, dtc_map, sensor_defs (50/50 tests passing) - BT layer: bt_service.dart, bt_poller.dart (100ms poll, NEG8 validation) - Connection test UI: device picker, protocol selector, live sensor screen with LIVE DATA + DEBUG LOG tabs - Runtime BT permission request (Android 12+) + auto-enable Bluetooth - Android: minSdk=26, all BT+location permissions in manifest - Fixed flutter_bluetooth_serial namespace for AGP compatibility
150 lines
4.1 KiB
Dart
150 lines
4.1 KiB
Dart
import 'dart:typed_data';
|
||
import 'sensor_state.dart';
|
||
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) {
|
||
if (frame.length != 128) {
|
||
throw ArgumentError('S300 frame must be 128 bytes, got ${frame.length}');
|
||
}
|
||
if (!validateFrame(frame)) {
|
||
throw ArgumentError('S300 frame NEG8 checksum failed');
|
||
}
|
||
|
||
// RPM: bytes 3..4 uint16 BE, used directly
|
||
final int rpmRaw = _readUint16BE(frame, 3);
|
||
final double rpm = rpmRaw.toDouble();
|
||
|
||
// VSS: bytes 5..6
|
||
final int vssRaw = _readUint16BE(frame, 5);
|
||
final double vss =
|
||
(vssRaw < 893 || vssRaw == 0xFFFF) ? 0.0 : 228480.0 / vssRaw;
|
||
|
||
// MAP: bytes 7..8 — raw / 10 = kPa
|
||
final int mapRaw = _readUint16BE(frame, 7);
|
||
final double map = mapRaw / 10.0;
|
||
|
||
// TPS: byte 9
|
||
final int tpsRaw = frame[9];
|
||
final double tps = (tpsRaw < 25) ? 0.0 : (tpsRaw * 51.0) / 46.0;
|
||
|
||
// INJ: bytes 10..11 raw ms
|
||
final int injRaw = _readUint16BE(frame, 10);
|
||
final double inj = injRaw.toDouble();
|
||
|
||
// IGN: byte 12 — (raw + 120) / 2
|
||
final double ign = (frame[12] + 120) / 2.0;
|
||
|
||
// O2: byte 16 raw
|
||
final double o2 = frame[16].toDouble();
|
||
|
||
// SW bitmaps
|
||
final int sw1 = frame[0x11]; // +17
|
||
final int sw2 = frame[0x12]; // +18
|
||
final int sw3 = frame[0x13]; // +19
|
||
final int sw5 = frame[0x43]; // +67
|
||
|
||
// Gear: byte 0x27 = +39 decimal... wait, +27 hex = decimal 39
|
||
final int gear = frame[0x27];
|
||
|
||
// Strim: byte 0x29 hex = 41 decimal
|
||
final double strim = frame[0x29].toDouble();
|
||
|
||
// Ltrim: byte 0x2B hex = 43 decimal
|
||
final double ltrim = frame[0x2B].toDouble();
|
||
|
||
// PA: byte 0x2C hex = 44 decimal
|
||
final double pa = frame[0x2C].toDouble();
|
||
|
||
// ECT: byte 0x2D hex = 45 decimal
|
||
final double ect = (tempXlt[frame[0x2D]] + 40).toDouble();
|
||
|
||
// IAT: byte 0x2E hex = 46 decimal
|
||
final double iat = (tempXlt[frame[0x2E]] + 40).toDouble();
|
||
|
||
// BAT: byte 0x30 hex = 48 decimal
|
||
final double bat = frame[0x30] * 26.0 / 270.0;
|
||
|
||
// ERR bytes: 0x31..0x34 (4 bytes for S300)
|
||
final List<int> errBytes = [
|
||
frame[0x31],
|
||
frame[0x32],
|
||
frame[0x33],
|
||
frame[0x34],
|
||
];
|
||
|
||
// Eth: byte 0x38 hex = 56 decimal
|
||
final double eth = frame[0x38].toDouble();
|
||
|
||
// AFR: byte 0x34 overlaps with ERR03 in the spec — use it as raw AFR
|
||
final double afr = frame[0x34].toDouble();
|
||
|
||
// AIN0–AIN7: decimal offsets 82..97 (uint16 BE each)
|
||
final double ain0 = _readUint16BE(frame, 82).toDouble();
|
||
final double ain1 = _readUint16BE(frame, 84).toDouble();
|
||
final double ain2 = _readUint16BE(frame, 86).toDouble();
|
||
final double ain3 = _readUint16BE(frame, 88).toDouble();
|
||
final double ain4 = _readUint16BE(frame, 90).toDouble();
|
||
final double ain5 = _readUint16BE(frame, 92).toDouble();
|
||
final double ain6 = _readUint16BE(frame, 94).toDouble();
|
||
final double ain7 = _readUint16BE(frame, 96).toDouble();
|
||
|
||
// Flags
|
||
// SW1 bit3 = REVL
|
||
final bool revLimit = (sw1 & 0x08) != 0;
|
||
// SW2 bit5 = MIL
|
||
final bool mil = (sw2 & 0x20) != 0;
|
||
// SW2 bit2 = Fuel (cut)
|
||
final bool fuelCut = (sw2 & 0x04) != 0;
|
||
// SW2 bit7 = ALTC → fanOut; also SW5 bit0 = FANC
|
||
final bool fanOut = (sw2 & 0x80) != 0 || (sw5 & 0x01) != 0;
|
||
// SW2 bit1..0 = VTS (VTEC solenoid — nonzero = active)
|
||
final bool vtec = (sw2 & 0x03) != 0;
|
||
// Knock = KRtrd > 0 (byte 13)
|
||
final bool knock = frame[13] > 0;
|
||
// SW3 bit7 = LnchC
|
||
final bool launch = (sw3 & 0x80) != 0;
|
||
|
||
return SensorState(
|
||
rpm: rpm,
|
||
vss: vss,
|
||
map: map,
|
||
tps: tps,
|
||
inj: inj,
|
||
ign: ign,
|
||
ect: ect,
|
||
iat: iat,
|
||
bat: bat,
|
||
o2: o2,
|
||
gear: gear,
|
||
eth: eth,
|
||
pa: pa,
|
||
afr: afr,
|
||
strim: strim,
|
||
ltrim: ltrim,
|
||
ain0: ain0,
|
||
ain1: ain1,
|
||
ain2: ain2,
|
||
ain3: ain3,
|
||
ain4: ain4,
|
||
ain5: ain5,
|
||
ain6: ain6,
|
||
ain7: ain7,
|
||
mil: mil,
|
||
fuelCut: fuelCut,
|
||
fanOut: fanOut,
|
||
vtec: vtec,
|
||
knock: knock,
|
||
revLimit: revLimit,
|
||
launch: launch,
|
||
errBytes: errBytes,
|
||
timestamp: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
int _readUint16BE(Uint8List frame, int offset) {
|
||
return (frame[offset] << 8) | frame[offset + 1];
|
||
}
|