MSP protocol implementation and settings menu
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user