live mission details in footer implemented
This commit is contained in:
@@ -26,3 +26,75 @@ MissionStats computeMissionStats(List<Waypoint> 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<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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<FlightControllerLink>((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;
|
||||
});
|
||||
|
||||
@@ -226,6 +226,19 @@ class _FlyScreenState extends ConsumerState<FlyScreen> {
|
||||
];
|
||||
}
|
||||
|
||||
// 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<FlyScreen> {
|
||||
activeIndex: -1,
|
||||
totalDistanceM: missionStats.distanceM,
|
||||
totalDurationSec: missionStats.durationSec,
|
||||
flyMissionStatus: flyMissionStatus,
|
||||
warnings: warnings,
|
||||
onDetailsTap: _openWaypointList,
|
||||
onWarningsTap: () => _openWarningsPanel(warnings),
|
||||
|
||||
@@ -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}'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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<MissionWarning> 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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user