telemetry fix gate for telemetry stream removed

This commit is contained in:
Constantin Leue
2026-08-02 20:44:20 +02:00
parent f42d997d4c
commit 11f894919b
6 changed files with 59 additions and 15 deletions
@@ -17,10 +17,19 @@ class FcCapabilities {
} }
/// Normalisiertes Telemetrie-Frame, das an die UI weitergereicht wird (Doku 3.4). /// 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 { class TelemetryFrame {
const TelemetryFrame({ const TelemetryFrame({
required this.lat, required this.lat,
required this.lon, required this.lon,
required this.hasFix,
required this.numSat,
required this.altitudeM, required this.altitudeM,
required this.speedMs, required this.speedMs,
required this.headingDeg, required this.headingDeg,
@@ -28,8 +37,12 @@ class TelemetryFrame {
this.activeWaypointIndex, this.activeWaypointIndex,
}); });
/// Nur aussagekraeftig, wenn [hasFix] - ohne Fix meldet der FC oft (0,0)
/// oder eine veraltete Position.
final double lat; final double lat;
final double lon; final double lon;
final bool hasFix;
final int numSat;
final double altitudeM; final double altitudeM;
final double speedMs; final double speedMs;
@@ -31,6 +31,8 @@ class MockFlightControllerLink implements FlightControllerLink {
_telemetryController?.add(TelemetryFrame( _telemetryController?.add(TelemetryFrame(
lat: 52.5, lat: 52.5,
lon: 13.4, lon: 13.4,
hasFix: true,
numSat: 12,
altitudeM: 120, altitudeM: 120,
speedMs: 18, speedMs: 18,
headingDeg: _heading, headingDeg: _heading,
@@ -21,6 +21,12 @@ import 'msp_telemetry_codec.dart';
/// hat aktuell keine Felder dafuer; sobald die UI das braucht, ist ein /// hat aktuell keine Felder dafuer; sobald die UI das braucht, ist ein
/// dritter, noch selteners abgefragter Zweig (`MSP2_INAV_ANALOG`) trivial /// dritter, noch selteners abgefragter Zweig (`MSP2_INAV_ANALOG`) trivial
/// ergaenzt. /// 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 { class MspTelemetryPoller {
MspTelemetryPoller(this._client); 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) { if (!_framesController.isClosed) {
_framesController.add(TelemetryFrame( _framesController.add(TelemetryFrame(
lat: gps.lat, lat: gps.lat,
lon: gps.lon, lon: gps.lon,
hasFix: gps.hasFix,
numSat: gps.numSat,
altitudeM: altitudeM, altitudeM: altitudeM,
speedMs: gps.speedMs, speedMs: gps.speedMs,
headingDeg: gps.headingDeg, headingDeg: gps.headingDeg,
+5 -4
View File
@@ -132,10 +132,11 @@ class FlyScreen extends ConsumerWidget {
); );
// Vor der ersten Telemetrie (Verbindungsaufbau laeuft noch, oder Mock // Vor der ersten Telemetrie (Verbindungsaufbau laeuft noch, oder Mock
// hat noch nicht getickt) auf dem ersten Wegpunkt statt gar nicht // hat noch nicht getickt) und ohne GPS-Fix (TelemetryFrame wird bewusst
// anzeigen - vermeidet einen leeren/verwirrenden Kartenausschnitt beim // nicht auf einen Fix gegated, siehe dortige Doku - ohne Fix meldet der
// Eintritt in den Fly-Modus. // FC aber oft (0,0) oder eine veraltete Position) auf dem ersten
final droneMarker = telemetry != null // Wegpunkt statt an einer erfundenen Stelle anzeigen.
final droneMarker = telemetry != null && telemetry.hasFix
? Marker( ? Marker(
point: LatLng(telemetry.lat, telemetry.lon), point: LatLng(telemetry.lat, telemetry.lon),
width: 28, width: 28,
@@ -141,7 +141,9 @@ class _WifiPanel extends ConsumerWidget {
// auslösen. Ist bereits eine echte Verbindung aktiv (z.B. weil der // auslösen. Ist bereits eine echte Verbindung aktiv (z.B. weil der
// Fly-Modus sie aufgebaut hat), liefert dies denselben Provider/dieselbe // Fly-Modus sie aufgebaut hat), liefert dies denselben Provider/dieselbe
// MSP-Client-Instanz wie FlyScreen - keine zweite, konkurrierende // 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 final telemetry = linkState == LinkState.connected
? ref.watch(telemetryProvider).value ? ref.watch(telemetryProvider).value
: null; : null;
@@ -173,7 +175,7 @@ class _WifiPanel extends ConsumerWidget {
const Spacer(), const Spacer(),
_StreamActivityPill(isReceiving: isReceiving, packetCount: packetCount), _StreamActivityPill(isReceiving: isReceiving, packetCount: packetCount),
const SizedBox(width: 6), const SizedBox(width: 6),
_StatePill(state: linkState, connectedLabel: connectedSsid), _StatePill(state: linkState, connectedLabel: connectedSsid ?? 'unknown'),
], ],
), ),
if (errorReason != null) ...[ if (errorReason != null) ...[
@@ -264,6 +266,7 @@ class _TelemetryFields extends StatelessWidget {
spacing: 16, spacing: 16,
runSpacing: 4, runSpacing: 4,
children: [ children: [
_field('GPS', t.hasFix ? 'Fix (${t.numSat} sats)' : 'No fix (${t.numSat} sats)'),
_field('Lat', t.lat.toStringAsFixed(6)), _field('Lat', t.lat.toStringAsFixed(6)),
_field('Lon', t.lon.toStringAsFixed(6)), _field('Lon', t.lon.toStringAsFixed(6)),
_field('Alt', '${t.altitudeM.toStringAsFixed(1)} m'), _field('Alt', '${t.altitudeM.toStringAsFixed(1)} m'),
@@ -12,7 +12,10 @@ import 'package:dmc_app/transport/msp/msp_frame_decoder.dart';
/// Beantwortet jede ueber [transport] gesendete MSP-Anfrage sofort mit einer /// Beantwortet jede ueber [transport] gesendete MSP-Anfrage sofort mit einer
/// synthetischen Antwort - simuliert den Flightcontroller, ohne echte /// synthetischen Antwort - simuliert den Flightcontroller, ohne echte
/// Hardware (Doku Kommunikationsschicht Abschnitt 9). /// Hardware (Doku Kommunikationsschicht Abschnitt 9).
Timer _startFakeFlightController(LoopbackTransport transport) { Timer _startFakeFlightController(
LoopbackTransport transport, {
int fixType = 3,
}) {
var answered = 0; var answered = 0;
return Timer.periodic(const Duration(milliseconds: 5), (_) { return Timer.periodic(const Duration(milliseconds: 5), (_) {
while (answered < transport.sentData.length) { while (answered < transport.sentData.length) {
@@ -24,7 +27,7 @@ Timer _startFakeFlightController(LoopbackTransport transport) {
switch (request.function) { switch (request.function) {
case MspCommands.rawGps: case MspCommands.rawGps:
final d = ByteData(18); 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.setUint8(1, 11);
d.setInt32(2, 525200000, Endian.little); // lat 52.52 d.setInt32(2, 525200000, Endian.little); // lat 52.52
d.setInt32(6, 134050000, Endian.little); // lon 13.405 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.lat, closeTo(52.52, 1e-6));
expect(frame.lon, closeTo(13.405, 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.altitudeM, closeTo(50.0, 1e-6));
expect(frame.speedMs, closeTo(5.0, 1e-6)); expect(frame.speedMs, closeTo(5.0, 1e-6));
expect(frame.headingDeg, closeTo(270.0, 1e-6)); expect(frame.headingDeg, closeTo(270.0, 1e-6));
@@ -76,6 +81,25 @@ void main() {
timeout: const Timeout(Duration(seconds: 5)), 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 { test('readActiveWaypointIndex() fragt MSP_NAV_STATUS direkt ab', () async {
final transport = LoopbackTransport(); final transport = LoopbackTransport();
final responder = _startFakeFlightController(transport); final responder = _startFakeFlightController(transport);