Files
dmc/app/lib/domain/mission/mission_stats.dart
T
Constantin Leue afc380cabc Point the fly-mode rubber band at home during Return-to-Home
iNAV freezes activeWaypointIndex at whatever mission waypoint was
active when RTH engaged instead of updating it to reflect the new
target - navigation.c derives NAV_Status.activeWpIndex unconditionally
from posControl.activeWaypointIndex, and none of the RTH state-entry
handlers touch that field. Drawing the guidance line to that stale
index would point at a waypoint the aircraft may have already passed.

Extracted the target selection into computeRubberBandTarget() (mission
stats.dart) so the RTH special-case and the abort-back-to-waypoint-mode
fallback are unit-tested rather than only living inline in the widget
build method - since it re-evaluates navMode on every frame, aborting
RTH switches the line back to the mission waypoint with no extra
transition logic needed.
2026-08-09 21:31:00 +02:00

139 lines
5.3 KiB
Dart

import 'package:latlong2/latlong.dart';
import '../waypoint/flat_waypoint_list.dart';
const _distance = Distance(roundResult: false);
/// Gesamtdistanz/-dauer einer Mission (HTML-Demonstrator: computeStats()).
class MissionStats {
const MissionStats({required this.distanceM, required this.durationSec});
final double distanceM;
final double durationSec;
}
/// Geteilt zwischen Plan- und Fly-Modus sowie der Warnungsberechnung (Doku
/// 3.9/3.10), die dieselben Werte fuer Zeit-/Reichweiten-Warnungen braucht.
MissionStats computeMissionStats(List<Waypoint> waypoints) {
var totalDist = 0.0;
var totalDur = 0.0;
for (var i = 0; i < waypoints.length - 1; i++) {
final a = LatLng(waypoints[i].lat, waypoints[i].lon);
final b = LatLng(waypoints[i + 1].lat, waypoints[i + 1].lon);
final d = _distance(a, b);
totalDist += d;
totalDur += d / waypoints[i + 1].speedMs;
}
return MissionStats(distanceM: totalDist, durationSec: totalDur);
}
/// Live-Missionsstatus im Fly-Modus fuer die Details-Pille (Doku: "aktiver
/// Wegpunkt (z.B. 3/23), Distanz und Zeit zum naechsten Wegpunkt" statt der
/// statischen Plan-Werte). MSP liefert das nicht direkt - `MSP_COMP_GPS`
/// gibt nur Distanz/Richtung zum HOME-Punkt (`GPS_distanceToHome`/
/// `GPS_directionToHome`, iNAV `fc_msp.c`), und obwohl iNAV intern fuer sein
/// eigenes OSD bereits eine Distanz zum aktiven Wegpunkt berechnet
/// (`posControl.wpDistance`/`posControl.activeWaypointIndex`/
/// `posControl.geoWaypointCount` in `navigation.c`/`osd.c`, Format dort
/// exakt "TO WP %u/%u (%s)"), ist das ueber keine MSP-Nachricht abrufbar -
/// deshalb hier aus der aktuellen Position + den ohnehin bekannten
/// Wegpunktkoordinaten selbst berechnet.
class FlyMissionStatus {
const FlyMissionStatus({
required this.activeWaypointNumber,
required this.waypointCount,
this.distanceToActiveWaypointM,
this.etaToActiveWaypointSec,
});
/// 1-basiert fuer die Anzeige ("3/23"), waehrend der uebergebene
/// activeWaypointIndex-Parameter (TelemetryFrame.activeWaypointIndex)
/// intern 0-basiert ist.
final int activeWaypointNumber;
final int waypointCount;
/// Null, solange keine gueltige Position bekannt ist (kein GPS-Fix).
final double? distanceToActiveWaypointM;
/// Null, solange keine Distanz oder keine hinreichende Geschwindigkeit
/// bekannt ist (Division durch (nahe) Null wuerde sonst eine bedeutungs-
/// lose Restzeit vortaeuschen).
final double? etaToActiveWaypointSec;
}
/// Vergleiche Klassendoku [FlyMissionStatus]. Liefert null, solange keine
/// Mission aktiv ist oder [activeWaypointIndex] (0-basiert) ausserhalb der
/// Wegpunktliste liegt (z.B. kurz nach Missionsende).
FlyMissionStatus? computeFlyMissionStatus({
required List<Waypoint> waypoints,
required int? activeWaypointIndex,
double? currentLat,
double? currentLon,
double? currentSpeedMs,
}) {
if (activeWaypointIndex == null ||
activeWaypointIndex < 0 ||
activeWaypointIndex >= waypoints.length) {
return null;
}
final target = waypoints[activeWaypointIndex];
double? distance;
if (currentLat != null && currentLon != null) {
distance = _distance(LatLng(currentLat, currentLon), LatLng(target.lat, target.lon));
}
double? eta;
// Unter 0.5 m/s waere eine aus der Distanz hochgerechnete Restzeit nur
// Sekunden bis Minuten vor einer erfundenen "fast unendlich" Anzeige -
// dann lieber "-" zeigen (siehe UI) statt eines irrefuehrenden Werts.
if (distance != null && currentSpeedMs != null && currentSpeedMs > 0.5) {
eta = distance / currentSpeedMs;
}
return FlyMissionStatus(
activeWaypointNumber: activeWaypointIndex + 1,
waypointCount: waypoints.length,
distanceToActiveWaypointM: distance,
etaToActiveWaypointSec: eta,
);
}
/// `MW_GPS_MODE_RTH` (siehe MspNavMode._modeNames in msp_telemetry_codec.dart).
const navModeReturnToHome = 2;
/// Ziel des Gummiband-Pfeils von der Drohne zum aktuellen Flugziel im
/// Fly-Modus (Doku: MissionMap.rubberBandLine) - normalerweise der aktive
/// Missionswegpunkt, waehrend Return-to-Home ([navModeReturnToHome])
/// stattdessen [homePoint].
///
/// iNAV friert `activeWaypointIndex` beim RTH-Eintritt auf dem zuletzt
/// aktiven Missionswegpunkt ein, statt ihn auf den Homepoint zu setzen
/// (`navigation.c`: `NAV_Status.activeWpIndex` wird unbedingt aus
/// `posControl.activeWaypointIndex` abgeleitet, keiner der
/// `navOnEnteringState_NAV_STATE_RTH_*`-Handler fasst diesen Wert an) - ein
/// Gummiband zu diesem (ggf. laengst passierten) Wegpunkt waere im RTH-Fall
/// irrefuehrend. Da [navMode] bei jedem neuen Telemetrie-Frame neu geprueft
/// wird, schaltet ein Abbruch von RTH zurueck in den Wegpunktmodus das
/// Ergebnis von selbst wieder auf den Missionswegpunkt zurueck.
///
/// Liefert null, wenn kein sinnvolles Ziel bekannt ist (RTH ohne bereits
/// abgefragten Homepoint, oder [activeWaypointIndex] ausserhalb der Liste).
LatLng? computeRubberBandTarget({
required int navMode,
required int? activeWaypointIndex,
required List<Waypoint> waypoints,
LatLng? homePoint,
}) {
if (navMode == navModeReturnToHome) {
return homePoint;
}
if (activeWaypointIndex == null ||
activeWaypointIndex < 0 ||
activeWaypointIndex >= waypoints.length) {
return null;
}
final target = waypoints[activeWaypointIndex];
return LatLng(target.lat, target.lon);
}