diff --git a/.~lock.Design Journal Drone Mission Control.odp# b/.~lock.Design Journal Drone Mission Control.odp# new file mode 100644 index 0000000..7eee7fd --- /dev/null +++ b/.~lock.Design Journal Drone Mission Control.odp# @@ -0,0 +1 @@ +,AI/Consti,AI,04.08.2026 09:42,file:///C:/Users/Consti/AppData/Roaming/LibreOffice/4; \ No newline at end of file diff --git a/design/DMC logo sketches.odp b/Design Journal Drone Mission Control.odp similarity index 52% rename from design/DMC logo sketches.odp rename to Design Journal Drone Mission Control.odp index 64ffe1b..fa0f772 100644 Binary files a/design/DMC logo sketches.odp and b/Design Journal Drone Mission Control.odp differ diff --git a/app/lib/domain/mission/mission_stats.dart b/app/lib/domain/mission/mission_stats.dart index 7f8c18b..e9a3d45 100644 --- a/app/lib/domain/mission/mission_stats.dart +++ b/app/lib/domain/mission/mission_stats.dart @@ -26,3 +26,75 @@ MissionStats computeMissionStats(List waypoints) { } 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, + ); +} diff --git a/app/lib/transport/flight_controller_link.dart b/app/lib/transport/flight_controller_link.dart index be6eb0e..2d8d664 100644 --- a/app/lib/transport/flight_controller_link.dart +++ b/app/lib/transport/flight_controller_link.dart @@ -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; } diff --git a/app/lib/transport/mock/mock_flight_controller_link.dart b/app/lib/transport/mock/mock_flight_controller_link.dart index a74b22f..0e85806 100644 --- a/app/lib/transport/mock/mock_flight_controller_link.dart +++ b/app/lib/transport/mock/mock_flight_controller_link.dart @@ -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 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? _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, diff --git a/app/lib/transport/msp/msp_telemetry_codec.dart b/app/lib/transport/msp/msp_telemetry_codec.dart index 1f67999..4767830 100644 --- a/app/lib/transport/msp/msp_telemetry_codec.dart +++ b/app/lib/transport/msp/msp_telemetry_codec.dart @@ -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 diff --git a/app/lib/ui/providers/telemetry_provider.dart b/app/lib/ui/providers/telemetry_provider.dart index 8cd9c74..e497d95 100644 --- a/app/lib/ui/providers/telemetry_provider.dart +++ b/app/lib/ui/providers/telemetry_provider.dart @@ -6,6 +6,7 @@ import '../../transport/link_transport.dart'; import '../../transport/mock/mock_flight_controller_link.dart'; import '../../transport/msp/msp_flight_controller_link.dart'; import 'connection_settings_provider.dart'; +import 'current_mission_provider.dart'; import 'wifi_connection_provider.dart'; /// FlightControllerLink fuer den Fly-Modus (Doku 3.1): bei WLAN als @@ -41,7 +42,12 @@ final flightControllerLinkProvider = Provider((ref) { final connectionType = ref.watch(connectionSettingsProvider).value?.connectionType; final FlightControllerLink link = connectionType == ConnectionType.wifi ? MspFlightControllerLink(transport: ref.watch(wifiTransportProvider)) - : MockFlightControllerLink(); + // ref.read() statt watch() - der Mock soll nur einmal bei seiner + // Erzeugung am ersten Wegpunkt der GERADE aktuellen Mission + // verankert werden (Doku: "Mock-Daten fuer die Live-Details-Ansicht + // nutzbar machen"), nicht bei jeder spaeteren Missionsaenderung + // neu erzeugt werden (das wuerde Timer/Drift/Akkustand zuruecksetzen). + : MockFlightControllerLink(initialWaypoints: ref.read(currentMissionProvider)); ref.onDispose(link.disconnect); return link; }); diff --git a/app/lib/ui/screens/fly/fly_screen.dart b/app/lib/ui/screens/fly/fly_screen.dart index a2e4f1a..123328a 100644 --- a/app/lib/ui/screens/fly/fly_screen.dart +++ b/app/lib/ui/screens/fly/fly_screen.dart @@ -226,6 +226,19 @@ class _FlyScreenState extends ConsumerState { ]; } + // Live-Missionsstatus fuer die Details-Pille (Doku: "aktiver Wegpunkt + // 3/23, Distanz und Zeit zum naechsten Wegpunkt" statt der statischen + // Plan-Werte) - siehe FlyMissionStatus-Klassendoku dazu, warum das aus + // Position+Wegpunktkoordinaten berechnet wird statt direkt von MSP + // abgefragt. + final flyMissionStatus = computeFlyMissionStatus( + waypoints: waypoints, + activeWaypointIndex: activeWpIndex, + currentLat: telemetry != null && telemetry.hasFix ? telemetry.lat : null, + currentLon: telemetry != null && telemetry.hasFix ? telemetry.lon : null, + currentSpeedMs: telemetry?.speedMs, + ); + return Stack( children: [ MissionMap( @@ -273,6 +286,7 @@ class _FlyScreenState extends ConsumerState { activeIndex: -1, totalDistanceM: missionStats.distanceM, totalDurationSec: missionStats.durationSec, + flyMissionStatus: flyMissionStatus, warnings: warnings, onDetailsTap: _openWaypointList, onWarningsTap: () => _openWarningsPanel(warnings), diff --git a/app/lib/ui/screens/settings/settings_screen.dart b/app/lib/ui/screens/settings/settings_screen.dart index a42ee93..b11101d 100644 --- a/app/lib/ui/screens/settings/settings_screen.dart +++ b/app/lib/ui/screens/settings/settings_screen.dart @@ -278,7 +278,9 @@ class _TelemetryFields extends StatelessWidget { _field('Link quality', '${t.linkQuality}%'), _field('Flight mode', t.flightMode), if (t.activeWaypointIndex != null) - _field('Active WP', '${t.activeWaypointIndex}'), + // +1: activeWaypointIndex ist 0-basiert (Array-Index), fuer + // Menschen zaehlt man Wegpunkte ab 1 (siehe TelemetryFrame-Doku). + _field('Active WP', '${t.activeWaypointIndex! + 1}'), ], ); } diff --git a/app/lib/ui/widgets/bottom_stats_bar.dart b/app/lib/ui/widgets/bottom_stats_bar.dart index 53171aa..ef899d0 100644 --- a/app/lib/ui/widgets/bottom_stats_bar.dart +++ b/app/lib/ui/widgets/bottom_stats_bar.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../domain/mission/mission_stats.dart'; import '../../domain/mission/mission_warnings.dart'; import '../providers/active_drone_profile_provider.dart'; import '../providers/altitude_profile_provider.dart'; @@ -25,6 +26,7 @@ class BottomStatsBar extends ConsumerWidget { required this.waypointCount, required this.totalDistanceM, required this.totalDurationSec, + this.flyMissionStatus, required this.onTap, required this.onSettingsTap, this.warnings = const [], @@ -34,6 +36,10 @@ class BottomStatsBar extends ConsumerWidget { final int waypointCount; final double totalDistanceM; final double totalDurationSec; + + /// Siehe MissionFooterBar-Doku - nur im Fly-Modus gesetzt, ersetzt dann + /// die statischen Felder in der Details-Pille. + final FlyMissionStatus? flyMissionStatus; final VoidCallback onTap; /// Oeffnet das Settings-Menue (runder Zahnrad-Knopf rechts der @@ -147,6 +153,26 @@ class BottomStatsBar extends ConsumerWidget { } Widget _detailsPill() { + final flyStatus = flyMissionStatus; + // Im Fly-Modus (flyStatus != null) ersetzen aktiver Wegpunkt sowie + // Distanz/Zeit dorthin die statischen Plan-Werte (Doku: "Live-Status + // der Mission in der Details-Pille, genau wie vorher aber mit aktivem + // Wegpunkt statt Gesamtzahl, Distanz/Zeit zum naechsten Wegpunkt statt + // Gesamtdistanz/-dauer"). + final waypointText = flyStatus != null + ? '${flyStatus.activeWaypointNumber}/${flyStatus.waypointCount}' + : '$waypointCount WP'; + final distanceText = flyStatus != null + ? (flyStatus.distanceToActiveWaypointM != null + ? '${flyStatus.distanceToActiveWaypointM!.round()} m' + : '—') + : '${totalDistanceM.round()} m'; + final durationText = flyStatus != null + ? (flyStatus.etaToActiveWaypointSec != null + ? _fmtDuration(flyStatus.etaToActiveWaypointSec!) + : '—') + : _fmtDuration(totalDurationSec); + return GestureDetector( onTap: onTap, child: Container( @@ -169,17 +195,17 @@ class BottomStatsBar extends ConsumerWidget { const SizedBox(width: 10), const Icon(Icons.flag, size: 14, color: Colors.white), const SizedBox(width: 4), - Text('$waypointCount WP', + Text(waypointText, style: const TextStyle(color: Colors.white, fontSize: 14)), const SizedBox(width: 14), const Icon(Icons.straighten, size: 14, color: Colors.white), const SizedBox(width: 4), - Text('${totalDistanceM.round()} m', + Text(distanceText, style: const TextStyle(color: Colors.white, fontSize: 14)), const SizedBox(width: 14), const Icon(Icons.access_time, size: 14, color: Colors.white), const SizedBox(width: 4), - Text(_fmtDuration(totalDurationSec), + Text(durationText, style: const TextStyle(color: Colors.white, fontSize: 14)), ], ), diff --git a/app/lib/ui/widgets/mission_footer_bar.dart b/app/lib/ui/widgets/mission_footer_bar.dart index d8c6ab2..caf9d4f 100644 --- a/app/lib/ui/widgets/mission_footer_bar.dart +++ b/app/lib/ui/widgets/mission_footer_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../domain/mission/mission_stats.dart'; import '../../domain/mission/mission_warnings.dart'; import '../../domain/waypoint/flat_waypoint_list.dart'; import '../providers/altitude_profile_provider.dart'; @@ -20,6 +21,7 @@ class MissionFooterBar extends ConsumerWidget { required this.activeIndex, required this.totalDistanceM, required this.totalDurationSec, + this.flyMissionStatus, required this.warnings, required this.onDetailsTap, required this.onWarningsTap, @@ -32,6 +34,12 @@ class MissionFooterBar extends ConsumerWidget { final int activeIndex; final double totalDistanceM; final double totalDurationSec; + + /// Nur im Fly-Modus gesetzt (Doku: "Live-Status der Mission in der + /// Details-Pille") - ersetzt dort die statischen Plan-Werte + /// (Wegpunktzahl/Gesamtdistanz/-dauer) durch aktiven Wegpunkt sowie + /// Distanz/Zeit dorthin. Bleibt im Plan-Modus null. + final FlyMissionStatus? flyMissionStatus; final List warnings; final VoidCallback onDetailsTap; final VoidCallback onWarningsTap; @@ -75,6 +83,7 @@ class MissionFooterBar extends ConsumerWidget { waypointCount: waypoints.length, totalDistanceM: totalDistanceM, totalDurationSec: totalDurationSec, + flyMissionStatus: flyMissionStatus, onTap: onDetailsTap, warnings: warnings, onWarningsTap: onWarningsTap, diff --git a/app/test/domain/mission_stats_test.dart b/app/test/domain/mission_stats_test.dart index a185068..b967f55 100644 --- a/app/test/domain/mission_stats_test.dart +++ b/app/test/domain/mission_stats_test.dart @@ -34,4 +34,80 @@ void main() { expect(stats.durationSec, closeTo(leg1 / 20 + leg2 / 10, 0.1)); }); }); + + group('computeFlyMissionStatus', () { + final waypoints = [_wp(0, 0), _wp(0, 0.01), _wp(0, 0.02)]; + + test('kein aktiver Wegpunkt (null) ergibt null', () { + expect( + computeFlyMissionStatus(waypoints: waypoints, activeWaypointIndex: null), + isNull, + ); + }); + + test('activeWaypointIndex ausserhalb der Liste ergibt null', () { + expect( + computeFlyMissionStatus(waypoints: waypoints, activeWaypointIndex: 3), + isNull, + ); + expect( + computeFlyMissionStatus(waypoints: waypoints, activeWaypointIndex: -1), + isNull, + ); + }); + + test('activeWaypointNumber ist 1-basiert (Index 1 -> Nummer 2)', () { + final status = computeFlyMissionStatus( + waypoints: waypoints, + activeWaypointIndex: 1, + ); + expect(status!.activeWaypointNumber, 2); + expect(status.waypointCount, 3); + }); + + test('ohne Position (kein Fix) bleiben Distanz/ETA null', () { + final status = computeFlyMissionStatus( + waypoints: waypoints, + activeWaypointIndex: 1, + ); + expect(status!.distanceToActiveWaypointM, isNull); + expect(status.etaToActiveWaypointSec, isNull); + }); + + test('mit Position wird die Distanz zum aktiven Wegpunkt berechnet', () { + final status = computeFlyMissionStatus( + waypoints: waypoints, + activeWaypointIndex: 1, + currentLat: 0, + currentLon: 0, + ); + // Wegpunkt 1 (Index 1) liegt bei (0, 0.01) - rund 1112.6 m vom + // Ursprung entfernt. + expect(status!.distanceToActiveWaypointM, closeTo(1112.6, 1)); + // Ohne Geschwindigkeit keine ETA. + expect(status.etaToActiveWaypointSec, isNull); + }); + + test('mit Position und Geschwindigkeit wird die ETA berechnet', () { + final status = computeFlyMissionStatus( + waypoints: waypoints, + activeWaypointIndex: 1, + currentLat: 0, + currentLon: 0, + currentSpeedMs: 10, + ); + expect(status!.etaToActiveWaypointSec, closeTo(1112.6 / 10, 0.1)); + }); + + test('unter 0.5 m/s wird keine (irrefuehrende) ETA berechnet', () { + final status = computeFlyMissionStatus( + waypoints: waypoints, + activeWaypointIndex: 1, + currentLat: 0, + currentLon: 0, + currentSpeedMs: 0.1, + ); + expect(status!.etaToActiveWaypointSec, isNull); + }); + }); } diff --git a/app/test/transport/msp/msp_flight_controller_link_test.dart b/app/test/transport/msp/msp_flight_controller_link_test.dart index 9dc7429..0e6e560 100644 --- a/app/test/transport/msp/msp_flight_controller_link_test.dart +++ b/app/test/transport/msp/msp_flight_controller_link_test.dart @@ -89,6 +89,9 @@ void main() { expect(frame.batteryPercent, 77); expect(frame.linkQuality, 88); expect(frame.flightMode, 'Waypoint mission (en route to WP)'); + // Fake-FC meldet activeWpNumber=3 (1-basiert) - als 0-basierter Index + // in die App-eigene Wegpunktliste entspricht das Index 2. + expect(frame.activeWaypointIndex, 2); await link.disconnect(); responder.cancel(); @@ -122,9 +125,10 @@ void main() { await link.connect(); // Der Poller laeuft bereits mit, aber readActiveWaypointIndex() fragt - // unabhaengig davon direkt ab. + // unabhaengig davon direkt ab. Fake-FC meldet activeWpNumber=3 + // (1-basiert) - als 0-basierter Index entspricht das 2. final index = await link.readActiveWaypointIndex(); - expect(index, 3); + expect(index, 2); await link.disconnect(); responder.cancel(); diff --git a/app/test/transport/msp/msp_telemetry_codec_test.dart b/app/test/transport/msp/msp_telemetry_codec_test.dart index a69db8a..471f5fa 100644 --- a/app/test/transport/msp/msp_telemetry_codec_test.dart +++ b/app/test/transport/msp/msp_telemetry_codec_test.dart @@ -90,8 +90,12 @@ void main() { expect(parseMspNavStatusActiveWaypoint(navStatusPayload(0)), isNull); }); - test('activeWpNumber > 0 wird durchgereicht', () { - expect(parseMspNavStatusActiveWaypoint(navStatusPayload(5)), 5); + test('activeWpNumber > 0 wird zu einem 0-basierten Index umgerechnet', () { + expect(parseMspNavStatusActiveWaypoint(navStatusPayload(5)), 4); + }); + + test('activeWpNumber 1 (erster Wegpunkt aktiv) wird zu Index 0', () { + expect(parseMspNavStatusActiveWaypoint(navStatusPayload(1)), 0); }); }); diff --git a/design/.~lock.DMC logo sketches.odp# b/design/.~lock.DMC logo sketches.odp# deleted file mode 100644 index 4a582da..0000000 --- a/design/.~lock.DMC logo sketches.odp# +++ /dev/null @@ -1 +0,0 @@ -,AI/Consti,AI,03.08.2026 21:43,file:///C:/Users/Consti/AppData/Roaming/LibreOffice/4; \ No newline at end of file diff --git a/design/fly screen battery indicator.png b/design/fly screen battery indicator.png new file mode 100644 index 0000000..c50659e Binary files /dev/null and b/design/fly screen battery indicator.png differ diff --git a/inav b/inav index c5c593d..b8de3f7 160000 --- a/inav +++ b/inav @@ -1 +1 @@ -Subproject commit c5c593d71d33c8e284bf9cd34381588fda7a98c8 +Subproject commit b8de3f78f968089408ecba9b5ee3af31b4c87660