Files
dmc/app/lib/transport/mock/mock_flight_controller_link.dart
T

95 lines
3.0 KiB
Dart

import 'dart:async';
import '../../domain/waypoint/flat_waypoint_list.dart';
import '../flight_controller_link.dart';
/// Synthetische Telemetrie fuer UI-Entwicklung/Tests ohne Hardware oder SITL
/// (Architektur-Doku 3.1).
class MockFlightControllerLink implements FlightControllerLink {
bool _armed = false;
int? _activeWaypointIndex;
StreamController<TelemetryFrame>? _telemetryController;
Timer? _ticker;
double _heading = 0;
double _batteryPercent = 100;
@override
FcCapabilities get capabilities => const FcCapabilities(
supportsMultiMission: true,
supportsInFlightUpload: false,
maxWaypoints: 60,
);
@override
Future<void> connect() async {
// Idempotent (Doku flightControllerLinkProvider: "Verbindung bleibt
// ueber Moduswechsel hinweg aktiv") - sonst wuerde jeder erneute
// Eintritt in den Fly-Modus einen zweiten Timer/Controller anlegen und
// den vorherigen verwaist weiterlaufen lassen.
if (_telemetryController != null) return;
_telemetryController = StreamController<TelemetryFrame>.broadcast();
_ticker = Timer.periodic(const Duration(seconds: 1), (_) {
// Kurs dreht sich langsam weiter statt fest zu stehen, damit die
// Rotation des Drohnen-Markers (DroneMarkerIcon) auch ohne Hardware
// sichtbar ueberprueft werden kann (Doku 9: UI-Entwicklung ohne
// Hardware).
_heading = (_heading + 5) % 360;
// Langsam sinkender Akkustand, damit die Anzeige auch ohne Hardware
// sichtbar auf Werteaenderungen reagiert (Doku 9).
_batteryPercent = (_batteryPercent - 0.1).clamp(0, 100);
_telemetryController?.add(TelemetryFrame(
lat: 52.5,
lon: 13.4,
hasFix: true,
numSat: 12,
altitudeM: 120,
speedMs: 18,
headingDeg: _heading,
armed: _armed,
batteryPercent: _batteryPercent.round(),
linkQuality: 95,
flightMode: _activeWaypointIndex != null
? 'Waypoint mission (en route)'
: 'Idle',
activeWaypointIndex: _activeWaypointIndex,
));
});
}
@override
Future<void> disconnect() async {
_ticker?.cancel();
await _telemetryController?.close();
_telemetryController = null;
}
@override
Future<void> uploadMission(FlatWaypointList mission) async {
// iNAV/MAVLink erlauben Mission-Upload nur im unarmed-Zustand (Doku 4.5).
if (_armed) {
throw StateError('Mission-Upload verweigert: Drohne ist armed.');
}
_activeWaypointIndex = mission.waypoints.isEmpty ? null : 0;
}
@override
Future<void> setFlightMode(FlightMode mode) async {}
@override
Future<void> arm() async {
_armed = true;
}
@override
Stream<TelemetryFrame> subscribeTelemetry() {
final controller = _telemetryController;
if (controller == null) {
throw StateError('subscribeTelemetry() vor connect() aufgerufen.');
}
return controller.stream;
}
@override
Future<int?> readActiveWaypointIndex() async => _activeWaypointIndex;
}