- 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
134 lines
3.5 KiB
Dart
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);
|
|
});
|
|
});
|
|
}
|