hondavert-dev/test/kpro_parser_test.dart
HVBT Dev 11cf7c2b63 v1.0.1 — Phase 1 complete + BT connection test UI
- 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
2026-04-12 18:07:37 +05:30

134 lines
3.5 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/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 → 113°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
frame[2] = (rpm >> 8) & 0xFF;
frame[3] = rpm & 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..34
frame[33] = (map2 >> 8) & 0xFF;
frame[34] = map2 & 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[52] = bat;
// 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 directly', () {
final frame = _buildKProFrame(tps: 75);
final state = parseKPro(frame);
expect(state.tps, equals(75.0));
});
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 → 113°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 bit2', () {
final frame = _buildKProFrame(sw2: 0x04); // bit2
final state = parseKPro(frame);
expect(state.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);
});
});
}