From 11f894919bb9c75af26f2e55f39d02756467ec92 Mon Sep 17 00:00:00 2001 From: Constantin Leue <77660791+Consti3D@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:44:20 +0200 Subject: [PATCH] telemetry fix gate for telemetry stream removed --- app/lib/transport/flight_controller_link.dart | 13 +++++++++ .../mock/mock_flight_controller_link.dart | 2 ++ .../transport/msp/msp_telemetry_poller.dart | 15 +++++----- app/lib/ui/screens/fly/fly_screen.dart | 9 +++--- .../ui/screens/settings/settings_screen.dart | 7 +++-- .../msp/msp_flight_controller_link_test.dart | 28 +++++++++++++++++-- 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/app/lib/transport/flight_controller_link.dart b/app/lib/transport/flight_controller_link.dart index ea34caf..6199f42 100644 --- a/app/lib/transport/flight_controller_link.dart +++ b/app/lib/transport/flight_controller_link.dart @@ -17,10 +17,19 @@ class FcCapabilities { } /// Normalisiertes Telemetrie-Frame, das an die UI weitergereicht wird (Doku 3.4). +/// +/// Der Strom wird bewusst NICHT auf einen gueltigen GPS-Fix gegated (frueher +/// wurde ohne Fix gar kein Frame gesendet) - jeder Konsument entscheidet +/// selbst anhand von [hasFix], ob/wie er [lat]/[lon] verwendet. Der +/// Kartenmarker (FlyScreen) faellt bei fehlendem Fix weiterhin auf den +/// ersten Wegpunkt zurueck, das Settings-Diagnosepanel zeigt Hoehe/ +/// Geschwindigkeit/Armed-Status dagegen auch ohne Fix an. class TelemetryFrame { const TelemetryFrame({ required this.lat, required this.lon, + required this.hasFix, + required this.numSat, required this.altitudeM, required this.speedMs, required this.headingDeg, @@ -28,8 +37,12 @@ class TelemetryFrame { this.activeWaypointIndex, }); + /// Nur aussagekraeftig, wenn [hasFix] - ohne Fix meldet der FC oft (0,0) + /// oder eine veraltete Position. final double lat; final double lon; + final bool hasFix; + final int numSat; final double altitudeM; final double speedMs; diff --git a/app/lib/transport/mock/mock_flight_controller_link.dart b/app/lib/transport/mock/mock_flight_controller_link.dart index a225e14..0e80da5 100644 --- a/app/lib/transport/mock/mock_flight_controller_link.dart +++ b/app/lib/transport/mock/mock_flight_controller_link.dart @@ -31,6 +31,8 @@ class MockFlightControllerLink implements FlightControllerLink { _telemetryController?.add(TelemetryFrame( lat: 52.5, lon: 13.4, + hasFix: true, + numSat: 12, altitudeM: 120, speedMs: 18, headingDeg: _heading, diff --git a/app/lib/transport/msp/msp_telemetry_poller.dart b/app/lib/transport/msp/msp_telemetry_poller.dart index a1a1174..03de413 100644 --- a/app/lib/transport/msp/msp_telemetry_poller.dart +++ b/app/lib/transport/msp/msp_telemetry_poller.dart @@ -21,6 +21,12 @@ import 'msp_telemetry_codec.dart'; /// hat aktuell keine Felder dafuer; sobald die UI das braucht, ist ein /// dritter, noch selteners abgefragter Zweig (`MSP2_INAV_ANALOG`) trivial /// ergaenzt. +/// +/// Der Strom wird NICHT auf einen gueltigen GPS-Fix gegated - jeder Zyklus +/// emittiert ein [TelemetryFrame], auch ohne Fix (dann mit `hasFix: false` +/// und der vom FC gemeldeten, ggf. bedeutungslosen Position). Konsumenten +/// wie FlyScreen entscheiden selbst anhand von [TelemetryFrame.hasFix], ob +/// sie die Position anzeigen. class MspTelemetryPoller { MspTelemetryPoller(this._client); @@ -77,17 +83,12 @@ class MspTelemetryPoller { ); } - // Sicherheitsrelevante Anzeigen nie mit Platzhaltern fuellen (Doku 9): - // ohne GPS-Fix gibt es keine sinnvolle Position - dann lieber gar kein - // Telemetrie-Frame senden, statt (0,0) oder einen eingefrorenen alten - // Wert zu zeigen. Die bestehende Fly-Anzeige zeigt ohne Frame ohnehin - // keinen Drohnen-Marker (siehe telemetryProvider/FlyScreen). - if (!gps.hasFix) return; - if (!_framesController.isClosed) { _framesController.add(TelemetryFrame( lat: gps.lat, lon: gps.lon, + hasFix: gps.hasFix, + numSat: gps.numSat, altitudeM: altitudeM, speedMs: gps.speedMs, headingDeg: gps.headingDeg, diff --git a/app/lib/ui/screens/fly/fly_screen.dart b/app/lib/ui/screens/fly/fly_screen.dart index 9fce4c7..cb9d2da 100644 --- a/app/lib/ui/screens/fly/fly_screen.dart +++ b/app/lib/ui/screens/fly/fly_screen.dart @@ -132,10 +132,11 @@ class FlyScreen extends ConsumerWidget { ); // Vor der ersten Telemetrie (Verbindungsaufbau laeuft noch, oder Mock - // hat noch nicht getickt) auf dem ersten Wegpunkt statt gar nicht - // anzeigen - vermeidet einen leeren/verwirrenden Kartenausschnitt beim - // Eintritt in den Fly-Modus. - final droneMarker = telemetry != null + // hat noch nicht getickt) und ohne GPS-Fix (TelemetryFrame wird bewusst + // nicht auf einen Fix gegated, siehe dortige Doku - ohne Fix meldet der + // FC aber oft (0,0) oder eine veraltete Position) auf dem ersten + // Wegpunkt statt an einer erfundenen Stelle anzeigen. + final droneMarker = telemetry != null && telemetry.hasFix ? Marker( point: LatLng(telemetry.lat, telemetry.lon), width: 28, diff --git a/app/lib/ui/screens/settings/settings_screen.dart b/app/lib/ui/screens/settings/settings_screen.dart index f476117..4679108 100644 --- a/app/lib/ui/screens/settings/settings_screen.dart +++ b/app/lib/ui/screens/settings/settings_screen.dart @@ -141,7 +141,9 @@ class _WifiPanel extends ConsumerWidget { // auslösen. Ist bereits eine echte Verbindung aktiv (z.B. weil der // Fly-Modus sie aufgebaut hat), liefert dies denselben Provider/dieselbe // MSP-Client-Instanz wie FlyScreen - keine zweite, konkurrierende - // Abfrage-Schleife. + // Abfrage-Schleife. Der Telemetrie-Strom selbst ist nicht auf einen + // GPS-Fix gegated (siehe TelemetryFrame-Doku) - die Felder erscheinen + // hier also auch ohne Fix, sobald ueberhaupt eine MSP-Antwort ankommt. final telemetry = linkState == LinkState.connected ? ref.watch(telemetryProvider).value : null; @@ -173,7 +175,7 @@ class _WifiPanel extends ConsumerWidget { const Spacer(), _StreamActivityPill(isReceiving: isReceiving, packetCount: packetCount), const SizedBox(width: 6), - _StatePill(state: linkState, connectedLabel: connectedSsid), + _StatePill(state: linkState, connectedLabel: connectedSsid ?? 'unknown'), ], ), if (errorReason != null) ...[ @@ -264,6 +266,7 @@ class _TelemetryFields extends StatelessWidget { spacing: 16, runSpacing: 4, children: [ + _field('GPS', t.hasFix ? 'Fix (${t.numSat} sats)' : 'No fix (${t.numSat} sats)'), _field('Lat', t.lat.toStringAsFixed(6)), _field('Lon', t.lon.toStringAsFixed(6)), _field('Alt', '${t.altitudeM.toStringAsFixed(1)} m'), 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 28fbc4b..51c6146 100644 --- a/app/test/transport/msp/msp_flight_controller_link_test.dart +++ b/app/test/transport/msp/msp_flight_controller_link_test.dart @@ -12,7 +12,10 @@ import 'package:dmc_app/transport/msp/msp_frame_decoder.dart'; /// Beantwortet jede ueber [transport] gesendete MSP-Anfrage sofort mit einer /// synthetischen Antwort - simuliert den Flightcontroller, ohne echte /// Hardware (Doku Kommunikationsschicht Abschnitt 9). -Timer _startFakeFlightController(LoopbackTransport transport) { +Timer _startFakeFlightController( + LoopbackTransport transport, { + int fixType = 3, +}) { var answered = 0; return Timer.periodic(const Duration(milliseconds: 5), (_) { while (answered < transport.sentData.length) { @@ -24,7 +27,7 @@ Timer _startFakeFlightController(LoopbackTransport transport) { switch (request.function) { case MspCommands.rawGps: final d = ByteData(18); - d.setUint8(0, 3); // fixType: 3D-Fix + d.setUint8(0, fixType); // fixType: 3 = 3D-Fix, 0 = kein Fix d.setUint8(1, 11); d.setInt32(2, 525200000, Endian.little); // lat 52.52 d.setInt32(6, 134050000, Endian.little); // lon 13.405 @@ -66,6 +69,8 @@ void main() { expect(frame.lat, closeTo(52.52, 1e-6)); expect(frame.lon, closeTo(13.405, 1e-6)); + expect(frame.hasFix, isTrue); + expect(frame.numSat, 11); expect(frame.altitudeM, closeTo(50.0, 1e-6)); expect(frame.speedMs, closeTo(5.0, 1e-6)); expect(frame.headingDeg, closeTo(270.0, 1e-6)); @@ -76,6 +81,25 @@ void main() { timeout: const Timeout(Duration(seconds: 5)), ); + test( + 'subscribeTelemetry() liefert auch ohne GPS-Fix ein Frame (Strom wird ' + 'nicht auf einen Fix gegated)', + () async { + final transport = LoopbackTransport(); + final responder = _startFakeFlightController(transport, fixType: 0); + final link = MspFlightControllerLink(transport: transport); + + await link.connect(); + final frame = await link.subscribeTelemetry().first; + + expect(frame.hasFix, isFalse); + + await link.disconnect(); + responder.cancel(); + }, + timeout: const Timeout(Duration(seconds: 5)), + ); + test('readActiveWaypointIndex() fragt MSP_NAV_STATUS direkt ab', () async { final transport = LoopbackTransport(); final responder = _startFakeFlightController(transport);