import 'dart:typed_data'; /// MSPv2-Rahmenformat (Doku Kommunikationsschicht Abschnitt 4), geprueft /// gegen den tatsaechlichen iNAV-9.1.0-Quellcode statt aus dem Gedaechtnis /// (`src/main/msp/msp_serial.c`, Zustaende `MSP_HEADER_X`/`MSP_HEADER_V2_NATIVE` /// sowie `mspSerialEncode()`): /// /// ``` /// '$' 'X' /// ``` /// /// `dir` ist '<' (0x3C) fuer Anfragen, '>' (0x3E) fuer Antworten, '!' (0x21) /// fuer Fehlerantworten. Die CRC ist CRC8 DVB-S2 (Poly 0xD5, Startwert 0), /// berechnet ueber `flags`..`payload` - NICHT ueber '$'/'X'/dir. enum MspDirection { request, response, error } const int mspDollarByte = 0x24; // '$' const int mspXByte = 0x58; // 'X' int? mspDirectionByte(MspDirection direction) => switch (direction) { MspDirection.request => 0x3C, // '<' MspDirection.response => 0x3E, // '>' MspDirection.error => 0x21, // '!' }; MspDirection? mspDirectionForByte(int byte) => switch (byte) { 0x3C => MspDirection.request, 0x3E => MspDirection.response, 0x21 => MspDirection.error, _ => null, }; /// Ein vollstaendig dekodierter (und CRC-gepruefter) MSPv2-Rahmen. class MspFrame { const MspFrame({ required this.direction, required this.function, required this.payload, }); final MspDirection direction; final int function; final Uint8List payload; @override String toString() => 'MspFrame($direction, fn=$function, ${payload.length} bytes)'; } /// CRC8 DVB-S2 (Poly 0xD5, kein Reflect, kein Final-XOR) - 1:1 aus /// `src/main/common/crc.c` (`crc8_dvb_s2`) uebernommen. int crc8DvbS2(int crc, int byte) { var c = (crc ^ byte) & 0xFF; for (var i = 0; i < 8; i++) { c = (c & 0x80) != 0 ? ((c << 1) ^ 0xD5) & 0xFF : (c << 1) & 0xFF; } return c; } int crc8DvbS2Update(int crc, Iterable bytes) { var c = crc; for (final b in bytes) { c = crc8DvbS2(c, b); } return c; } /// Kodiert einen vollstaendigen MSPv2-Rahmen fuer [direction]/[function] mit /// optionalem [payload]. `flags` ist im MVP immer 0. /// /// Produktionscode braucht nur [encodeMspV2Request] (die App sendet /// ausschliesslich Anfragen); der allgemeine Encoder ist zusaetzlich /// oeffentlich, damit Tests damit Antwort-/Fehlerrahmen der Gegenstelle /// nachbilden koennen, ohne den Rahmenaufbau ein zweites Mal zu duplizieren. Uint8List encodeMspV2Frame( MspDirection direction, int function, [ Uint8List? payload, ]) { final body = payload ?? Uint8List(0); if (body.length > 0xFFFF) { throw ArgumentError.value( body.length, 'payload.length', 'MSPv2-Payload darf 65535 Byte nicht ueberschreiten', ); } final header = Uint8List(5); final headerView = ByteData.sublistView(header); headerView.setUint8(0, 0); // flags headerView.setUint16(1, function, Endian.little); headerView.setUint16(3, body.length, Endian.little); var crc = crc8DvbS2Update(0, header); crc = crc8DvbS2Update(crc, body); final frame = BytesBuilder(); frame.addByte(mspDollarByte); frame.addByte(mspXByte); frame.addByte(mspDirectionByte(direction)!); frame.add(header); frame.add(body); frame.addByte(crc); return frame.toBytes(); } /// Kodiert eine MSPv2-Anfrage (`dir` = '<') fuer [function] mit optionalem /// [payload]. `flags` ist im MVP immer 0 - iNAV nutzt das Feld aktuell nicht /// fuer Client-Anfragen. Uint8List encodeMspV2Request(int function, [Uint8List? payload]) => encodeMspV2Frame(MspDirection.request, function, payload);