MSP protocol implementation and settings menu
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user