113 lines
3.5 KiB
Dart
113 lines
3.5 KiB
Dart
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);
|