102 lines
3.4 KiB
Dart
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),
|
|
);
|
|
}
|
|
}
|
|
}
|