import 'dart:typed_data'; import 'msp_frame.dart'; enum _State { idle, waitingForX, waitingForDirection, header, payload, checksum } /// Streaming-Dekodierer fuer MSPv2-Rahmen (Doku Kommunikationsschicht /// Abschnitt 4): der Byte-Strom von der Gegenstelle kommt fragmentiert an /// (RFCOMM liefert kein "ein Paket pro Lesevorgang"), also ein /// Zustandsautomat statt eines naiven Parsers - ein Byte pro Aufruf, egal /// wie [addBytes] geschnitten wird. Zustaende gespiegelt aus iNAVs /// `msp_serial.c` (`mspState_e`), reduziert auf den MSPv2-Native-Pfad, da /// die Gegenstelle (mLRS-Backpack) ausschliesslich MSPv2 spricht - iNAV /// benoetigt MSPv2, MSPv1 reicht laut Doku nicht. /// /// Fehlerhafte Rahmen (CRC-Mismatch, unbekanntes Richtungszeichen, zu grosse /// gemeldete Payload-Groesse) werden stillschweigend verworfen und der /// Automat kehrt in den Idle-Zustand zurueck, ohne den weiteren Strom zu /// verlieren (Abnahmekriterium: "Fehlerhafte Rahmen werden verworfen, ohne /// den Strom zu verlieren"). class MspFrameDecoder { /// Schutz vor unbegrenztem Speicherwachstum, falls eine als Muell /// interpretierte Payload-Groesse absurd gross waere - kein reales /// MSP-Kommando braucht mehr. static const int maxPayloadSize = 4096; _State _state = _State.idle; MspDirection _direction = MspDirection.response; final List _headerBytes = []; int _function = 0; int _payloadSize = 0; final List _payloadBytes = []; int _crc = 0; /// Verarbeitet [chunk] und liefert alle Rahmen, die dadurch vollstaendig /// wurden (0, 1 oder mehrere, je nachdem wie der Strom geschnitten ist). List addBytes(Uint8List chunk) { final frames = []; for (final byte in chunk) { final frame = _consume(byte); if (frame != null) frames.add(frame); } return frames; } MspFrame? _consume(int byte) { switch (_state) { case _State.idle: if (byte == mspDollarByte) _state = _State.waitingForX; return null; case _State.waitingForX: _state = byte == mspXByte ? _State.waitingForDirection : _State.idle; return null; case _State.waitingForDirection: final direction = mspDirectionForByte(byte); if (direction == null) { _state = _State.idle; return null; } _direction = direction; _headerBytes.clear(); _crc = 0; _state = _State.header; return null; case _State.header: _headerBytes.add(byte); _crc = crc8DvbS2(_crc, byte); if (_headerBytes.length == 5) { _function = _headerBytes[1] | (_headerBytes[2] << 8); _payloadSize = _headerBytes[3] | (_headerBytes[4] << 8); if (_payloadSize > maxPayloadSize) { _state = _State.idle; return null; } _payloadBytes.clear(); _state = _payloadSize == 0 ? _State.checksum : _State.payload; } return null; case _State.payload: _payloadBytes.add(byte); _crc = crc8DvbS2(_crc, byte); if (_payloadBytes.length == _payloadSize) { _state = _State.checksum; } return null; case _State.checksum: _state = _State.idle; if (byte != _crc) return null; return MspFrame( direction: _direction, function: _function, payload: Uint8List.fromList(_payloadBytes), ); } } }