hondavert-dev/lib/core/protocol/kpro_parser.dart
metatroncubeswdev 82b1d822f9 KPro values from KPro Manager, TPS formula, RPM glitch filter, nav fix
KPro, checked against KPro Manager on the bench (7267 rpm capture; a
test replays that frame):
- TPS (raw - 25) / 2.04 (68 %), IGN (raw - 128) / 2 (5 deg), short trim
  (raw - 128) / 1.28, injector 16-bit LE at 11-12.
- BAT is byte 51 / 10 (11.40 V) and PA byte 53 kPa (95.0); the PDF table
  had them at 52 / 51.
- SW1/SW2 bits are listed lowest-first: FLR bit6, FANC bit7, VTP bit0,
  VTS bit1, MIL bit3 (VTEC Out no longer shows VTP).
S300: the same TPS formula matches the USB tool (31 % at raw ~88).

RPM glitch filter: frames whose RPM jumps more than an engine can are
dropped (KPro bench crank fault P0336 sent ~300 rpm frames between real
ones); a real change is accepted once 6 of the last 10 readings agree.
Dropped count shown on the Debug screen.

Connecting from Settings now replaces the whole navigation stack, so no
stale dashboard is left underneath.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:16:53 -04:00

164 lines
5.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:typed_data';
import 'sensor_state.dart';
import 'temp_table.dart';
import 'neg8.dart';
import 'dtc_map.dart';
import 's300_parser.dart' show tpsPercent;
/// 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');
}
// Values marked KMGR were checked against KPro Manager on the bench
// (2026-10-13, 7267 rpm frame); the PDF table's offsets for PA and BAT
// turned out to be wrong, see below.
// RPM: bytes 2..3 little-endian / 4. KMGR: 7267 rpm.
final int rpmRaw = _readUint16LE(frame, 2);
final double rpm = rpmRaw / 4.0;
// VSS: byte 4 direct km/h
final double vss = frame[4].toDouble();
// TPS: byte 5, (raw - 25) / 2.04. KMGR: raw 163 -> 68 %.
final double tps = tpsPercent(frame[5]);
// MAP: bytes 6 (MAP1) and 33..34 (MAP2 L+H)
final int map1Raw = frame[6];
final int map2Raw = _readUint16LE(frame, 33); // MAP2L @33, MAP2H @34
final double map = (map2Raw != 0) ? map2Raw / 10.0 : map1Raw.toDouble();
// INJ: bytes 11..12 little-endian, µs -> ms. KMGR: 00 00 -> 0.00 ms (in
// fuel cut). Byte order follows the L/H pairs; scale UNVERIFIED.
final double inj = _readUint16LE(frame, 11) / 1000.0;
// IGN: byte 13, (raw - 128) / 2 degrees. KMGR: raw 138 -> 5°.
final double ign = (frame[13] - 128) / 2.0;
// O2: byte 14 raw
// O2: volts, raw × 5/256 as on the S300. UNVERIFIED for KPro.
final double o2 = frame[14] * 5.0 / 256.0;
// KRtrd: byte 21
final int krtrd = frame[21];
// SW bitmaps
final int sw1 = frame[31];
final int sw2 = frame[32];
// ECT: byte 49. Table is tempXlt[raw] - 40, not +40 — see s300_parser.dart
// for the reasoning (the declared -40..150 degC sensor range is only
// reachable with subtraction).
final double ect = (tempXlt[frame[49]] - 40).toDouble();
// IAT: byte 50
final double iat = (tempXlt[frame[50]] - 40).toDouble();
// PA: byte 53, kPa. KMGR: raw 95 -> 95.0 kPa. (The PDF puts PA at 51.)
final double pa = frame[53].toDouble();
// BAT: byte 51, raw / 10. KMGR: raw 114 -> 11.40 V. (The PDF puts BAT at
// 52; that byte read 232 -> "23.2 V".)
final double bat = frame[51] / 10.0;
// STRIM: byte 20, (raw - 128) / 1.28 %. KMGR: raw 128 -> 0 %.
final double strim = (frame[20] - 128) / 1.28;
// 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 LE each)
final double ain0 = _readUint16LE(frame, 82).toDouble();
final double ain1 = _readUint16LE(frame, 84).toDouble();
final double ain2 = _readUint16LE(frame, 86).toDouble();
final double ain3 = _readUint16LE(frame, 88).toDouble();
final double ain4 = _readUint16LE(frame, 90).toDouble();
final double ain5 = _readUint16LE(frame, 92).toDouble();
final double ain6 = _readUint16LE(frame, 94).toDouble();
final double ain7 = _readUint16LE(frame, 96).toDouble();
// Gear — not in KPro frame, default 0
const int gear = 0;
// Flags. The PDF lists the bits LOWEST first. KMGR: SW1 = 0x50 with SCS
// and FLR on -> SCS bit4, FLR bit6.
// SW1: RVSLCK(0) BKSW(1) ACSW(2) ACCL(3) SCS(4) EPS(5) FLR(6) FANC(7)
final bool brakeSwitch = (sw1 & 0x02) != 0;
final bool acSwitch = (sw1 & 0x04) != 0;
final bool fuelPump = (sw1 & 0x40) != 0; // FLR = fuel pump relay
final bool fanOut = (sw1 & 0x80) != 0; // FANC
// SW2: VTP(0) VTS(1) … MIL(3). KMGR: SW2 = 0x01/0x09 with VTP on, VTS
// off, MIL on -> MIL is bit3 (the PDF list has a gap). MIL UNVERIFIED
// beyond this one capture.
final bool vtp = (sw2 & 0x01) != 0;
final bool vtec = (sw2 & 0x02) != 0; // VTS = VTEC solenoid
final bool mil = (sw2 & 0x08) != 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,
fuelPump: fuelPump,
fanOut: fanOut,
vtec: vtec,
knock: knock,
revLimit: revLimit,
launch: launch,
brakeSwitch: brakeSwitch,
acSwitch: acSwitch,
vtp: vtp,
iatError: anyDtcBit(errBytes, kproIatErrorBits),
ectError: anyDtcBit(errBytes, kproEctErrorBits),
o2Error: anyDtcBit(errBytes, kproO2ErrorBits),
errBytes: errBytes,
timestamp: DateTime.now(),
);
}
int _readUint16LE(Uint8List frame, int offset) {
return frame[offset] | (frame[offset + 1] << 8);
}