Files
dmc/app/lib/ui/providers/system_message_log_provider.dart
Constantin LeueandClaude Sonnet 5 e902197462 System messages fuer GPS-Fix und Drone connected/disconnected, Batterie-Schwellwerte auf Pro-Zelle umgestellt
- detectSystemMessages() erkennt jetzt auch den Uebergang zu einem GPS-Fix
  (flankengetriggert wie Battery/Failsafe), Meldung "GPS fix acquired (N
  satellites)".
- Die automatisch erkannten Verbindungsmeldungen heissen jetzt "Drone
  connected"/"Drone disconnected" statt "Connected"/"Connection lost" -
  praeziser an die tatsaechliche Bedeutung angelehnt (Eintreffen/Ausbleiben
  echter Telemetrie-Frames, nicht nur des rohen Socket-Zustands).
- DroneProfile: feste Pack-Alarmspannung (batteryVoltageGreenMinV/RedMinV)
  ersetzt durch batteryCellCount + Pro-Zelle-Schwellwerte
  (batteryVoltageGreenMinPerCellV/RedMinPerCellV, LiPo-Standardwerte 3.4/3.2
  V als Default). Die alten Feldnamen bleiben als berechnete Getter
  (Zellenzahl * Pro-Zelle-Wert) erhalten, damit
  telemetry_field_status.dart unveraendert bleibt. T1-Ranger-Standardprofil
  auf 4S gesetzt.
- DB-Schema v8 -> v9 (additiv, alte Pack-Spannungs-Spalten bleiben als tote
  Spalten stehen), Repository und Share-Codec-Im-/Export entsprechend
  angepasst; alte Exportdateien ohne die neuen Felder fallen auf die
  DroneProfile-Defaults zurueck statt eine unbekannte Zellenzahl zu raten.
- DroneProfileEditor: "Battery voltage alarm"-Gruppe um ein Zellenzahl-Feld
  erweitert und auf V/Zelle umbenannt.
- Tests ergaenzt/angepasst: GPS-Fix-Erkennung (Unit + Provider-Integration),
  neuer v8->v9-Migrationstest analog zum bestehenden v7->v8-Test,
  Connected/Disconnected-Umbenennung in Provider- und Widget-Tests,
  Cell-Count-Roundtrip in Repository- und Share-Codec-Tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 13:06:49 +02:00

