disabled auto connect when switching to fly mode and no disconnect when leavin fly mode, race condition fix for connectionType ( now telemetry not working anymore), UI optimizations: centered speed and alt, zoom to mission uses full screen
This commit is contained in:
@@ -22,6 +22,11 @@ class MockFlightControllerLink implements FlightControllerLink {
|
||||
|
||||
@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
|
||||
|
||||
@@ -43,6 +43,13 @@ class MspFlightControllerLink implements FlightControllerLink {
|
||||
|
||||
@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;
|
||||
|
||||
@@ -36,7 +36,11 @@ void fitMapToWaypoints(WidgetRef ref, List<Waypoint> waypoints) {
|
||||
ref.read(mapControllerProvider).fitCamera(
|
||||
CameraFit.bounds(
|
||||
bounds: LatLngBounds.fromPoints(points),
|
||||
padding: const EdgeInsets.fromLTRB(50, 50, 50, 140),
|
||||
// Symmetrisch statt vorher unten 140 (Reservierung fuer die
|
||||
// Fusszeile) - der Fit soll den ganzen Bildschirm ausnutzen statt
|
||||
// dort unnoetig herauszuzoomen (Doku: "Fusszeile wird ausgespart,
|
||||
// nicht genutzt").
|
||||
padding: const EdgeInsets.all(50),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../transport/connection_type.dart';
|
||||
import '../../transport/flight_controller_link.dart';
|
||||
import '../../transport/link_transport.dart';
|
||||
import '../../transport/mock/mock_flight_controller_link.dart';
|
||||
import '../../transport/msp/msp_flight_controller_link.dart';
|
||||
import 'connection_settings_provider.dart';
|
||||
@@ -15,20 +16,29 @@ import 'wifi_connection_provider.dart';
|
||||
/// (Doku Kommunikationsschicht v2, Abschnitt 1), 5G/USB sind noch nicht
|
||||
/// umgesetzt.
|
||||
///
|
||||
/// connectionType wird bewusst per ref.read() einmalig beim Eintritt in den
|
||||
/// Fly-Modus gelesen, nicht reaktiv beobachtet - sonst wuerde jede
|
||||
/// Einstellungsaenderung waehrend des Flugs (z.B. Tippen im
|
||||
/// SSID-Praefix-Feld, das ueberall im Settings-Screen ein
|
||||
/// `ref.invalidate(connectionSettingsProvider)` ausloest) einen Neuaufbau
|
||||
/// dieses Providers samt Trennen/Neuverbinden des Transports ausloesen.
|
||||
/// connectionType wird reaktiv beobachtet (ref.watch), nicht nur einmalig
|
||||
/// gelesen - beim ersten App-Start ist connectionSettingsProvider oft noch
|
||||
/// nicht aus der DB geladen (AsyncLoading, value == null), was ohne Watch
|
||||
/// dauerhaft auf den Mock-Fallback festgenagelt haette (siehe unten, "nicht
|
||||
/// autoDispose"): sobald die Einstellungen nachladen oder der Nutzer die
|
||||
/// Verbindungsart in den Settings aendert, baut dieser Provider sich korrekt
|
||||
/// neu auf (alter Link wird dabei via ref.onDispose sauber getrennt). Das
|
||||
/// ist inzwischen unkritisch haeufig, da die fruehere Sorge (Tippen im
|
||||
/// SSID-Praefix-Feld loest bei jedem Zeichen einen Neuaufbau aus) entfaellt,
|
||||
/// seit dieses Feld aus den Settings entfernt wurde (Preset "WiFi mLRS UDP").
|
||||
///
|
||||
/// autoDispose: verbindet erst, wenn der Fly-Screen tatsaechlich beobachtet
|
||||
/// wird, und trennt sich wieder (Doku Abschnitt 8: "Verlassen des
|
||||
/// Fly-Modus gibt den Netz-Request frei"), sobald niemand mehr zuhoert
|
||||
/// (z.B. Rueckkehr in den Plan-Modus) statt endlos im Hintergrund
|
||||
/// weiterzulaufen.
|
||||
final flightControllerLinkProvider = Provider.autoDispose<FlightControllerLink>((ref) {
|
||||
final connectionType = ref.read(connectionSettingsProvider).value?.connectionType;
|
||||
/// Bewusst NICHT autoDispose (Doku: "beim Wechsel in Plan kein disconnect
|
||||
/// triggern, Verbindung aktiv halten") - anders als frueher trennt ein
|
||||
/// Wechsel zurueck in den Plan-Modus (der telemetryProvider nicht mehr
|
||||
/// beobachtet) die Verbindung nicht mehr automatisch. Sie bleibt bis zum
|
||||
/// expliziten "Disconnect" in den Settings, einer Aenderung der
|
||||
/// Verbindungsart oder Schliessen der App bestehen.
|
||||
/// [MspFlightControllerLink.connect]/[MockFlightControllerLink.connect]
|
||||
/// sind deshalb idempotent: jeder erneute Watch von [telemetryProvider]
|
||||
/// (z.B. erneuter Eintritt in den Fly-Modus) ruft `connect()` erneut auf,
|
||||
/// darf aber keinen zweiten MSP-Client/-Poller nebenher starten.
|
||||
final flightControllerLinkProvider = Provider<FlightControllerLink>((ref) {
|
||||
final connectionType = ref.watch(connectionSettingsProvider).value?.connectionType;
|
||||
final FlightControllerLink link = connectionType == ConnectionType.wifi
|
||||
? MspFlightControllerLink(transport: ref.watch(wifiTransportProvider))
|
||||
: MockFlightControllerLink();
|
||||
@@ -36,11 +46,29 @@ final flightControllerLinkProvider = Provider.autoDispose<FlightControllerLink>(
|
||||
return link;
|
||||
});
|
||||
|
||||
/// Live-Telemetrie des verbundenen Flightcontrollers (Doku 3.4). connect()
|
||||
/// wird beim ersten Abonnenten ausgeloest - MockFlightControllerLink
|
||||
/// verlangt das vor subscribeTelemetry().
|
||||
/// Live-Telemetrie des verbundenen Flightcontrollers (Doku 3.4). Bleibt
|
||||
/// selbst autoDispose: nur die Frame-Weiterleitung an gerade aktive Watcher
|
||||
/// endet beim Verlassen, nicht die zugrundeliegende Verbindung (siehe
|
||||
/// [flightControllerLinkProvider]).
|
||||
///
|
||||
/// Ruft bei WLAN/MSP bewusst NICHT einfach `link.connect()` auf, sobald
|
||||
/// irgendwer (z.B. FlyScreen beim Eintritt in den Fly-Modus) diesen Provider
|
||||
/// beobachtet (Doku: "kein connect beim Wechsel in den Fly-Modus, um das
|
||||
/// Debuggen auf echter Hardware zu vereinfachen") - `MspFlightControllerLink.
|
||||
/// connect()` wuerde sonst ueber `transport.connect()` sofort einen
|
||||
/// WifiNetworkSpecifier-Systemdialog ausloesen, unabhaengig vom entfernten
|
||||
/// Auto-Connect-Trigger in TopModeBar. Stattdessen wird erst aktiv, sobald
|
||||
/// die rohe WLAN-Verbindung bereits ueber den expliziten "Connect"-Knopf in
|
||||
/// den Settings steht - bis dahin bleibt der Stream leer (kein Frame,
|
||||
/// FlyScreen zeigt den Fallback-Marker auf dem ersten Wegpunkt). Fuer
|
||||
/// Mock/Bluetooth/5G/USB gilt diese Einschraenkung nicht, dort ist
|
||||
/// "verbinden" ohnehin folgenlos bzw. noch nicht umgesetzt.
|
||||
final telemetryProvider = StreamProvider.autoDispose<TelemetryFrame>((ref) async* {
|
||||
final link = ref.watch(flightControllerLinkProvider);
|
||||
if (link is MspFlightControllerLink &&
|
||||
ref.watch(wifiLinkStateProvider).value != LinkState.connected) {
|
||||
return;
|
||||
}
|
||||
await link.connect();
|
||||
yield* link.subscribeTelemetry();
|
||||
});
|
||||
|
||||
@@ -120,6 +120,14 @@ class FlyScreen extends ConsumerWidget {
|
||||
MediaQuery.of(context).padding.left,
|
||||
MediaQuery.of(context).padding.right,
|
||||
);
|
||||
// Gleiche Reservierung fuer die Fusszeile wie die Rad-Readouts im
|
||||
// Plan-Modus (PlanScreen: wheelBottomInset), damit die Alt/Speed-
|
||||
// Anzeige zwischen Kopf- und Fusszeile vertikal zentriert erscheint,
|
||||
// statt am Kartenrand zu kleben.
|
||||
final footerBottomInset = math.max(
|
||||
MediaQuery.of(context).size.height * 0.06,
|
||||
124.0,
|
||||
);
|
||||
final terrainProfile = ref.watch(terrainProvider).profile;
|
||||
final warnings = computeMissionWarnings(
|
||||
waypoints: waypoints,
|
||||
@@ -179,13 +187,15 @@ class FlyScreen extends ConsumerWidget {
|
||||
// Rad-Widget (curAlt/curSpeed) gibt, dessen Wert sinnvoll waere.
|
||||
Positioned(
|
||||
top: 56,
|
||||
bottom: footerBottomInset,
|
||||
left: 14 + safeEdgeInset,
|
||||
child: _liveReadout('Alt', telemetry?.altitudeM, 'm'),
|
||||
child: Center(child: _liveReadout('Alt', telemetry?.altitudeM, 'm')),
|
||||
),
|
||||
Positioned(
|
||||
top: 56,
|
||||
bottom: footerBottomInset,
|
||||
right: 14 + safeEdgeInset,
|
||||
child: _liveReadout('Speed', telemetry?.speedMs, 'm/s'),
|
||||
child: Center(child: _liveReadout('Speed', telemetry?.speedMs, 'm/s')),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
|
||||
@@ -135,18 +135,15 @@ class _WifiPanel extends ConsumerWidget {
|
||||
final packetCount = ref.watch(wifiPacketCountProvider);
|
||||
final connectedSsid = ref.watch(wifiConnectedSsidProvider).value;
|
||||
|
||||
// MSP-Telemetrie nur beobachten, wenn der Transport bereits verbunden
|
||||
// ist - sonst wuerde allein das Oeffnen dieses Screens ueber
|
||||
// telemetryProvider einen neuen Verbindungsversuch (samt Systemdialog)
|
||||
// auslösen. Ist bereits eine echte Verbindung aktiv (z.B. weil der
|
||||
// Fly-Modus sie aufgebaut hat), liefert dies denselben Provider/dieselbe
|
||||
// MSP-Client-Instanz wie FlyScreen - keine zweite, konkurrierende
|
||||
// Abfrage-Schleife. Der Telemetrie-Strom selbst ist nicht auf einen
|
||||
// GPS-Fix gegated (siehe TelemetryFrame-Doku) - die Felder erscheinen
|
||||
// hier also auch ohne Fix, sobald ueberhaupt eine MSP-Antwort ankommt.
|
||||
final telemetry = linkState == LinkState.connected
|
||||
? ref.watch(telemetryProvider).value
|
||||
: null;
|
||||
// telemetryProvider loest selbst keinen Verbindungsversuch aus, solange
|
||||
// WLAN/MSP noch nicht ueber den "Connect"-Knopf verbunden ist (siehe
|
||||
// dortige Doku) - hier also gefahrlos unbedingt beobachtbar. Liefert bei
|
||||
// aktiver Verbindung denselben Provider/dieselbe MSP-Client-Instanz wie
|
||||
// FlyScreen - keine zweite, konkurrierende Abfrage-Schleife. Der
|
||||
// Telemetrie-Strom selbst ist nicht auf einen GPS-Fix gegated (siehe
|
||||
// TelemetryFrame-Doku) - die Felder erscheinen hier also auch ohne Fix,
|
||||
// sobald ueberhaupt eine MSP-Antwort ankommt.
|
||||
final telemetry = ref.watch(telemetryProvider).value;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -186,8 +183,12 @@ class _WifiPanel extends ConsumerWidget {
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('Auto-connect when entering Fly mode'),
|
||||
// Trigger aktuell bewusst deaktiviert (Doku: "kein connect beim
|
||||
// Wechsel in den Fly-Modus, um das Debuggen auf echter Hardware
|
||||
// zu vereinfachen") - die Einstellung bleibt fuer eine spaetere
|
||||
// Reaktivierung erhalten, wirkt sich derzeit aber auf nichts aus.
|
||||
subtitle: const Text(
|
||||
'Only once a network has been remembered.',
|
||||
'Currently inactive - connect manually above.',
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
value: connectionSettings?.connectionAutoConnect ?? true,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -6,12 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../app_mode/app_mode_cubit.dart';
|
||||
import '../../transport/connection_type.dart';
|
||||
import '../../transport/link_transport.dart';
|
||||
import '../dmc_colors.dart';
|
||||
import '../providers/connection_settings_provider.dart';
|
||||
import '../providers/flight_mode_provider.dart';
|
||||
import '../providers/wifi_connection_provider.dart';
|
||||
import 'flight_mode_pill.dart';
|
||||
import 'fly_map_controls.dart';
|
||||
import 'header_wind_pill.dart';
|
||||
@@ -110,7 +105,13 @@ class TopModeBar extends ConsumerWidget {
|
||||
// Eintritt in den Fly-Modus setzt immer auf Waypoint
|
||||
// zurueck (Doku 3.11).
|
||||
ref.read(flightModeProvider.notifier).reset();
|
||||
_maybeAutoConnectWifi(ref);
|
||||
// Kein Auto-Connect mehr beim Wechsel in den Fly-Modus
|
||||
// (bewusst entfernt, um das Debuggen auf echter Hardware
|
||||
// zu vereinfachen - Verbindungsaufbau laeuft jetzt
|
||||
// ausschliesslich ueber den expliziten "Connect"-Knopf in
|
||||
// den Settings). Die Verbindung selbst bleibt ohnehin ueber
|
||||
// Moduswechsel hinweg bestehen, siehe
|
||||
// flightControllerLinkProvider.
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -121,34 +122,6 @@ class TopModeBar extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Verbindet automatisch zur mLRS-Bruecke, wenn in den Fly-Modus gewechselt
|
||||
/// wird (Doku Kommunikationsschicht v2 Abschnitt 8: "Verbindung automatisch
|
||||
/// herstellen bei Wechsel in Fly, wenn WLAN gemerkt"). Voraussetzungen:
|
||||
/// Verbindungsart ist WLAN, "Auto-connect"-Einstellung ist an, und es gibt
|
||||
/// bereits eine gemerkte SSID aus einer frueheren erfolgreichen Verbindung -
|
||||
/// ohne gemerkte SSID muesste ohnehin der Systemdialog erscheinen, das ist
|
||||
/// dann kein "automatisches" Verbinden mehr. Der Fortschritt ist ueber
|
||||
/// [_WifiConnectionProgressPill] sichtbar.
|
||||
Future<void> _maybeAutoConnectWifi(WidgetRef ref) async {
|
||||
final settings = ref.read(connectionSettingsProvider).value;
|
||||
if (settings == null) return;
|
||||
if (settings.connectionType != ConnectionType.wifi) return;
|
||||
if (!settings.connectionAutoConnect) return;
|
||||
if (settings.connectionWifiLastSsid == null) return;
|
||||
|
||||
final transport = ref.read(wifiTransportProvider);
|
||||
if (transport.currentState == LinkState.connected ||
|
||||
transport.currentState == LinkState.connecting) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await transport.connect();
|
||||
} on LinkConnectException {
|
||||
// Grund/Nachricht sind bereits ueber den State-Stream gemeldet, siehe
|
||||
// _WifiConnectionProgressPill.
|
||||
}
|
||||
}
|
||||
|
||||
class _ModeButton extends StatelessWidget {
|
||||
const _ModeButton({
|
||||
required this.label,
|
||||
|
||||
Reference in New Issue
Block a user