167 lines
6.7 KiB
Dart
167 lines
6.7 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_ANALOG`-Antwort und liefert die Akkuspannung in Volt
|
|
/// (Offset 1, u16 in 0.01V-Schritten - `battery.c`: `static uint16_t vbat`,
|
|
/// Kommentar "battery voltage in 0.01V steps", siehe [MspCommands.inavAnalog]).
|
|
double parseMspInavAnalogVoltage(Uint8List payload) =>
|
|
ByteData.sublistView(payload).getUint16(1, Endian.little) / 100.0;
|
|
|
|
/// Parst die `MSP2_INAV_ANALOG`-Antwort und liefert die aktuelle
|
|
/// Stromaufnahme in Ampere (Offset 3, `fc_msp.c`: `sbufWriteU16(dst,
|
|
/// getAmperage())` - "send amperage in 0.01 A steps", `getAmperage()` selbst
|
|
/// liefert `int16_t`, deshalb vorzeichenbehaftet gelesen, siehe
|
|
/// [MspCommands.inavAnalog]).
|
|
double parseMspInavAnalogAmperage(Uint8List payload) =>
|
|
ByteData.sublistView(payload).getInt16(3, Endian.little) / 100.0;
|
|
|
|
/// 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);
|