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>
168 lines
5.1 KiB
Dart
168 lines
5.1 KiB
Dart
import 'dart:typed_data';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:hvbt_dash/core/protocol/kpro_parser.dart';
|
|
import 'package:hvbt_dash/core/protocol/neg8.dart';
|
|
import 'package:hvbt_dash/core/protocol/temp_table.dart';
|
|
|
|
/// Builds a zeroed 128-byte KPro frame and stamps the NEG8 checksum at byte 127.
|
|
Uint8List _buildKProFrame({
|
|
int rpm = 0,
|
|
int vss = 0,
|
|
int tps = 0,
|
|
int map1 = 0,
|
|
int map2 = 0,
|
|
int ign = 0,
|
|
int ect = 0x80, // tempXlt[0x80]=73 → 33°C
|
|
int bat = 0,
|
|
int sw1 = 0,
|
|
int sw2 = 0,
|
|
int krtrd = 0,
|
|
}) {
|
|
final frame = Uint8List(128);
|
|
// Header
|
|
frame[0] = 0x1B;
|
|
frame[1] = 0x01;
|
|
// RPM: bytes 2..3, little-endian
|
|
frame[2] = rpm & 0xFF;
|
|
frame[3] = (rpm >> 8) & 0xFF;
|
|
// VSS: byte 4
|
|
frame[4] = vss & 0xFF;
|
|
// TPS: byte 5
|
|
frame[5] = tps & 0xFF;
|
|
// MAP1: byte 6
|
|
frame[6] = map1 & 0xFF;
|
|
// MAP2: bytes 33 (L), 34 (H)
|
|
frame[33] = map2 & 0xFF;
|
|
frame[34] = (map2 >> 8) & 0xFF;
|
|
// IGN: byte 13
|
|
frame[13] = ign;
|
|
// KRtrd: byte 21
|
|
frame[21] = krtrd;
|
|
// SW1: byte 31, SW2: byte 32
|
|
frame[31] = sw1;
|
|
frame[32] = sw2;
|
|
// ECT: byte 49
|
|
frame[49] = ect;
|
|
// BAT: byte 52
|
|
frame[51] = bat; // KMGR: BAT is byte 51 (the PDF says 52)
|
|
|
|
// Stamp NEG8 checksum at byte 127
|
|
int neg8 = 0;
|
|
for (int i = 0; i < 127; i++) {
|
|
neg8 = (neg8 - frame[i]) & 0xFF;
|
|
}
|
|
frame[127] = neg8;
|
|
return frame;
|
|
}
|
|
|
|
void main() {
|
|
group('KPro parser', () {
|
|
test('parses RPM = raw / 4', () {
|
|
// rpm raw = 4000 → 1000.0 revs
|
|
final frame = _buildKProFrame(rpm: 4000);
|
|
final state = parseKPro(frame);
|
|
expect(state.rpm, equals(1000.0));
|
|
});
|
|
|
|
test('parses VSS directly', () {
|
|
final frame = _buildKProFrame(vss: 120);
|
|
final state = parseKPro(frame);
|
|
expect(state.vss, equals(120.0));
|
|
});
|
|
|
|
test('parses TPS = (raw - 25) / 2.04', () {
|
|
final frame = _buildKProFrame(tps: 163); // KMGR: 68 %
|
|
final state = parseKPro(frame);
|
|
expect(state.tps, closeTo(67.6, 0.1));
|
|
});
|
|
|
|
test('parses MAP from MAP1 when MAP2 = 0', () {
|
|
final frame = _buildKProFrame(map1: 100, map2: 0);
|
|
final state = parseKPro(frame);
|
|
expect(state.map, equals(100.0));
|
|
});
|
|
|
|
test('parses MAP from MAP2 / 10 when MAP2 != 0', () {
|
|
// map2 raw = 1000 → 100.0 kPa
|
|
final frame = _buildKProFrame(map1: 50, map2: 1000);
|
|
final state = parseKPro(frame);
|
|
expect(state.map, equals(100.0));
|
|
});
|
|
|
|
test('parses ECT using temp lookup table', () {
|
|
// ect raw = 0x80 = 128 → tempXlt[128]=73 → 33°C
|
|
final frame = _buildKProFrame(ect: 0x80);
|
|
final state = parseKPro(frame);
|
|
expect(state.ect, equals((tempXlt[0x80] - 40).toDouble()));
|
|
});
|
|
|
|
test('parses BAT = raw / 10', () {
|
|
// bat raw = 130 → 13.0 V
|
|
final frame = _buildKProFrame(bat: 130);
|
|
final state = parseKPro(frame);
|
|
expect(state.bat, equals(13.0));
|
|
});
|
|
|
|
test('MIL flag from SW2 bit3', () {
|
|
final frame = _buildKProFrame(sw2: 0x08); // bit3
|
|
final state = parseKPro(frame);
|
|
expect(state.mil, isTrue);
|
|
});
|
|
|
|
test('real bench frame matches KPro Manager (7267 rpm capture)', () {
|
|
// First 64 bytes logged at 00:40:25 on 2026-10-13, while KPro Manager
|
|
// showed: RPM 7267, VSS 0, TPS 68 %, IGN 5°, S.TRIM 0 %, BAT 11.40 V,
|
|
// PA 95.0 kPa, INJ 0.00 ms, SCS/FLR on, VTP on, VTS off, MIL on.
|
|
const head = [
|
|
0x1b, 0x01, 0x8d, 0x71, 0x00, 0xa3, 0x75, 0xff, //
|
|
0x28, 0x00, 0x4d, 0x00, 0x00, 0x8a, 0x00, 0xd2,
|
|
0xd9, 0xaf, 0xf6, 0x97, 0x80, 0x00, 0x03, 0x8b,
|
|
0x00, 0x00, 0x12, 0x0c, 0x07, 0x09, 0x00, 0x50,
|
|
0x09, 0xa0, 0x04, 0x01, 0x00, 0x00, 0xff, 0x00,
|
|
0x00, 0xbf, 0xa3, 0xbf, 0xe8, 0x10, 0xf0, 0xff,
|
|
0x8d, 0xd8, 0xd2, 0x72, 0xe8, 0x5f, 0xfe, 0x80,
|
|
0x04, 0x00, 0x00, 0x00, 0x65,
|
|
];
|
|
final frame = Uint8List(128)..setRange(0, head.length, head);
|
|
frame[127] = calculateNeg8(frame.sublist(0, 127));
|
|
final s = parseKPro(frame);
|
|
expect(s.rpm, closeTo(7267, 1));
|
|
expect(s.vss, 0);
|
|
expect(s.tps.round(), 68);
|
|
expect(s.ign, 5);
|
|
expect(s.strim, 0);
|
|
expect(s.bat, closeTo(11.4, 0.001));
|
|
expect(s.pa, 95);
|
|
expect(s.inj, 0);
|
|
expect(s.map, closeTo(118.4, 0.001)); // 2.5 psi boost
|
|
expect(s.fuelPump, isTrue); // FLR
|
|
expect(s.fanOut, isFalse); // FANC
|
|
expect(s.vtp, isTrue);
|
|
expect(s.vtec, isFalse); // VTS
|
|
expect(s.mil, isTrue);
|
|
});
|
|
|
|
test('knock flag when krtrd > 0', () {
|
|
final frame = _buildKProFrame(krtrd: 3);
|
|
final state = parseKPro(frame);
|
|
expect(state.knock, isTrue);
|
|
});
|
|
|
|
test('errBytes has 18 entries', () {
|
|
final frame = _buildKProFrame();
|
|
final state = parseKPro(frame);
|
|
expect(state.errBytes.length, equals(18));
|
|
});
|
|
|
|
test('throws on wrong frame length', () {
|
|
expect(() => parseKPro(Uint8List(64)), throwsArgumentError);
|
|
});
|
|
|
|
test('throws on bad checksum', () {
|
|
final frame = _buildKProFrame(rpm: 4000);
|
|
frame[127] = 0x00; // corrupt
|
|
expect(() => parseKPro(frame), throwsArgumentError);
|
|
});
|
|
});
|
|
}
|