Files
dmc/app/lib/transport/msp/msp_frame_decoder.dart
T

102 lines
3.4 KiB
Dart

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<int> _headerBytes = [];
int _function = 0;
int _payloadSize = 0;
final List<int> _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<MspFrame> addBytes(Uint8List chunk) {
final frames = <MspFrame>[];
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),
);
}
}
}