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 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 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 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); }