hondavert-dev/lib/core/protocol/kpro_parser.dart
metatroncubeswdev 6b8d069ef4 O2 in volts, injector duty, Fuel Pump default flag, raw frame log
Checked against the USB tool on the bench (3210 rpm):
- O2: raw x 5/256 V (raw 195 -> 3.81 V, as the USB tool shows).
- New derived sensor: injector duty = INJ ms x RPM / 1200
  (16.67 ms @ 3210 rpm -> 44 %, as the USB tool shows).
- Default dashboard flags: MIL, Fuel Pump, FAN Out, VTEC Out (the
  requirement's "FuelCut" is the fuel pump flag on the S300).
- Log one raw frame (bytes 0-63) every 5 s so USB-tool readings can be
  matched to bytes; INJ, IGN, TPS, MIL, AF and PA still need that check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:47:12 -04:00

156 lines
4.6 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';
/// 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. Little-endian like every other multi-byte
// field from this adapter (S300 RPM/VSS/MAP confirmed LE live; the KPro
// table itself labels MAP2 and AFCMD as L/H byte pairs). Was read BE.
// VERIFY on bench: 2000 rpm must show 2000.
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 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 = _readUint16LE(frame, 33); // MAP2L @33, MAP2H @34
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
// 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 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 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
// SW2 bit2 = MIL
final bool mil = (sw2 & 0x04) != 0;
// SW1 bit6 = FLR. Unverified: never live-tested for KPro, and the
// equivalent S300 bit turned out to be fuel PUMP relay status, not fuel
// cut, despite an initially plausible-looking name — treat "FLR" the
// same way until confirmed.
final bool fuelPump = (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,
fuelPump: fuelPump,
fanOut: fanOut,
vtec: vtec,
knock: knock,
revLimit: revLimit,
launch: launch,
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);
}