Files
dmc/app/lib/transport/msp/msp_telemetry_codec.dart
T

153 lines
5.9 KiB
Dart

import 'dart:typed_data';
import 'msp_commands.dart';
/// GPS-Rohwerte aus `MSP_RAW_GPS` (Doku Kommunikationsschicht Abschnitt 4).
class MspGpsReading {
const MspGpsReading({
required this.fixType,
required this.numSat,
required this.lat,
required this.lon,
required this.speedMs,
required this.headingDeg,
required this.hdop,
});
/// 0 = kein Fix (`fc_msp.c`: `gpsSol.fixType`).
final int fixType;
final int numSat;
final double lat;
final double lon;
final double speedMs;
/// Kurs ueber Grund in Grad (0 = Norden, im Uhrzeigersinn), aus
/// `groundCourse` - nur bei [hasFix] aussagekraeftig.
final double headingDeg;
/// Horizontale Streuung der Positionsschaetzung (kleiner = besser, ~1.0
/// oder darunter ist gut) - `gpsSol.hdop / HDOP_SCALE` (`gps.h`:
/// `#define HDOP_SCALE (100)`). Ohne Fix meldet iNAV hier 9999/100 =
/// 99.99 (`gps.c`: `gpsSol.hdop = 9999`), also einen sehr grossen statt
/// eines fehlenden Werts.
final double hdop;
bool get hasFix => fixType != 0;
}
/// Parst die `MSP_RAW_GPS`-Antwort (Byte-Layout siehe [MspCommands.rawGps]).
MspGpsReading parseMspRawGps(Uint8List payload) {
final data = ByteData.sublistView(payload);
return MspGpsReading(
fixType: data.getUint8(0),
numSat: data.getUint8(1),
lat: data.getInt32(2, Endian.little) / 1e7,
lon: data.getInt32(6, Endian.little) / 1e7,
speedMs: data.getUint16(12, Endian.little) / 100.0,
headingDeg: data.getUint16(14, Endian.little) / 10.0,
hdop: data.getUint16(16, Endian.little) / 100.0,
);
}
/// Parst die `MSP_ALTITUDE`-Antwort und liefert die geschaetzte Hoehe in
/// Metern (siehe [MspCommands.altitude]).
double parseMspAltitudeMeters(Uint8List payload) {
final data = ByteData.sublistView(payload);
return data.getInt32(0, Endian.little) / 100.0;
}
/// Parst die `MSP2_INAV_STATUS`-Antwort und liefert, ob der ARMED-Bit in
/// `armingFlags` gesetzt ist (siehe [MspCommands.inavStatus]).
bool parseMspInavStatusArmed(Uint8List payload) {
final data = ByteData.sublistView(payload);
// Offset 9 = cycleTime(2) + i2cErrors(2) + sensorStatus(2) + avgLoad(2)
// + Profil-Byte(1), siehe fc_msp.c/MSP2_INAV_STATUS.
final armingFlags = data.getUint32(9, Endian.little);
return (armingFlags & MspArmingFlags.armed) != 0;
}
/// Parst die `MSP_NAV_STATUS`-Antwort und liefert den aktiven Wegpunkt als
/// 0-basierten Index in die App-eigene Wegpunktliste, oder null, wenn keine
/// Mission aktiv ist (`activeWpNumber == 0`, siehe [MspCommands.navStatus]).
///
/// `activeWpNumber` selbst ist 1-basiert (`navigation.c`: `NAV_Status.
/// activeWpIndex = posControl.activeWaypointIndex - posControl.startWpIndex;
/// NAV_Status.activeWpNumber = NAV_Status.activeWpIndex + 1;`) - bei einem
/// einzelnen, komplett hochgeladenen Missions-Upload (kein Multi-Mission-
/// Jump, `startWpIndex` = 0) entspricht `activeWpNumber - 1` also exakt dem
/// 0-basierten Index in [Waypoint]-Listen wie `currentMissionProvider`.
/// Frueher wurde hier der Rohwert unveraendert durchgereicht, was beim
/// Indizieren in die Wegpunktliste zu einem Off-by-One fuehrte (durch
/// Zufall unauffaellig, weil [MockFlightControllerLink] von Anfang an
/// 0-basiert gezaehlt hat).
int? parseMspNavStatusActiveWaypoint(Uint8List payload) {
final activeWpNumber = payload[3];
return activeWpNumber == 0 ? null : activeWpNumber - 1;
}
/// Parst `mode`/`state` aus der `MSP_NAV_STATUS`-Antwort (Byte 0/1, siehe
/// [MspCommands.navStatus]) - Rohwerte aus `navSystemStatus_Mode_e`/
/// `navSystemStatus_State_e` (`navigation.h`, iNAV 9.1.0). [formatted]
/// bildet daraus eine fuer die UI lesbare Kurzbeschreibung (Doku: "drone
/// flight mode" im Settings-Telemetriepanel).
class MspNavMode {
const MspNavMode({required this.mode, required this.state});
final int mode;
final int state;
/// `MW_GPS_MODE_*`-Namen (0=None, 1=Hold, 2=RTH, 3=Nav/Waypoint,
/// 15=Emergency) - mode 0 heisst nur "keine GPS-Nav-Uebersteuerung aktiv",
/// nicht "Manual/Acro": die reine Fluglage (Angle/Horizon/Acro) ist ueber
/// MSP_NAV_STATUS nicht sichtbar (dafuer waere MSP_BOXNAMES+boxModeFlags
/// noetig, hier bewusst nicht umgesetzt).
static const _modeNames = {0: 'Idle', 1: 'Position hold', 2: 'Return to home', 3: 'Waypoint mission', 15: 'Emergency'};
/// `MW_NAV_STATE_*`-Namen, nur die fuer die Kurzanzeige relevanten.
static const _stateNames = {
1: 'starting',
2: 'en route',
3: 'holding',
4: 'holding (timed)',
5: 'en route to WP',
6: 'next waypoint',
7: 'jump',
8: 'landing',
9: 'landing',
10: 'landed',
11: 'landing',
12: 'descending',
13: 'hover above home',
14: 'emergency landing',
15: 'climbing',
};
String get formatted {
final modeLabel = _modeNames[mode] ?? 'Unknown ($mode)';
final stateLabel = _stateNames[state];
return stateLabel == null ? modeLabel : '$modeLabel ($stateLabel)';
}
}
MspNavMode parseMspNavMode(Uint8List payload) {
return MspNavMode(mode: payload[0], state: payload[1]);
}
/// Parst die `MSP2_INAV_ANALOG`-Antwort und liefert den Akku-Ladestand in
/// Prozent (Offset 21, `calculateBatteryPercentage()` - bereits fertig auf
/// 0-100 berechnet, siehe [MspCommands.inavAnalog]).
int parseMspInavAnalogBatteryPercent(Uint8List payload) => payload[21];
/// Parst die `MSP2_INAV_GET_LINK_STATS`-Antwort und liefert die
/// Empfaenger-Linkqualitaet in Prozent (Offset 1, `rxLinkStatistics.uplinkLQ`
/// - bereits 0-100, siehe [MspCommands.inavLinkStats]).
int parseMspLinkQuality(Uint8List payload) => payload[1];
/// Parst die `MSP2_INAV_GET_LINK_STATS`-Antwort und liefert das
/// Signal-Rausch-Verhaeltnis des Uplinks in dB (Offset 2,
/// `rxLinkStatistics.uplinkSNR` - `int8_t`, `fc_msp.c` schreibt
/// `(uint8_t)rxLinkStatistics.uplinkSNR`, das Byte muss also vorzeichen-
/// behaftet zurueckgelesen werden, siehe [MspCommands.inavLinkStats]).
int parseMspLinkStatsSnr(Uint8List payload) =>
ByteData.sublistView(payload).getInt8(2);