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

107 lines
3.7 KiB
Dart

import '../../domain/waypoint/flat_waypoint_list.dart';
import '../flight_controller_link.dart';
import '../link_transport.dart';
import 'msp_client.dart';
import 'msp_commands.dart';
import 'msp_telemetry_codec.dart';
import 'msp_telemetry_poller.dart';
/// iNAV-Anbindung via MSP (Architektur-Doku 3.1), umgesetzt gegen den
/// tatsaechlichen iNAV-9.1.0-Quellcode (Doku Kommunikationsschicht Abschnitt
/// 4 - Befehlsnummern/Byte-Layouts gegen `msp_protocol.h`,
/// `msp_protocol_v2_inav.h` und `fc_msp.c` geprueft, nicht aus dem
/// Gedaechtnis uebernommen).
///
/// Kennt nur die protokollneutrale [LinkTransport]-Abstraktion (Doku
/// Kommunikationsschicht Abschnitt 2) - ob darunter Bluetooth Classic (MVP)
/// oder spaeter WLAN/UDP steckt, ist fuer diese Klasse unsichtbar.
///
/// Setzt die in der Doku vorgegebene Reihenfolge um: Rahmen kodieren/
/// dekodieren + Telemetrie lesen (dieser Schritt). Missionsupload,
/// Flugmodus-Wechsel und Armen sind bewusst noch nicht umgesetzt (Doku 4,
/// "Reihenfolge der Umsetzung": "Erst danach Missionsupload") - das sind
/// eigene, sicherheitsrelevante Schritte (u.a. `MSP_SET_WP`), die eigene
/// Sorgfalt brauchen.
class MspFlightControllerLink implements FlightControllerLink {
MspFlightControllerLink({required this.transport});
final LinkTransport transport;
MspClient? _client;
MspTelemetryPoller? _poller;
@override
FcCapabilities get capabilities => const FcCapabilities(
supportsMultiMission: true,
supportsInFlightUpload: false,
// Konservativer iNAV-Default (navigation.h: NAV_MAX_WAYPOINTS = 15).
// Viele Boards/Targets erlauben mehr; ohne eine MSP_WP_GETINFO-
// Abfrage (Teil des noch nicht umgesetzten Missions-Uploads) nicht
// praeziser bestimmbar.
maxWaypoints: 15,
);
@override
Future<void> connect() async {
// Idempotent (Doku flightControllerLinkProvider: "Verbindung bleibt
// ueber Moduswechsel hinweg aktiv") - jeder erneute Eintritt in den
// Fly-Modus ruft connect() erneut auf derselben, weiterhin
// bestehenden Instanz auf; ohne diese Sperre wuerde das einen zweiten
// MspClient/-Poller nebenher starten, der um MSP-Antworten race
// (MSP kennt keine Request-IDs, siehe MspClient-Doku).
if (_poller != null) return;
await transport.connect();
final client = MspClient(transport);
_client = client;
final poller = MspTelemetryPoller(client);
_poller = poller;
poller.start();
}
@override
Future<void> disconnect() async {
await _poller?.dispose();
_poller = null;
_client?.dispose();
_client = null;
await transport.disconnect();
}
@override
Future<void> uploadMission(FlatWaypointList mission) =>
throw UnimplementedError(
'Missions-Upload (MSP_SET_WP) ist noch nicht umgesetzt - Doku '
'Kommunikationsschicht 4 sieht das erst nach dem Telemetrie-Schritt vor.',
);
@override
Future<void> setFlightMode(FlightMode mode) => throw UnimplementedError(
'Flugmodus-Wechsel ist noch nicht umgesetzt.',
);
@override
Future<void> arm() => throw UnimplementedError(
'Armen ueber MSP ist noch nicht umgesetzt.',
);
@override
Stream<TelemetryFrame> subscribeTelemetry() {
final poller = _poller;
if (poller == null) {
throw StateError('subscribeTelemetry() vor connect() aufgerufen.');
}
return poller.frames;
}
@override
Future<int?> readActiveWaypointIndex() async {
final client = _client;
if (client == null) {
throw StateError('readActiveWaypointIndex() vor connect() aufgerufen.');
}
return parseMspNavStatusActiveWaypoint(
await client.request(MspCommands.navStatus),
);
}
}