import 'dart:async'; import '../flight_controller_link.dart'; import 'msp_client.dart'; import 'msp_commands.dart'; import 'msp_telemetry_codec.dart'; /// Eigener Poll-Takt ueber [MspClient] (Doku Kommunikationsschicht Abschnitt /// 4, "Polling"): MSP liefert nichts von selbst. Rundenbasiert statt /// paralleler Timer pro Nachrichtengruppe, weil [MspClient] Anfragen /// ohnehin serialisiert (immer nur eine offene Anfrage) - ein /// Round-Robin-Zyklus bildet die in der Doku genannten Raten ab, ohne dass /// sich Anfragen ueberlappen koennten: /// /// - Position/Lage/Hoehe (5-10 Hz): jeden Zyklus (`MSP_RAW_GPS` + /// `MSP_ALTITUDE`). /// - Status/Flugmodus (2 Hz): jeden [_statusEveryNCycles]-ten Zyklus /// (`MSP2_INAV_STATUS` + `MSP_NAV_STATUS`). /// /// Spannung/Strom (1 Hz laut Doku) fehlt hier bewusst noch - [TelemetryFrame] /// hat aktuell keine Felder dafuer; sobald die UI das braucht, ist ein /// dritter, noch selteners abgefragter Zweig (`MSP2_INAV_ANALOG`) trivial /// ergaenzt. class MspTelemetryPoller { MspTelemetryPoller(this._client); final MspClient _client; final _framesController = StreamController.broadcast(); static const _cycleInterval = Duration(milliseconds: 150); // ~6-7 Hz static const _statusEveryNCycles = 3; // ~2 Hz bei 150-ms-Takt bool _running = false; int _cycle = 0; bool _armed = false; int? _activeWaypointIndex; Stream get frames => _framesController.stream; void start() { if (_running) return; _running = true; unawaited(_loop()); } void stop() { _running = false; } Future _loop() async { while (_running) { try { await _runCycle(); } catch (_) { // Eine einzelne fehlgeschlagene Anfrage (Timeout/CRC/Transport // getrennt) darf den Takt nicht stoppen - der naechste Zyklus // startet nach der Wartezeit regulaer weiter (Doku 4: Zeitgrenze + // Wiederholung; Wiederverbinden passiert bereits auf Transport- // Ebene, siehe BluetoothClassicTransport). } await Future.delayed(_cycleInterval); } } Future _runCycle() async { final gps = parseMspRawGps(await _client.request(MspCommands.rawGps)); final altitudeM = parseMspAltitudeMeters(await _client.request(MspCommands.altitude)); _cycle++; if (_cycle % _statusEveryNCycles == 0) { _armed = parseMspInavStatusArmed( await _client.request(MspCommands.inavStatus), ); _activeWaypointIndex = parseMspNavStatusActiveWaypoint( await _client.request(MspCommands.navStatus), ); } // 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, altitudeM: altitudeM, speedMs: gps.speedMs, armed: _armed, activeWaypointIndex: _activeWaypointIndex, )); } } Future dispose() async { stop(); await _framesController.close(); } }