Drohnen-Status-Pille zeigt bei Verbindungsverlust rot/disconnected statt eingefrorener Werte
Waehrend eines Verbindungsverlusts frieren die TelemetryFrame-Werte
bewusst ein (telemetryProvider.value bleibt auf dem letzten Stand, siehe
vorherigen Commit) - die Fusszeilen-Pille im Fly-Modus hat daraus bislang
weiterhin den zuletzt bekannten (ggf. laengst veralteten) Zustand
abgeleitet, statt den Verbindungsverlust selbst widerzuspiegeln.
- domain/telemetry/drone_status.dart: neuer DroneState.disconnected,
computeDroneStatus() bekommt einen neuen Parameter `connected` - hat
Vorrang vor allem anderen (auch vor Emergency) und uebersteuert
connectionQuality auf rot. positionQuality bleibt bewusst unveraendert
(folgt weiterhin den eingefrorenen Werten) - laut Anfrage sollen nur
Drohnen-Icon und Verbindungsqualitaet, nicht die Positionsqualitaet, rot
erzwungen werden.
- fly_screen.dart: `connected: !telemetryAsync.hasError` statt der
bisherigen Ableitung aus einzelnen Telemetriewerten.
- bottom_stats_bar.dart: neues Label/Farbe ("disconnected", rot) fuer den
neuen Zustand in der Drohnen-Status-Pille.
Auf dem Pixel_10a-Emulator verifiziert: WiFi-Verbindungsart (noch nie
verbunden) zeigt bereits korrekt rotes Papierflieger-Icon, rote
Verbindungsqualitaet, rotes Satelliten-Icon und "disconnected" in rot;
Mock-Verbindung zeigt unveraendert normal gruen "ready".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e5c87fba5f
commit
46e51b3920
@@ -4,8 +4,13 @@ enum SignalLevel { green, yellow, red }
|
|||||||
|
|
||||||
/// Zustand der Drohne fuer das Papierflieger-Icon in der Drohnen-Status-
|
/// Zustand der Drohne fuer das Papierflieger-Icon in der Drohnen-Status-
|
||||||
/// Pille (Doku: "drone state: papierflieger icon mit farbcodierung ... mit
|
/// Pille (Doku: "drone state: papierflieger icon mit farbcodierung ... mit
|
||||||
/// text wie (idle, ready, armed, waypoint, error)").
|
/// text wie (idle, ready, armed, waypoint, error)"). [disconnected] deckt
|
||||||
enum DroneState { idle, ready, armed, waypoint, error }
|
/// den Fall ab, dass der Telemetrie-Strom selbst gerade fehlerhaft ist
|
||||||
|
/// (Doku: "Statuspille ... muss eine rote Drohne, eine rote
|
||||||
|
/// Verbindungsqualität und den Status text in rot disconnected anzeigen"),
|
||||||
|
/// im Unterschied zu den anderen Zustaenden, die aus (ggf. eingefrorenen,
|
||||||
|
/// siehe computeDroneStatus) Telemetriewerten abgeleitet werden.
|
||||||
|
enum DroneState { idle, ready, armed, waypoint, disconnected, error }
|
||||||
|
|
||||||
/// Kombiniertes Ergebnis aus [computeDroneStatus] - die drei Elemente der
|
/// Kombiniertes Ergebnis aus [computeDroneStatus] - die drei Elemente der
|
||||||
/// Fly-Modus-Drohnen-Pille (Doku: "connection quality", "position quality",
|
/// Fly-Modus-Drohnen-Pille (Doku: "connection quality", "position quality",
|
||||||
@@ -49,12 +54,15 @@ SignalLevel _positionQuality({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Zustand aus Armed-Status, GPS-Fix und dem rohen Navigationsmodus
|
/// Zustand aus Armed-Status, GPS-Fix und dem rohen Navigationsmodus
|
||||||
/// (`MW_GPS_MODE_*`, siehe TelemetryFrame.navMode) - Emergency (15) hat
|
/// (`MW_GPS_MODE_*`, siehe TelemetryFrame.navMode) - [connected] hat
|
||||||
/// Vorrang vor allem anderen, "waypoint" nur solange tatsaechlich armed und
|
/// Vorrang vor allem anderen (kein Telemetrie-Strom heisst, keiner der
|
||||||
/// im Waypoint-Missionsmodus (3) navigiert wird, "ready" markiert lediglich
|
/// anderen Werte ist aktuell), danach Emergency (15), "waypoint" nur
|
||||||
/// einen vorhandenen GPS-Fix im disarmed-Zustand (kein zusaetzlicher
|
/// solange tatsaechlich armed und im Waypoint-Missionsmodus (3) navigiert
|
||||||
/// Qualitaets-Gate durch Positionsqualitaet - Nutzerentscheidung).
|
/// wird, "ready" markiert lediglich einen vorhandenen GPS-Fix im
|
||||||
|
/// disarmed-Zustand (kein zusaetzlicher Qualitaets-Gate durch
|
||||||
|
/// Positionsqualitaet - Nutzerentscheidung).
|
||||||
DroneState _droneState({
|
DroneState _droneState({
|
||||||
|
required bool connected,
|
||||||
required bool armed,
|
required bool armed,
|
||||||
required bool hasFix,
|
required bool hasFix,
|
||||||
required int navMode,
|
required int navMode,
|
||||||
@@ -62,6 +70,7 @@ DroneState _droneState({
|
|||||||
const navModeEmergency = 15;
|
const navModeEmergency = 15;
|
||||||
const navModeWaypointMission = 3;
|
const navModeWaypointMission = 3;
|
||||||
|
|
||||||
|
if (!connected) return DroneState.disconnected;
|
||||||
if (navMode == navModeEmergency) return DroneState.error;
|
if (navMode == navModeEmergency) return DroneState.error;
|
||||||
if (armed && navMode == navModeWaypointMission) return DroneState.waypoint;
|
if (armed && navMode == navModeWaypointMission) return DroneState.waypoint;
|
||||||
if (armed) return DroneState.armed;
|
if (armed) return DroneState.armed;
|
||||||
@@ -72,7 +81,16 @@ DroneState _droneState({
|
|||||||
/// Reine Ableitung aus einem [TelemetryFrame] (siehe transport/
|
/// Reine Ableitung aus einem [TelemetryFrame] (siehe transport/
|
||||||
/// flight_controller_link.dart) - kein MSP-/Protokollwissen, nur die
|
/// flight_controller_link.dart) - kein MSP-/Protokollwissen, nur die
|
||||||
/// bereits normalisierten Felder.
|
/// bereits normalisierten Felder.
|
||||||
|
///
|
||||||
|
/// [connected] spiegelt, ob der Telemetrie-Strom gerade tatsaechlich Daten
|
||||||
|
/// liefert (z.B. `!telemetryProvider.hasError`, siehe fly_screen.dart) -
|
||||||
|
/// getrennt von den einzelnen Telemetriewerten, weil diese bei einem
|
||||||
|
/// Verbindungsverlust bewusst auf ihrem letzten Stand "einfrieren" (Doku:
|
||||||
|
/// "im Drone Detail Menü ist es okay, dass die Daten einfrieren"), die
|
||||||
|
/// Statuspille aber trotzdem ehrlich "disconnected" anzeigen soll statt
|
||||||
|
/// den zuletzt bekannten (ggf. laengst veralteten) Zustand fortzuschreiben.
|
||||||
DroneStatus computeDroneStatus({
|
DroneStatus computeDroneStatus({
|
||||||
|
required bool connected,
|
||||||
required bool armed,
|
required bool armed,
|
||||||
required bool hasFix,
|
required bool hasFix,
|
||||||
required int numSat,
|
required int numSat,
|
||||||
@@ -82,8 +100,10 @@ DroneStatus computeDroneStatus({
|
|||||||
required int navMode,
|
required int navMode,
|
||||||
}) {
|
}) {
|
||||||
return DroneStatus(
|
return DroneStatus(
|
||||||
connectionQuality: _connectionQuality(linkQuality: linkQuality, snrDb: snrDb),
|
connectionQuality: connected
|
||||||
|
? _connectionQuality(linkQuality: linkQuality, snrDb: snrDb)
|
||||||
|
: SignalLevel.red,
|
||||||
positionQuality: _positionQuality(hasFix: hasFix, numSat: numSat, hdop: hdop),
|
positionQuality: _positionQuality(hasFix: hasFix, numSat: numSat, hdop: hdop),
|
||||||
state: _droneState(armed: armed, hasFix: hasFix, navMode: navMode),
|
state: _droneState(connected: connected, armed: armed, hasFix: hasFix, navMode: navMode),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,8 @@ class _FlyScreenState extends ConsumerState<FlyScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final waypoints = ref.watch(currentMissionProvider);
|
final waypoints = ref.watch(currentMissionProvider);
|
||||||
final droneProfile = ref.watch(activeDroneProfileProvider);
|
final droneProfile = ref.watch(activeDroneProfileProvider);
|
||||||
final telemetry = ref.watch(telemetryProvider).value;
|
final telemetryAsync = ref.watch(telemetryProvider);
|
||||||
|
final telemetry = telemetryAsync.value;
|
||||||
final followDrone = ref.watch(followDroneProvider);
|
final followDrone = ref.watch(followDroneProvider);
|
||||||
// Beobachtet die automatische Ereigniserkennung (Connected/Lost/
|
// Beobachtet die automatische Ereigniserkennung (Connected/Lost/
|
||||||
// Batterie/Failsafe/Verbindungstyp, Doku: "log von kritischen
|
// Batterie/Failsafe/Verbindungstyp, Doku: "log von kritischen
|
||||||
@@ -322,7 +323,17 @@ class _FlyScreenState extends ConsumerState<FlyScreen> {
|
|||||||
// Ohne Frame (Verbindungsaufbau laeuft noch) liefern die Platzhalterwerte
|
// Ohne Frame (Verbindungsaufbau laeuft noch) liefern die Platzhalterwerte
|
||||||
// ueber computeDroneStatus zuverlaessig idle/rot/rot - eine ehrliche
|
// ueber computeDroneStatus zuverlaessig idle/rot/rot - eine ehrliche
|
||||||
// "keine Daten"-Anzeige statt eines unterschiedlichen Widget-Typs.
|
// "keine Daten"-Anzeige statt eines unterschiedlichen Widget-Typs.
|
||||||
|
//
|
||||||
|
// connected: telemetryAsync.hasError statt telemetry == null, weil
|
||||||
|
// TelemetryFrame-Werte bei einem Verbindungsverlust bewusst auf dem
|
||||||
|
// letzten Stand "einfrieren" (Doku: "im Drone Detail Menü okay, dass
|
||||||
|
// Daten einfrieren" - telemetry bleibt also nicht null, siehe
|
||||||
|
// telemetryProvider-Doku zu AsyncError mit erhaltenem previous value).
|
||||||
|
// Die Pille selbst soll den Verbindungsverlust trotzdem sofort ehrlich
|
||||||
|
// als "disconnected" zeigen, statt den zuletzt bekannten (ggf. laengst
|
||||||
|
// veralteten) Zustand fortzuschreiben.
|
||||||
final droneStatus = computeDroneStatus(
|
final droneStatus = computeDroneStatus(
|
||||||
|
connected: !telemetryAsync.hasError,
|
||||||
armed: telemetry?.armed ?? false,
|
armed: telemetry?.armed ?? false,
|
||||||
hasFix: telemetry?.hasFix ?? false,
|
hasFix: telemetry?.hasFix ?? false,
|
||||||
numSat: telemetry?.numSat ?? 0,
|
numSat: telemetry?.numSat ?? 0,
|
||||||
|
|||||||
@@ -453,20 +453,23 @@ class _DroneStatusPill extends StatelessWidget {
|
|||||||
DroneState.ready: 'ready',
|
DroneState.ready: 'ready',
|
||||||
DroneState.armed: 'armed',
|
DroneState.armed: 'armed',
|
||||||
DroneState.waypoint: 'waypoint',
|
DroneState.waypoint: 'waypoint',
|
||||||
|
DroneState.disconnected: 'disconnected',
|
||||||
DroneState.error: 'error',
|
DroneState.error: 'error',
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Nur 3 Farben fuer 5 Zustaende (Doku: "papierflieger icon mit
|
/// Nur 3 Farben fuer 6 Zustaende (Doku: "papierflieger icon mit
|
||||||
/// farbcodierung (gruen/gelb/rot)") - gruen fuer die beiden Zustaende, in
|
/// farbcodierung (gruen/gelb/rot)") - gruen fuer die beiden Zustaende, in
|
||||||
/// denen alles nominal laeuft (bereit zum Start bzw. aktiv unterwegs),
|
/// denen alles nominal laeuft (bereit zum Start bzw. aktiv unterwegs),
|
||||||
/// gelb fuer die beiden "Achtung"-Uebergangszustaende (am Boden wartend
|
/// gelb fuer die beiden "Achtung"-Uebergangszustaende (am Boden wartend
|
||||||
/// bzw. bereits scharf aber ohne aktive Wegpunktnavigation, z.B. manuell
|
/// bzw. bereits scharf aber ohne aktive Wegpunktnavigation, z.B. manuell
|
||||||
/// oder im Hold), rot ausschliesslich fuer Emergency.
|
/// oder im Hold), rot fuer Emergency UND fehlenden Telemetrie-Strom
|
||||||
|
/// (Doku: "rote Drohne ... Status text in rot disconnected").
|
||||||
static const _stateColors = {
|
static const _stateColors = {
|
||||||
DroneState.idle: DmcColors.pafAmber,
|
DroneState.idle: DmcColors.pafAmber,
|
||||||
DroneState.ready: DmcColors.flyModeColor,
|
DroneState.ready: DmcColors.flyModeColor,
|
||||||
DroneState.armed: DmcColors.pafAmber,
|
DroneState.armed: DmcColors.pafAmber,
|
||||||
DroneState.waypoint: DmcColors.flyModeColor,
|
DroneState.waypoint: DmcColors.flyModeColor,
|
||||||
|
DroneState.disconnected: DmcColors.warnRed,
|
||||||
DroneState.error: DmcColors.warnRed,
|
DroneState.error: DmcColors.warnRed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ void main() {
|
|||||||
group('computeDroneStatus - connectionQuality', () {
|
group('computeDroneStatus - connectionQuality', () {
|
||||||
test('gruen bei LQ >=70% und SNR >=0dB', () {
|
test('gruen bei LQ >=70% und SNR >=0dB', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -19,6 +20,7 @@ void main() {
|
|||||||
|
|
||||||
test('gelb bei LQ >=70% aber SNR <0dB (Rauschen trotz guter LQ)', () {
|
test('gelb bei LQ >=70% aber SNR <0dB (Rauschen trotz guter LQ)', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -32,6 +34,7 @@ void main() {
|
|||||||
|
|
||||||
test('gelb bei LQ zwischen 50% und 70%', () {
|
test('gelb bei LQ zwischen 50% und 70%', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -45,6 +48,7 @@ void main() {
|
|||||||
|
|
||||||
test('rot bei LQ <50%', () {
|
test('rot bei LQ <50%', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -60,6 +64,7 @@ void main() {
|
|||||||
group('computeDroneStatus - positionQuality', () {
|
group('computeDroneStatus - positionQuality', () {
|
||||||
test('gruen bei >=8 Satelliten und HDOP <2.0', () {
|
test('gruen bei >=8 Satelliten und HDOP <2.0', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 9,
|
numSat: 9,
|
||||||
@@ -73,6 +78,7 @@ void main() {
|
|||||||
|
|
||||||
test('gelb bei >=6 Satelliten und HDOP <5.0, aber nicht gruen-Schwelle', () {
|
test('gelb bei >=6 Satelliten und HDOP <5.0, aber nicht gruen-Schwelle', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 6,
|
numSat: 6,
|
||||||
@@ -86,6 +92,7 @@ void main() {
|
|||||||
|
|
||||||
test('rot ohne GPS-Fix, unabhaengig von Satelliten/HDOP', () {
|
test('rot ohne GPS-Fix, unabhaengig von Satelliten/HDOP', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: false,
|
hasFix: false,
|
||||||
numSat: 12,
|
numSat: 12,
|
||||||
@@ -99,6 +106,7 @@ void main() {
|
|||||||
|
|
||||||
test('rot bei wenigen Satelliten trotz Fix', () {
|
test('rot bei wenigen Satelliten trotz Fix', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 3,
|
numSat: 3,
|
||||||
@@ -114,6 +122,7 @@ void main() {
|
|||||||
group('computeDroneStatus - state', () {
|
group('computeDroneStatus - state', () {
|
||||||
test('error hat Vorrang, sobald navMode Emergency (15) ist', () {
|
test('error hat Vorrang, sobald navMode Emergency (15) ist', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: true,
|
armed: true,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -127,6 +136,7 @@ void main() {
|
|||||||
|
|
||||||
test('waypoint, wenn armed und navMode Waypoint mission (3)', () {
|
test('waypoint, wenn armed und navMode Waypoint mission (3)', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: true,
|
armed: true,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -140,6 +150,7 @@ void main() {
|
|||||||
|
|
||||||
test('armed, wenn armed aber kein Waypoint-Modus (z.B. Hold)', () {
|
test('armed, wenn armed aber kein Waypoint-Modus (z.B. Hold)', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: true,
|
armed: true,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -153,6 +164,7 @@ void main() {
|
|||||||
|
|
||||||
test('ready, wenn nicht armed aber GPS-Fix vorhanden', () {
|
test('ready, wenn nicht armed aber GPS-Fix vorhanden', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: true,
|
hasFix: true,
|
||||||
numSat: 10,
|
numSat: 10,
|
||||||
@@ -166,6 +178,7 @@ void main() {
|
|||||||
|
|
||||||
test('idle, wenn nicht armed und kein GPS-Fix', () {
|
test('idle, wenn nicht armed und kein GPS-Fix', () {
|
||||||
final status = computeDroneStatus(
|
final status = computeDroneStatus(
|
||||||
|
connected: true,
|
||||||
armed: false,
|
armed: false,
|
||||||
hasFix: false,
|
hasFix: false,
|
||||||
numSat: 0,
|
numSat: 0,
|
||||||
@@ -177,4 +190,56 @@ void main() {
|
|||||||
expect(status.state, DroneState.idle);
|
expect(status.state, DroneState.idle);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('computeDroneStatus - connected: false (Doku: "Statuspille ... muss '
|
||||||
|
'eine rote Drohne, eine rote Verbindungsqualität und den Status text '
|
||||||
|
'in rot disconnected anzeigen")', () {
|
||||||
|
test('state ist disconnected, unabhaengig von allen anderen Werten', () {
|
||||||
|
final status = computeDroneStatus(
|
||||||
|
connected: false,
|
||||||
|
armed: true,
|
||||||
|
hasFix: true,
|
||||||
|
numSat: 10,
|
||||||
|
hdop: 1.0,
|
||||||
|
linkQuality: 80,
|
||||||
|
snrDb: 5,
|
||||||
|
// Emergency-navMode haette ohne den connected-Vorrang "error"
|
||||||
|
// ergeben statt "disconnected".
|
||||||
|
navMode: 15,
|
||||||
|
);
|
||||||
|
expect(status.state, DroneState.disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connectionQuality ist rot, unabhaengig von linkQuality/snrDb', () {
|
||||||
|
final status = computeDroneStatus(
|
||||||
|
connected: false,
|
||||||
|
armed: false,
|
||||||
|
hasFix: true,
|
||||||
|
numSat: 10,
|
||||||
|
hdop: 1.0,
|
||||||
|
// Waeren an sich beste Werte (gruen) - werden von connected:false
|
||||||
|
// trotzdem uebersteuert.
|
||||||
|
linkQuality: 100,
|
||||||
|
snrDb: 20,
|
||||||
|
navMode: 0,
|
||||||
|
);
|
||||||
|
expect(status.connectionQuality, SignalLevel.red);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('positionQuality folgt weiterhin den (eingefrorenen) Telemetriewerten '
|
||||||
|
'- nur Drohne/Verbindungsqualitaet werden per Doku explizit rot '
|
||||||
|
'gefordert', () {
|
||||||
|
final status = computeDroneStatus(
|
||||||
|
connected: false,
|
||||||
|
armed: false,
|
||||||
|
hasFix: true,
|
||||||
|
numSat: 10,
|
||||||
|
hdop: 1.0,
|
||||||
|
linkQuality: 0,
|
||||||
|
snrDb: -99,
|
||||||
|
navMode: 0,
|
||||||
|
);
|
||||||
|
expect(status.positionQuality, SignalLevel.green);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user