123 lines
5.1 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/telemetry/system_message.dart';
import '../../transport/connection_type.dart';
import '../../transport/flight_controller_link.dart';
import 'connection_settings_provider.dart';
import 'telemetry_provider.dart';
/// Haelt die Liste der System Messages plus das manuelle [log()] (Doku:
/// "aendere das warnings Menü in System Messages"). Bewusst schlank, ohne
/// jede Telemetrie-/Verbindungsbeobachtung: mission_meta_provider.dart und
/// active_drone_profile_provider.dart rufen log() auch aus dem Plan-Modus
/// heraus auf (Missions-/Drohnenprofilwechsel) - wuerde diese Klasse selbst
/// telemetryProvider beobachten, risse schon ein simpler Missionswechsel im
/// Plan-Modus ungewollt den kompletten Telemetrie-/Transport-Stack (inkl.
/// echtem MSP/UDP-Transport) mit hoch. Die automatische Erkennung
/// (Drone connected/disconnected/GPS-Fix/Batterie/Failsafe/Verbindungstyp-
/// Wechsel) sitzt deshalb separat in [systemMessageAutoLogProvider], die
/// ausschliesslich der Fly-Modus beobachtet.
class SystemMessageLogNotifier extends Notifier<List<SystemMessage>> {
@override
List<SystemMessage> build() => [];
/// Fuegt einen Eintrag manuell hinzu (Doku: "Wegpunkte uebertragen",
/// "Wechsel der Mission oder des Drohnenprofils") - fuer Ereignisse, die
/// vom Aufloeser einer konkreten Aktion gemeldet werden, statt aus
/// Telemetrie/Einstellungen automatisch erkannt zu werden.
void log(String message, {SystemMessageSeverity severity = SystemMessageSeverity.info}) {
state = [
SystemMessage(timestamp: DateTime.now(), message: message, severity: severity),
...state,
];
}
void clear() => state = [];
}
final systemMessageLogProvider =
NotifierProvider<SystemMessageLogNotifier, List<SystemMessage>>(
SystemMessageLogNotifier.new);
/// Automatische Erkennung von Drone-connected/-disconnected/GPS-Fix/
/// Batterie-/Failsafe-/Verbindungstyp-Aenderungen (Doku: "wenn die
/// Verbindung hergestellt wird/verloren geht, der verbindungstyp geaendert
/// wird, gps fix aquired") - getrennt von
/// [systemMessageLogProvider] selbst (siehe dessen Doku), damit Plan-Modus-
/// Code, der nur log() fuer eine manuelle Meldung braucht, nicht versehentlich
/// telemetryProvider mit hochzieht. autoDispose: nur beobachtet, waehrend
/// irgendwer (FlyScreen) diesen Provider liest - verlaesst der Nutzer den
/// Fly-Modus, wird die Beobachtung wieder abgebaut wie bei telemetryProvider
/// selbst.
final systemMessageAutoLogProvider = Provider.autoDispose<void>((ref) {
TelemetryFrame? previousFrame;
var hadData = false;
ref.listen<AsyncValue<TelemetryFrame>>(telemetryProvider, (previous, next) {
next.when(
data: (frame) {
final log = ref.read(systemMessageLogProvider.notifier);
// Flankengetriggert wie "Drone disconnected" unten, nur umgekehrt -
// anders als dort ist hier kein Sonderfall fuer die allererste
// Verbindung noetig: der Uebergang "noch keine Daten" -> "erste
// Daten" ist immer ein echtes "Drone connected", auch beim
// allerersten Mal. Bewusst an das Eintreffen eines echten
// Telemetrie-Frames gebunden statt an den rohen Socket-/Link-
// Zustand (Doku: "drone connected/disconnected (receiving valid
// telemetry)") - ein offener Socket ohne auswertbare MSP-Antworten
// zaehlt also nicht als "connected".
if (!hadData) {
log.log('Drone connected');
}
final events = detectSystemMessages(
previous: previousFrame,
current: frame,
now: DateTime.now(),
);
previousFrame = frame;
hadData = true;
for (final event in events) {
log.log(event.message, severity: event.severity);
}
},
error: (error, stackTrace) {
// Nur melden, wenn zuvor bereits Frames flossen - sonst wuerde
// schon das (normale) Ausbleiben der allerersten Verbindung als
// "Drone disconnected" auftauchen.
if (hadData) {
ref.read(systemMessageLogProvider.notifier)
.log('Drone disconnected', severity: SystemMessageSeverity.critical);
}
previousFrame = null;
hadData = false;
},
loading: () {},
);
});
// Verbindungsart-Wechsel (Doku: "Verbindungstyp geaendert") - nur ab dem
// zweiten geladenen Wert protokollieren, sonst wuerde bereits das
// erstmalige Laden der Einstellungen aus der DB als "Wechsel" erscheinen.
ConnectionType? previousType;
var sawFirstLoad = false;
ref.listen(connectionSettingsProvider, (previous, next) {
final type = next.value?.connectionType;
if (type == null) return;
if (!sawFirstLoad) {
sawFirstLoad = true;
previousType = type;
return;
}
if (type != previousType) {
ref.read(systemMessageLogProvider.notifier)
.log('Connection type changed to ${_connectionTypeLabel(type)}');
previousType = type;
}
});
});
String _connectionTypeLabel(ConnectionType type) => switch (type) {
ConnectionType.wifi => 'WiFi mLRS UDP',
ConnectionType.mock => 'Mock (Test)',
};