MSP protocol implementation and settings menu
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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:1> <flags:1> <function:2 LE> <payloadSize:2 LE> <payload> <crc:1>
|
||||
/// ```
|
||||
///
|
||||
/// `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<int> 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);
|
||||
Reference in New Issue
Block a user