hondavert-dev/lib/core/protocol/kpro_parser.dart
metatroncubeswdev e4bcfb57a6 Meet Requrements.pdf: selectable dashboard, units, list/graph, light mode, playback
Dashboard
- Selectable main sensor (gauge) + sub sensor, 10 selectable values
  (landscape 5 left + 5 right, portrait 2x5 on one screen) and 4
  selectable flags; long-press to change; persisted.
- Flags: the requirement examples (MIL, FAN Out, VTEC Out, Knock, WBO /
  IAT / CLT Error - errors derived from the stored DTC bits) plus the
  other S300 status bits (cuts, launch, closed loop, VTP, AC, brake).
- Record button moved to the app bar; debug screen moved to Settings.

Sensor list: 1 column on phone portrait, 2 on tablet/landscape; name,
unit, min/max, segmented bar and value.
Graph: auto-scaled Y, window max/min, current value, seconds axis and a
touch cursor; RPM/MAP/TPS/ECT by default.
Units: km/h|mph, degC|degF, kPa|bar|psi (display only), persisted.
Light/dark: palette is a ThemeExtension used by every screen; theme
persisted. SafeArea keeps content off the side navigation bar.
Datalog playback now drives the dashboard, list, graph and DTC screen,
with the playback bar on the main screen and seek updating the displays.

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

155 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
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. 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);
}