import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:hvbt_dash/core/models/sensor_defs.dart'; import 'package:hvbt_dash/core/models/units.dart'; import 'package:hvbt_dash/core/protocol/neg8.dart'; import 'package:hvbt_dash/core/protocol/s300_parser.dart'; /// S300 frame with header, the given bytes set, and a valid checksum. Uint8List _s300(Map bytes) { final f = Uint8List(128); f[0] = 0x1B; bytes.forEach((i, v) => f[i] = v); f[127] = calculateNeg8(f.sublist(0, 127)); return f; } void main() { group('display units', () { test('metric defaults are unchanged', () { final v = sensorView('vss', const UnitPrefs()); expect(v.unit, 'km/h'); expect(v.convert(100), 100); }); test('speed in mph', () { final v = sensorView('vss', const UnitPrefs(speed: SpeedUnit.mph)); expect(v.unit, 'mph'); expect(v.convert(100), closeTo(62.14, 0.01)); }); test('temperatures in °F, range converted too', () { final v = sensorView('ect', const UnitPrefs(temp: TempUnit.fahrenheit)); expect(v.unit, '°F'); expect(v.convert(100), closeTo(212, 0.001)); expect(v.min, closeTo(-40, 0.001)); expect(v.max, closeTo(302, 0.001)); }); test('MAP and baro in bar and psi', () { final bar = sensorView('map', const UnitPrefs(pressure: PressureUnit.bar)); expect(bar.unit, 'bar'); expect(bar.format(101.3), '1.01'); final psi = sensorView('pa', const UnitPrefs(pressure: PressureUnit.psi)); expect(psi.unit, 'psi'); expect(psi.convert(100), closeTo(14.50, 0.01)); }); test('unit choice does not touch unrelated sensors', () { const u = UnitPrefs( speed: SpeedUnit.mph, temp: TempUnit.fahrenheit, pressure: PressureUnit.psi, ); expect(sensorView('rpm', u).convert(3000), 3000); expect(sensorView('bat', u).unit, 'V'); }); }); group('values checked against the USB tool (bench, 3210 rpm)', () { test('O2 volts = raw × 5/256 (raw 195 → 3.81 V)', () { expect(parseS300(_s300({16: 195})).o2, closeTo(3.81, 0.005)); }); test('injector duty = INJ ms × RPM / 1200 (16.67 ms @ 3210 → 44 %)', () { final s = parseS300(_s300({})).copyWith(inj: 16.67, rpm: 3210); expect(sensorValue(s, 'duty'), closeTo(44.6, 0.1)); }); }); group('S300 flags', () { test('SW3 cut/launch bits (byte 19)', () { final s = parseS300(_s300({19: 0x20 | 0x08})); expect(s.ignCut, isTrue); expect(s.boostCut, isTrue); expect(s.shiftCut, isFalse); expect(s.launch, isFalse); }); test('SW1 brake/AC/VTP (byte 17), SW5 closed loop (byte 49)', () { final s = parseS300(_s300({17: 0x04 | 0x02 | 0x01, 49: 0x04})); expect(s.brakeSwitch, isTrue); expect(s.acSwitch, isTrue); expect(s.vtp, isTrue); expect(s.closedLoop, isTrue); }); test('IAT / CLT / WBO error flags come from stored DTC bits', () { // ERR01 bit1 = P0113 IAT, ERR00 bit5 = ECT, ERR00 bit0 = O2A final s = parseS300(_s300({38: 0x02, 37: 0x20 | 0x01})); expect(s.iatError, isTrue); expect(s.ectError, isTrue); expect(s.o2Error, isTrue); final clean = parseS300(_s300({})); expect(clean.iatError || clean.ectError || clean.o2Error, isFalse); }); test('every flag id resolves and reads a value', () { final s = parseS300(_s300({})); for (final f in flagDefs) { expect(flagDef(f.id).id, f.id); expect(f.read(s), isA()); } }); }); }