- 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
143 lines
3.8 KiB
Dart
143 lines
3.8 KiB
Dart
import 'dart:typed_data';
|
||
import 'sensor_state.dart';
|
||
import 'temp_table.dart';
|
||
import 'neg8.dart';
|
||
|
||
/// Parses a 128-byte KPro ECU response frame into a [SensorState].
|
||
/// Throws [ArgumentError] if frame length is wrong or NEG8 checksum fails.
|
||
SensorState parseKPro(Uint8List frame) {
|
||
if (frame.length != 128) {
|
||
throw ArgumentError('KPro frame must be 128 bytes, got ${frame.length}');
|
||
}
|
||
if (!validateFrame(frame)) {
|
||
throw ArgumentError('KPro frame NEG8 checksum failed');
|
||
}
|
||
|
||
// RPM: bytes 2..3 — raw / 4
|
||
final int rpmRaw = _readUint16BE(frame, 2);
|
||
final double rpm = rpmRaw / 4.0;
|
||
|
||
// VSS: byte 4 direct km/h
|
||
final double vss = frame[4].toDouble();
|
||
|
||
// TPS: byte 5 direct %
|
||
final double tps = frame[5].toDouble();
|
||
|
||
// MAP: bytes 6 (MAP1) and 33..34 (MAP2 L+H)
|
||
final int map1Raw = frame[6];
|
||
final int map2Raw = _readUint16BE(frame, 33);
|
||
final double map = (map2Raw != 0) ? map2Raw / 10.0 : map1Raw.toDouble();
|
||
|
||
// INJ: byte 11 raw
|
||
final double inj = frame[11].toDouble();
|
||
|
||
// IGN: byte 13 raw degrees
|
||
final double ign = frame[13].toDouble();
|
||
|
||
// O2: byte 14 raw
|
||
final double o2 = frame[14].toDouble();
|
||
|
||
// KRtrd: byte 21
|
||
final int krtrd = frame[21];
|
||
|
||
// SW bitmaps
|
||
final int sw1 = frame[31];
|
||
final int sw2 = frame[32];
|
||
|
||
// ECT: byte 49
|
||
final double ect = (tempXlt[frame[49]] + 40).toDouble();
|
||
|
||
// IAT: byte 50
|
||
final double iat = (tempXlt[frame[50]] + 40).toDouble();
|
||
|
||
// PA: byte 51
|
||
final double pa = frame[51].toDouble();
|
||
|
||
// BAT: byte 52 — raw / 10
|
||
final double bat = frame[52] / 10.0;
|
||
|
||
// STRIM: byte 32 (SW2 shares offset 32, STRIM is byte 20 per spec offset +20)
|
||
final double strim = frame[20].toDouble();
|
||
|
||
// LTRIM — not explicitly listed in KPro; use 0
|
||
const double ltrim = 0.0;
|
||
|
||
// ETH — not in KPro spec; use 0
|
||
const double eth = 0.0;
|
||
|
||
// AFR: byte 25 (AFCMD at offset +19, then raw)
|
||
final double afr = frame[25].toDouble();
|
||
|
||
// ERR bytes: 18 bytes starting at offset 0x3E = 62
|
||
final List<int> errBytes = List<int>.generate(18, (i) => frame[62 + i]);
|
||
|
||
// AIN0–AIN7: bytes 0x52..0x61 (offset 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();
|
||
|
||
// Gear — not in KPro frame, default 0
|
||
const int gear = 0;
|
||
|
||
// Flags
|
||
// SW2 bit2 = MIL
|
||
final bool mil = (sw2 & 0x04) != 0;
|
||
// SW1 bit6 = FLR (fuel cut relay)
|
||
final bool fuelCut = (sw1 & 0x40) != 0;
|
||
// SW1 bit0 = FANC
|
||
final bool fanOut = (sw1 & 0x01) != 0;
|
||
// SW2 bit1..0 = VTS (VTEC)
|
||
final bool vtec = (sw2 & 0x03) != 0;
|
||
// Knock = KRtrd > 0
|
||
final bool knock = krtrd > 0;
|
||
// Rev limit — not directly mapped in KPro SW; false
|
||
const bool revLimit = false;
|
||
// Launch — not in KPro SW; false
|
||
const bool launch = false;
|
||
|
||
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];
|
||
}
|