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.
139 lines
5.3 KiB
Dart
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);
|
|
}
|