live mission details in footer implemented

This commit is contained in:
Constantin Leue
2026-08-04 09:48:01 +02:00
parent 35351140ae
commit a66c199b37
17 changed files with 295 additions and 17 deletions
@@ -69,6 +69,12 @@ class TelemetryFrame {
/// mission (en route)", "Return to home (climbing)", "Idle") - siehe
/// MspNavMode.formatted in msp_telemetry_codec.dart.
final String flightMode;
/// 0-basierter Index des gerade angeflogenen Wegpunkts in der
/// App-eigenen Wegpunktliste (z.B. `currentMissionProvider`), oder null,
/// wenn keine Mission aktiv ist. Bei MSP bereits aus dem 1-basierten
/// `activeWpNumber` umgerechnet (siehe parseMspNavStatusActiveWaypoint in
/// msp_telemetry_codec.dart).
final int? activeWaypointIndex;
}
@@ -1,11 +1,31 @@
import 'dart:async';
import 'dart:math' as math;
import '../../domain/waypoint/flat_waypoint_list.dart';
import '../flight_controller_link.dart';
/// Synthetische Telemetrie fuer UI-Entwicklung/Tests ohne Hardware oder SITL
/// (Architektur-Doku 3.1).
///
/// Verankert am ersten Wegpunkt der uebergebenen Mission (Doku: "solange die
/// Drohne nicht verbunden ist, Mock-Daten fuer die neue Live-Details-
/// Ansicht nutzbar machen") statt an einem fest verdrahteten Ort (frueher
/// Berlin) - dadurch liegt die simulierte Position tatsaechlich auf der
/// gerade bearbeiteten Route, und activeWaypointIndex ist von Anfang an
/// gesetzt (0, "erster Wegpunkt aktiv"), auch ohne den noch nicht
/// umgesetzten Missions-Upload-Flow. Fehlt eine Mission (keine Wegpunkte),
/// bleibt der alte Berlin-Fallback bestehen, damit die Klasse auch ganz
/// ohne Mission nutzbar ist.
class MockFlightControllerLink implements FlightControllerLink {
MockFlightControllerLink({List<Waypoint> initialWaypoints = const []})
: _anchorLat = initialWaypoints.isEmpty ? 52.5 : initialWaypoints.first.lat,
_anchorLon = initialWaypoints.isEmpty ? 13.4 : initialWaypoints.first.lon,
_activeWaypointIndex = initialWaypoints.isEmpty ? null : 0;
final double _anchorLat;
final double _anchorLon;
final _random = math.Random();
bool _armed = false;
int? _activeWaypointIndex;
StreamController<TelemetryFrame>? _telemetryController;
@@ -13,6 +33,16 @@ class MockFlightControllerLink implements FlightControllerLink {
double _heading = 0;
double _batteryPercent = 100;
/// Aktuelle Drift vom Ankerpunkt in Metern (Nord/Ost) - Zufalls-Trudeln
/// innerhalb eines 10-m-Radius statt eines fest stehenden Punkts, damit
/// auch die Distanz/ETA-Anzeige der neuen Details-Pille sichtbar auf
/// Werteaenderungen reagiert (Doku 9: UI-Entwicklung ohne Hardware).
double _driftNorthM = 0;
double _driftEastM = 0;
static const _driftRadiusM = 10.0;
static const _driftStepM = 1.5;
static const _metersPerDegreeLat = 111320.0;
@override
FcCapabilities get capabilities => const FcCapabilities(
supportsMultiMission: true,
@@ -37,9 +67,27 @@ class MockFlightControllerLink implements FlightControllerLink {
// Langsam sinkender Akkustand, damit die Anzeige auch ohne Hardware
// sichtbar auf Werteaenderungen reagiert (Doku 9).
_batteryPercent = (_batteryPercent - 0.1).clamp(0, 100);
// Zufallsschritt, danach auf den 10-m-Radius um den Ankerpunkt
// zurueckgeklemmt statt frei wegzulaufen.
_driftNorthM += (_random.nextDouble() - 0.5) * 2 * _driftStepM;
_driftEastM += (_random.nextDouble() - 0.5) * 2 * _driftStepM;
final driftMagnitude = math.sqrt(
_driftNorthM * _driftNorthM + _driftEastM * _driftEastM,
);
if (driftMagnitude > _driftRadiusM) {
final scale = _driftRadiusM / driftMagnitude;
_driftNorthM *= scale;
_driftEastM *= scale;
}
final metersPerDegreeLon =
_metersPerDegreeLat * math.cos(_anchorLat * math.pi / 180);
final lat = _anchorLat + _driftNorthM / _metersPerDegreeLat;
final lon = _anchorLon + _driftEastM / metersPerDegreeLon;
_telemetryController?.add(TelemetryFrame(
lat: 52.5,
lon: 13.4,
lat: lat,
lon: lon,
hasFix: true,
numSat: 12,
altitudeM: 120,
+15 -4
View File
@@ -57,12 +57,23 @@ bool parseMspInavStatusArmed(Uint8List payload) {
return (armingFlags & MspArmingFlags.armed) != 0;
}
/// Parst die `MSP_NAV_STATUS`-Antwort und liefert den aktiven
/// Wegpunktindex, oder null, wenn keine Mission aktiv ist (`activeWpNumber
/// == 0`, siehe [MspCommands.navStatus]).
/// 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;
return activeWpNumber == 0 ? null : activeWpNumber - 1;
}
/// Parst `mode`/`state` aus der `MSP_NAV_STATUS`-Antwort (Byte 0/1, siehe