Files

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);