import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:dmc_app/transport/msp/msp_commands.dart'; import 'package:dmc_app/transport/msp/msp_telemetry_codec.dart'; void main() { group('parseMspRawGps', () { test('parst fixType, numSat, lat/lon (1e-7 deg), Hoehe, Speed (cm/s) und HDOP', () { // fixType=2, numSat=9, lat=52.5200000 deg, lon=13.4050000 deg, // alt=100m, groundSpeed=1234 cm/s, groundCourse=900 (0.1deg, // ungenutzt), hdop=120 (/100 = 1.2). final data = ByteData(18); data.setUint8(0, 2); data.setUint8(1, 9); data.setInt32(2, 525200000, Endian.little); data.setInt32(6, 134050000, Endian.little); data.setUint16(10, 100, Endian.little); data.setUint16(12, 1234, Endian.little); data.setUint16(14, 900, Endian.little); data.setUint16(16, 120, Endian.little); final reading = parseMspRawGps(data.buffer.asUint8List()); expect(reading.fixType, 2); expect(reading.hasFix, isTrue); expect(reading.numSat, 9); expect(reading.lat, closeTo(52.52, 1e-9)); expect(reading.lon, closeTo(13.405, 1e-9)); expect(reading.altitudeM, closeTo(100, 1e-9)); expect(reading.speedMs, closeTo(12.34, 1e-9)); expect(reading.headingDeg, closeTo(90.0, 1e-9)); expect(reading.hdop, closeTo(1.2, 1e-9)); }); test('fixType 0 bedeutet kein Fix', () { final data = ByteData(18); final reading = parseMspRawGps(data.buffer.asUint8List()); expect(reading.hasFix, isFalse); }); }); group('parseMspAltitudeMeters', () { test('estAlt in cm wird zu Metern (auch negativ moeglich)', () { final data = ByteData(10); data.setInt32(0, 1234, Endian.little); // 12.34 m data.setInt16(4, -50, Endian.little); data.setInt32(6, 1200, Endian.little); expect( parseMspAltitudeMeters(data.buffer.asUint8List()), closeTo(12.34, 1e-9), ); }); test('negative Hoehe (unter Startpunkt)', () { final data = ByteData(10); data.setInt32(0, -250, Endian.little); // -2.5 m expect( parseMspAltitudeMeters(data.buffer.asUint8List()), closeTo(-2.5, 1e-9), ); }); }); group('parseMspAltitudeVerticalSpeedMs', () { test('vario in cm/s wird zu m/s, positiv = Steigen', () { final data = ByteData(10); data.setInt16(4, 250, Endian.little); // 2.5 m/s Steigen expect( parseMspAltitudeVerticalSpeedMs(data.buffer.asUint8List()), closeTo(2.5, 1e-9), ); }); test('negativer vario-Wert bedeutet Sinken', () { final data = ByteData(10); data.setInt16(4, -180, Endian.little); // -1.8 m/s Sinken expect( parseMspAltitudeVerticalSpeedMs(data.buffer.asUint8List()), closeTo(-1.8, 1e-9), ); }); }); group('parseMspInavStatusSensorStatus', () { Uint8List statusPayload(int sensorStatus) { final data = ByteData(22); data.setUint16(4, sensorStatus, Endian.little); return data.buffer.asUint8List(); } test('liest das rohe sensorStatus-Bitfeld an Offset 4', () { expect( parseMspInavStatusSensorStatus( statusPayload(MspSensorStatusBits.acc | MspSensorStatusBits.gps), ), MspSensorStatusBits.acc | MspSensorStatusBits.gps, ); }); test('Hardware-Defekt-Bit (1<<15) wird mit uebertragen', () { final bits = parseMspInavStatusSensorStatus( statusPayload(MspSensorStatusBits.hardwareFailure), ); expect(bits & MspSensorStatusBits.hardwareFailure, isNot(0)); }); test('keine Sensoren gemeldet', () { expect(parseMspInavStatusSensorStatus(statusPayload(0)), 0); }); }); group('parseMspInavTemperaturesC', () { test('liest die ersten 3 von bis zu 8 Slots in 0.1°C-Schritten', () { final data = ByteData(16); // 8 x i16 data.setInt16(0, 285, Endian.little); // 28.5°C data.setInt16(2, 320, Endian.little); // 32.0°C data.setInt16(4, -1000, Endian.little); // ungueltig data.setInt16(6, 999, Endian.little); // 4. Slot wird ignoriert final temps = parseMspInavTemperaturesC(data.buffer.asUint8List()); expect(temps, hasLength(3)); expect(temps[0], closeTo(28.5, 1e-9)); expect(temps[1], closeTo(32.0, 1e-9)); expect(temps[2], isNull); }); test('Werte unterhalb des Sentinels gelten ebenfalls als ungueltig', () { final data = ByteData(16); data.setInt16(0, -1250, Endian.little); final temps = parseMspInavTemperaturesC(data.buffer.asUint8List()); expect(temps[0], isNull); }); }); group('parseMspInavStatusArmed', () { Uint8List statusPayload(int armingFlags) { final data = ByteData(22); data.setUint32(9, armingFlags, Endian.little); return data.buffer.asUint8List(); } test('ARMED-Bit (1<<2) gesetzt', () { expect(parseMspInavStatusArmed(statusPayload(1 << 2)), isTrue); }); test('nur andere Bits gesetzt, ARMED-Bit fehlt', () { expect(parseMspInavStatusArmed(statusPayload(1 << 3)), isFalse); }); test('keine Flags gesetzt', () { expect(parseMspInavStatusArmed(statusPayload(0)), isFalse); }); }); group('parseMspNavStatusActiveWaypoint', () { Uint8List navStatusPayload(int activeWpNumber) { return Uint8List.fromList([0, 0, 0, activeWpNumber, 0, 0, 0]); } test('activeWpNumber 0 bedeutet keine aktive Mission', () { expect(parseMspNavStatusActiveWaypoint(navStatusPayload(0)), isNull); }); test('activeWpNumber > 0 wird zu einem 0-basierten Index umgerechnet', () { expect(parseMspNavStatusActiveWaypoint(navStatusPayload(5)), 4); }); test('activeWpNumber 1 (erster Wegpunkt aktiv) wird zu Index 0', () { expect(parseMspNavStatusActiveWaypoint(navStatusPayload(1)), 0); }); }); group('parseMspNavMode/MspNavMode.formatted', () { Uint8List navStatusPayload(int mode, int state) { return Uint8List.fromList([mode, state, 0, 0, 0, 0, 0]); } test('mode 0 (None) ohne state', () { final nav = parseMspNavMode(navStatusPayload(0, 0)); expect(nav.mode, 0); expect(nav.state, 0); expect(nav.formatted, 'Idle'); }); test('mode 3 (Nav/Waypoint) + state 5 (WP_ENROUTE)', () { final nav = parseMspNavMode(navStatusPayload(3, 5)); expect(nav.formatted, 'Waypoint mission (en route to WP)'); }); test('mode 2 (RTH) + state 15 (RTH_CLIMB)', () { final nav = parseMspNavMode(navStatusPayload(2, 15)); expect(nav.formatted, 'Return to home (climbing)'); }); test('unbekannter mode faellt auf Nummer zurueck', () { final nav = parseMspNavMode(navStatusPayload(7, 0)); expect(nav.formatted, 'Unknown (7)'); }); }); group('parseMspInavAnalogBatteryPercent', () { test('liest Batterie-Prozent an Offset 21', () { final payload = Uint8List(24); payload[21] = 42; expect(parseMspInavAnalogBatteryPercent(payload), 42); }); }); group('parseMspInavAnalogVoltage', () { test('liest Akkuspannung an Offset 1 (u16, 0.01V-Schritte)', () { final data = ByteData(24); data.setUint16(1, 2220, Endian.little); // 22.20 V expect( parseMspInavAnalogVoltage(data.buffer.asUint8List()), closeTo(22.2, 1e-9), ); }); }); group('parseMspInavAnalogAmperage', () { test('liest positive Stromaufnahme an Offset 3 (i16, 0.01A-Schritte)', () { final data = ByteData(24); data.setInt16(3, 1250, Endian.little); // 12.50 A expect( parseMspInavAnalogAmperage(data.buffer.asUint8List()), closeTo(12.5, 1e-9), ); }); test('liest negative Stromaufnahme (vorzeichenbehaftet)', () { final data = ByteData(24); data.setInt16(3, -50, Endian.little); // -0.50 A expect( parseMspInavAnalogAmperage(data.buffer.asUint8List()), closeTo(-0.5, 1e-9), ); }); }); group('parseMspLinkQuality', () { test('liest Linkqualitaet an Offset 1', () { final payload = Uint8List(3); payload[1] = 87; expect(parseMspLinkQuality(payload), 87); }); }); group('parseMspLinkStatsSnr', () { test('liest positives SNR an Offset 2', () { final payload = Uint8List(3); payload[2] = 12; expect(parseMspLinkStatsSnr(payload), 12); }); test('liest negatives SNR (vorzeichenbehaftetes Byte) an Offset 2', () { // -5 dB als uint8 geschrieben (fc_msp.c: (uint8_t)rxLinkStatistics. // uplinkSNR) entspricht 251 (256-5). final payload = Uint8List(3); payload[2] = 251; expect(parseMspLinkStatsSnr(payload), -5); }); }); }