Files
dmc/app/lib/transport/msp/msp_telemetry_poller.dart
T

104 lines
3.3 KiB
Dart

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<TelemetryFrame>.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<TelemetryFrame> get frames => _framesController.stream;
void start() {
if (_running) return;
_running = true;
unawaited(_loop());
}
void stop() {
_running = false;
}
Future<void> _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<void>.delayed(_cycleInterval);
}
}
Future<void> _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<void> dispose() async {
stop();
await _framesController.close();
}
}