Warning thresholds added in drone profile. traffic light indication in drone satus menue added. layout and content optimized

This commit is contained in:
Constantin Leue
2026-08-05 20:15:18 +02:00
parent f65dceb338
commit 4982341203
8 changed files with 1302 additions and 37 deletions
+72
View File
@@ -17,6 +17,18 @@ class DroneProfile {
required this.maxRangeM,
required this.maxEnduranceMin,
required this.maxAltitudeM,
this.gpsSatGreenMin = defaultGpsSatGreenMin,
this.gpsSatRedMin = defaultGpsSatRedMin,
this.hdopGreenMax = defaultHdopGreenMax,
this.hdopRedMax = defaultHdopRedMax,
this.currentGreenMaxA = defaultCurrentGreenMaxA,
this.currentRedMaxA = defaultCurrentRedMaxA,
this.batteryVoltageGreenMinV = defaultBatteryVoltageGreenMinV,
this.batteryVoltageRedMinV = defaultBatteryVoltageRedMinV,
this.rxQualityGreenMin = defaultRxQualityGreenMin,
this.rxQualityRedMin = defaultRxQualityRedMin,
this.rxSnrGreenMinDb = defaultRxSnrGreenMinDb,
this.rxSnrRedMinDb = defaultRxSnrRedMinDb,
});
/// Null fuer ein noch nicht gespeichertes (neu angelegtes) Profil.
@@ -34,6 +46,42 @@ class DroneProfile {
final double maxEnduranceMin;
final double maxAltitudeM;
/// Ampel-Schwellwerte fuer die farbcodierten Telemetriefelder im
/// Drohnen-Status-Menue (Doku: "Schwellwerte fuer die Ampel-Kodierung im
/// Drohnenprofil anlegen", siehe domain/telemetry/telemetry_field_status.dart)
/// - je Feld eine Gruen- und eine Rot-Schwelle, dazwischen gilt gelb.
/// *Max/*GreenMax bei Feldern, bei denen ein NIEDRIGER Wert gut ist (HDOP),
/// *Min/*GreenMin bei Feldern, bei denen ein HOHER Wert gut ist (alle
/// anderen). Default-Werte als statische Konstanten (statt nur inline im
/// Konstruktor), damit sie auch als Fallback beim Import aelterer, noch
/// ohne diese Felder exportierter Profile dienen koennen (siehe
/// drone_share_codec.dart).
final double gpsSatGreenMin;
final double gpsSatRedMin;
final double hdopGreenMax;
final double hdopRedMax;
final double currentGreenMaxA;
final double currentRedMaxA;
final double batteryVoltageGreenMinV;
final double batteryVoltageRedMinV;
final double rxQualityGreenMin;
final double rxQualityRedMin;
final double rxSnrGreenMinDb;
final double rxSnrRedMinDb;
static const defaultGpsSatGreenMin = 8.0;
static const defaultGpsSatRedMin = 6.0;
static const defaultHdopGreenMax = 2.0;
static const defaultHdopRedMax = 5.0;
static const defaultCurrentGreenMaxA = 20.0;
static const defaultCurrentRedMaxA = 30.0;
static const defaultBatteryVoltageGreenMinV = 20.4;
static const defaultBatteryVoltageRedMinV = 19.2;
static const defaultRxQualityGreenMin = 70.0;
static const defaultRxQualityRedMin = 50.0;
static const defaultRxSnrGreenMinDb = 0.0;
static const defaultRxSnrRedMinDb = -10.0;
/// T1 Ranger (Heewing) - Standardprofil (Doku 3.5), analog DEFAULT_DRONE
/// im HTML-Demonstrator.
static const _t1RangerName = 'T1 Ranger';
@@ -76,6 +124,18 @@ class DroneProfile {
double? maxRangeM,
double? maxEnduranceMin,
double? maxAltitudeM,
double? gpsSatGreenMin,
double? gpsSatRedMin,
double? hdopGreenMax,
double? hdopRedMax,
double? currentGreenMaxA,
double? currentRedMaxA,
double? batteryVoltageGreenMinV,
double? batteryVoltageRedMinV,
double? rxQualityGreenMin,
double? rxQualityRedMin,
double? rxSnrGreenMinDb,
double? rxSnrRedMinDb,
}) {
return DroneProfile(
id: id ?? this.id,
@@ -91,6 +151,18 @@ class DroneProfile {
maxRangeM: maxRangeM ?? this.maxRangeM,
maxEnduranceMin: maxEnduranceMin ?? this.maxEnduranceMin,
maxAltitudeM: maxAltitudeM ?? this.maxAltitudeM,
gpsSatGreenMin: gpsSatGreenMin ?? this.gpsSatGreenMin,
gpsSatRedMin: gpsSatRedMin ?? this.gpsSatRedMin,
hdopGreenMax: hdopGreenMax ?? this.hdopGreenMax,
hdopRedMax: hdopRedMax ?? this.hdopRedMax,
currentGreenMaxA: currentGreenMaxA ?? this.currentGreenMaxA,
currentRedMaxA: currentRedMaxA ?? this.currentRedMaxA,
batteryVoltageGreenMinV: batteryVoltageGreenMinV ?? this.batteryVoltageGreenMinV,
batteryVoltageRedMinV: batteryVoltageRedMinV ?? this.batteryVoltageRedMinV,
rxQualityGreenMin: rxQualityGreenMin ?? this.rxQualityGreenMin,
rxQualityRedMin: rxQualityRedMin ?? this.rxQualityRedMin,
rxSnrGreenMinDb: rxSnrGreenMinDb ?? this.rxSnrGreenMinDb,
rxSnrRedMinDb: rxSnrRedMinDb ?? this.rxSnrRedMinDb,
);
}
}
@@ -0,0 +1,89 @@
import '../mission/drone_profile.dart';
import 'drone_status.dart';
/// Ampel-Farbcodierung je einzelnem Telemetriefeld im Drohnen-Status-Menue
/// (Doku: "drone status felder im menue auch farblich kodieren") - anders
/// als [computeDroneStatus] (das Verbindungs-/Positionsqualitaet je zu einem
/// kombinierten Signal fuer die Fusszeilen-Pille zusammenfasst) bewertet
/// dies jedes Feld einzeln anhand der Schwellwerte im aktiven [DroneProfile].
class TelemetryFieldStatus {
const TelemetryFieldStatus({
required this.numSat,
required this.hdop,
required this.currentA,
required this.batteryVoltage,
required this.rxQuality,
required this.rxSnr,
});
final SignalLevel numSat;
final SignalLevel hdop;
final SignalLevel currentA;
final SignalLevel batteryVoltage;
final SignalLevel rxQuality;
final SignalLevel rxSnr;
}
/// Fuer Felder, bei denen ein HOHER Wert gut ist (Satelliten, RX-Qualitaet/
/// -SNR): rot unterhalb [redMin], gruen ab [greenMin], dazwischen gelb.
SignalLevel _levelHigherIsBetter(double value, double greenMin, double redMin) {
if (value < redMin) return SignalLevel.red;
if (value < greenMin) return SignalLevel.yellow;
return SignalLevel.green;
}
/// Fuer Felder, bei denen ein NIEDRIGER Wert gut ist (HDOP): rot oberhalb
/// [redMax], gruen bis [greenMax], dazwischen gelb.
SignalLevel _levelLowerIsBetter(double value, double greenMax, double redMax) {
if (value > redMax) return SignalLevel.red;
if (value > greenMax) return SignalLevel.yellow;
return SignalLevel.green;
}
/// Reine Ableitung aus normalisierten Telemetriewerten + den
/// Ampel-Schwellwerten des aktiven Drohnenprofils - kein MSP-/
/// Protokollwissen, siehe computeDroneStatus-Doku.
TelemetryFieldStatus computeTelemetryFieldStatus({
required int numSat,
required bool hasFix,
required double hdop,
required double currentA,
required double batteryVoltage,
required int linkQuality,
required int snrDb,
required DroneProfile profile,
}) {
return TelemetryFieldStatus(
numSat: _levelHigherIsBetter(
numSat.toDouble(),
profile.gpsSatGreenMin,
profile.gpsSatRedMin,
),
// Ohne Fix ist der HDOP-Wert bedeutungslos/veraltet (siehe Anzeige in
// drone_status_warnings_panel.dart, die dafuer bereits '—' zeigt) - dann
// unabhaengig vom Zahlenwert rot statt eines irrefuehrend guten Signals.
hdop: hasFix
? _levelLowerIsBetter(hdop, profile.hdopGreenMax, profile.hdopRedMax)
: SignalLevel.red,
currentA: _levelLowerIsBetter(
currentA,
profile.currentGreenMaxA,
profile.currentRedMaxA,
),
batteryVoltage: _levelHigherIsBetter(
batteryVoltage,
profile.batteryVoltageGreenMinV,
profile.batteryVoltageRedMinV,
),
rxQuality: _levelHigherIsBetter(
linkQuality.toDouble(),
profile.rxQualityGreenMin,
profile.rxQualityRedMin,
),
rxSnr: _levelHigherIsBetter(
snrDb.toDouble(),
profile.rxSnrGreenMinDb,
profile.rxSnrRedMinDb,
),
);
}
+47 -1
View File
@@ -50,6 +50,32 @@ class DroneProfiles extends Table {
RealColumn get maxRangeM => real()();
RealColumn get maxEnduranceMin => real()();
RealColumn get maxAltitudeM => real()();
/// Ampel-Schwellwerte fuer die Telemetriefelder im Drohnen-Status-Menue
/// (siehe domain.DroneProfile-Doku und domain/telemetry/
/// telemetry_field_status.dart) - Defaults identisch zu den
/// DroneProfile.default*-Konstanten.
RealColumn get gpsSatGreenMin =>
real().withDefault(const Constant(8.0))();
RealColumn get gpsSatRedMin => real().withDefault(const Constant(6.0))();
RealColumn get hdopGreenMax => real().withDefault(const Constant(2.0))();
RealColumn get hdopRedMax => real().withDefault(const Constant(5.0))();
RealColumn get currentGreenMaxA =>
real().withDefault(const Constant(20.0))();
RealColumn get currentRedMaxA =>
real().withDefault(const Constant(30.0))();
RealColumn get batteryVoltageGreenMinV =>
real().withDefault(const Constant(20.4))();
RealColumn get batteryVoltageRedMinV =>
real().withDefault(const Constant(19.2))();
RealColumn get rxQualityGreenMin =>
real().withDefault(const Constant(70.0))();
RealColumn get rxQualityRedMin =>
real().withDefault(const Constant(50.0))();
RealColumn get rxSnrGreenMinDb =>
real().withDefault(const Constant(0.0))();
RealColumn get rxSnrRedMinDb =>
real().withDefault(const Constant(-10.0))();
}
/// Einzeilige Einstellungstabelle (id fest 0) fuer app-weite Auswahl, die
@@ -110,7 +136,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase.forTesting(super.executor);
@override
int get schemaVersion => 7;
int get schemaVersion => 8;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -157,6 +183,26 @@ class AppDatabase extends _$AppDatabase {
if (from < 7) {
await m.addColumn(missions, missions.terrainProfileJson);
}
if (from < 8) {
await m.addColumn(droneProfiles, droneProfiles.gpsSatGreenMin);
await m.addColumn(droneProfiles, droneProfiles.gpsSatRedMin);
await m.addColumn(droneProfiles, droneProfiles.hdopGreenMax);
await m.addColumn(droneProfiles, droneProfiles.hdopRedMax);
await m.addColumn(droneProfiles, droneProfiles.currentGreenMaxA);
await m.addColumn(droneProfiles, droneProfiles.currentRedMaxA);
await m.addColumn(
droneProfiles,
droneProfiles.batteryVoltageGreenMinV,
);
await m.addColumn(
droneProfiles,
droneProfiles.batteryVoltageRedMinV,
);
await m.addColumn(droneProfiles, droneProfiles.rxQualityGreenMin);
await m.addColumn(droneProfiles, droneProfiles.rxQualityRedMin);
await m.addColumn(droneProfiles, droneProfiles.rxSnrGreenMinDb);
await m.addColumn(droneProfiles, droneProfiles.rxSnrRedMinDb);
}
},
);
File diff suppressed because it is too large Load Diff
@@ -60,6 +60,18 @@ class DroneProfileRepository {
maxRangeM: row.maxRangeM,
maxEnduranceMin: row.maxEnduranceMin,
maxAltitudeM: row.maxAltitudeM,
gpsSatGreenMin: row.gpsSatGreenMin,
gpsSatRedMin: row.gpsSatRedMin,
hdopGreenMax: row.hdopGreenMax,
hdopRedMax: row.hdopRedMax,
currentGreenMaxA: row.currentGreenMaxA,
currentRedMaxA: row.currentRedMaxA,
batteryVoltageGreenMinV: row.batteryVoltageGreenMinV,
batteryVoltageRedMinV: row.batteryVoltageRedMinV,
rxQualityGreenMin: row.rxQualityGreenMin,
rxQualityRedMin: row.rxQualityRedMin,
rxSnrGreenMinDb: row.rxSnrGreenMinDb,
rxSnrRedMinDb: row.rxSnrRedMinDb,
);
DroneProfilesCompanion _toCompanion(domain.DroneProfile profile) =>
@@ -76,5 +88,17 @@ class DroneProfileRepository {
maxRangeM: Value(profile.maxRangeM),
maxEnduranceMin: Value(profile.maxEnduranceMin),
maxAltitudeM: Value(profile.maxAltitudeM),
gpsSatGreenMin: Value(profile.gpsSatGreenMin),
gpsSatRedMin: Value(profile.gpsSatRedMin),
hdopGreenMax: Value(profile.hdopGreenMax),
hdopRedMax: Value(profile.hdopRedMax),
currentGreenMaxA: Value(profile.currentGreenMaxA),
currentRedMaxA: Value(profile.currentRedMaxA),
batteryVoltageGreenMinV: Value(profile.batteryVoltageGreenMinV),
batteryVoltageRedMinV: Value(profile.batteryVoltageRedMinV),
rxQualityGreenMin: Value(profile.rxQualityGreenMin),
rxQualityRedMin: Value(profile.rxQualityRedMin),
rxSnrGreenMinDb: Value(profile.rxSnrGreenMinDb),
rxSnrRedMinDb: Value(profile.rxSnrRedMinDb),
);
}
@@ -24,6 +24,18 @@ String buildDroneExportJson(DroneProfile profile) {
'maxRangeM': profile.maxRangeM,
'maxEnduranceMin': profile.maxEnduranceMin,
'maxAltitudeM': profile.maxAltitudeM,
'gpsSatGreenMin': profile.gpsSatGreenMin,
'gpsSatRedMin': profile.gpsSatRedMin,
'hdopGreenMax': profile.hdopGreenMax,
'hdopRedMax': profile.hdopRedMax,
'currentGreenMaxA': profile.currentGreenMaxA,
'currentRedMaxA': profile.currentRedMaxA,
'batteryVoltageGreenMinV': profile.batteryVoltageGreenMinV,
'batteryVoltageRedMinV': profile.batteryVoltageRedMinV,
'rxQualityGreenMin': profile.rxQualityGreenMin,
'rxQualityRedMin': profile.rxQualityRedMin,
'rxSnrGreenMinDb': profile.rxSnrGreenMinDb,
'rxSnrRedMinDb': profile.rxSnrRedMinDb,
},
};
return const JsonEncoder.withIndent(' ').convert(map);
@@ -88,6 +100,15 @@ ParsedDroneImport parseDroneImportJson(String jsonText) {
}
}
// Ampel-Schwellwerte (siehe DroneProfile-Doku) sind bewusst NICHT Teil von
// _requiredNumericFields - aeltere, vor deren Einfuehrung exportierte
// Profildateien haben diese Felder noch nicht und sollen trotzdem
// importierbar bleiben (dann gelten die DroneProfile.default*-Werte).
double numOrDefault(String field, double fallback) {
final v = droneData[field];
return v is num ? v.toDouble() : fallback;
}
final profile = DroneProfile(
name: name,
minSpeed: (droneData['minSpeed'] as num).toDouble(),
@@ -101,6 +122,24 @@ ParsedDroneImport parseDroneImportJson(String jsonText) {
maxRangeM: (droneData['maxRangeM'] as num).toDouble(),
maxEnduranceMin: (droneData['maxEnduranceMin'] as num).toDouble(),
maxAltitudeM: (droneData['maxAltitudeM'] as num).toDouble(),
gpsSatGreenMin: numOrDefault('gpsSatGreenMin', DroneProfile.defaultGpsSatGreenMin),
gpsSatRedMin: numOrDefault('gpsSatRedMin', DroneProfile.defaultGpsSatRedMin),
hdopGreenMax: numOrDefault('hdopGreenMax', DroneProfile.defaultHdopGreenMax),
hdopRedMax: numOrDefault('hdopRedMax', DroneProfile.defaultHdopRedMax),
currentGreenMaxA: numOrDefault('currentGreenMaxA', DroneProfile.defaultCurrentGreenMaxA),
currentRedMaxA: numOrDefault('currentRedMaxA', DroneProfile.defaultCurrentRedMaxA),
batteryVoltageGreenMinV: numOrDefault(
'batteryVoltageGreenMinV',
DroneProfile.defaultBatteryVoltageGreenMinV,
),
batteryVoltageRedMinV: numOrDefault(
'batteryVoltageRedMinV',
DroneProfile.defaultBatteryVoltageRedMinV,
),
rxQualityGreenMin: numOrDefault('rxQualityGreenMin', DroneProfile.defaultRxQualityGreenMin),
rxQualityRedMin: numOrDefault('rxQualityRedMin', DroneProfile.defaultRxQualityRedMin),
rxSnrGreenMinDb: numOrDefault('rxSnrGreenMinDb', DroneProfile.defaultRxSnrGreenMinDb),
rxSnrRedMinDb: numOrDefault('rxSnrRedMinDb', DroneProfile.defaultRxSnrRedMinDb),
);
final appVersion = decoded['appVersion'] as String?;
@@ -65,6 +65,52 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
(key: 'maxAltitudeM', label: 'Ceiling', min: 50.0, max: 10000.0, stepBig: 100.0, stepSmall: 10.0, decimals: 0, unit: 'm'),
],
),
// Ampel-Schwellwerte fuer die farbcodierten Telemetriefelder im
// Drohnen-Status-Menue (Doku: "Schwellwerte fuer die Ampel-Kodierung im
// Drohnenprofil anlegen", siehe domain/telemetry/telemetry_field_status.dart) -
// je Feld Gruen-/Rot-Schwelle als eigene Gruppe, analog Climb/Descent oben.
(
title: 'GPS satellites alarm',
fields: [
(key: 'gpsSatGreenMin', label: 'Green from', min: 0.0, max: 20.0, stepBig: 2.0, stepSmall: 1.0, decimals: 0, unit: 'sat'),
(key: 'gpsSatRedMin', label: 'Red below', min: 0.0, max: 20.0, stepBig: 2.0, stepSmall: 1.0, decimals: 0, unit: 'sat'),
],
),
(
title: 'GPS precision (HDOP) alarm',
fields: [
(key: 'hdopGreenMax', label: 'Green up to', min: 0.5, max: 10.0, stepBig: 1.0, stepSmall: 0.1, decimals: 1, unit: ''),
(key: 'hdopRedMax', label: 'Red above', min: 0.5, max: 20.0, stepBig: 1.0, stepSmall: 0.1, decimals: 1, unit: ''),
],
),
(
title: 'Current draw alarm (A)',
fields: [
(key: 'currentGreenMaxA', label: 'Green up to', min: 1.0, max: 200.0, stepBig: 10.0, stepSmall: 1.0, decimals: 0, unit: 'A'),
(key: 'currentRedMaxA', label: 'Red above', min: 1.0, max: 300.0, stepBig: 10.0, stepSmall: 1.0, decimals: 0, unit: 'A'),
],
),
(
title: 'Battery voltage alarm (V)',
fields: [
(key: 'batteryVoltageGreenMinV', label: 'Green from', min: 5.0, max: 60.0, stepBig: 1.0, stepSmall: 0.1, decimals: 1, unit: 'V'),
(key: 'batteryVoltageRedMinV', label: 'Red below', min: 5.0, max: 60.0, stepBig: 1.0, stepSmall: 0.1, decimals: 1, unit: 'V'),
],
),
(
title: 'RX link quality alarm (%)',
fields: [
(key: 'rxQualityGreenMin', label: 'Green from', min: 0.0, max: 100.0, stepBig: 10.0, stepSmall: 1.0, decimals: 0, unit: '%'),
(key: 'rxQualityRedMin', label: 'Red below', min: 0.0, max: 100.0, stepBig: 10.0, stepSmall: 1.0, decimals: 0, unit: '%'),
],
),
(
title: 'RX SNR alarm (dB)',
fields: [
(key: 'rxSnrGreenMinDb', label: 'Green from', min: -30.0, max: 30.0, stepBig: 5.0, stepSmall: 1.0, decimals: 0, unit: 'dB'),
(key: 'rxSnrRedMinDb', label: 'Red below', min: -30.0, max: 30.0, stepBig: 5.0, stepSmall: 1.0, decimals: 0, unit: 'dB'),
],
),
];
double _fieldValue(DroneProfile p, String key) => switch (key) {
@@ -79,6 +125,18 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
'maxRangeM' => p.maxRangeM,
'maxEnduranceMin' => p.maxEnduranceMin,
'maxAltitudeM' => p.maxAltitudeM,
'gpsSatGreenMin' => p.gpsSatGreenMin,
'gpsSatRedMin' => p.gpsSatRedMin,
'hdopGreenMax' => p.hdopGreenMax,
'hdopRedMax' => p.hdopRedMax,
'currentGreenMaxA' => p.currentGreenMaxA,
'currentRedMaxA' => p.currentRedMaxA,
'batteryVoltageGreenMinV' => p.batteryVoltageGreenMinV,
'batteryVoltageRedMinV' => p.batteryVoltageRedMinV,
'rxQualityGreenMin' => p.rxQualityGreenMin,
'rxQualityRedMin' => p.rxQualityRedMin,
'rxSnrGreenMinDb' => p.rxSnrGreenMinDb,
'rxSnrRedMinDb' => p.rxSnrRedMinDb,
_ => throw ArgumentError(key),
};
@@ -120,6 +178,18 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
maxRangeM: v('maxRangeM'),
maxEnduranceMin: v('maxEnduranceMin'),
maxAltitudeM: v('maxAltitudeM'),
gpsSatGreenMin: v('gpsSatGreenMin'),
gpsSatRedMin: v('gpsSatRedMin'),
hdopGreenMax: v('hdopGreenMax'),
hdopRedMax: v('hdopRedMax'),
currentGreenMaxA: v('currentGreenMaxA'),
currentRedMaxA: v('currentRedMaxA'),
batteryVoltageGreenMinV: v('batteryVoltageGreenMinV'),
batteryVoltageRedMinV: v('batteryVoltageRedMinV'),
rxQualityGreenMin: v('rxQualityGreenMin'),
rxQualityRedMin: v('rxQualityRedMin'),
rxSnrGreenMinDb: v('rxSnrGreenMinDb'),
rxSnrRedMinDb: v('rxSnrRedMinDb'),
);
final repo = ref.read(droneProfileRepositoryProvider);
@@ -2,7 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/telemetry/drone_event.dart';
import '../../domain/telemetry/drone_status.dart';
import '../../domain/telemetry/telemetry_field_status.dart';
import '../dmc_colors.dart';
import '../providers/active_drone_profile_provider.dart';
import '../providers/drone_event_log_provider.dart';
import '../providers/telemetry_provider.dart';
@@ -109,36 +112,56 @@ class _DroneStatusWarningsPanelState extends ConsumerState<DroneStatusWarningsPa
Widget _statusTab() {
final telemetry = ref.watch(telemetryProvider);
final droneProfile = ref.watch(activeDroneProfileProvider);
return telemetry.when(
data: (frame) => ListView(
data: (frame) {
final fieldStatus = computeTelemetryFieldStatus(
numSat: frame.numSat,
hasFix: frame.hasFix,
hdop: frame.hdop,
currentA: frame.currentA,
batteryVoltage: frame.batteryVoltage,
linkQuality: frame.linkQuality,
snrDb: frame.snrDb,
profile: droneProfile,
);
return ListView(
padding: const EdgeInsets.all(16),
children: [
_statusRow('GPS satellites', '${frame.numSat}'),
// Farbcodierte Felder zuerst (Doku: "drone status felder im
// menue auch farblich kodieren", Schwellwerte je Drohnenprofil -
// siehe computeTelemetryFieldStatus), informelle Felder ohne
// Ampel-Bewertung (Koordinaten/Heading/Alt/Speed) danach.
_statusRow('GPS satellites', '${frame.numSat}', level: fieldStatus.numSat),
_statusRow(
'GPS precision (HDOP)',
frame.hasFix ? frame.hdop.toStringAsFixed(2) : '—',
level: fieldStatus.hdop,
),
_statusRow(
'Current draw',
'${frame.currentA.toStringAsFixed(1)} A',
level: fieldStatus.currentA,
),
_statusRow(
'Battery voltage',
'${frame.batteryVoltage.toStringAsFixed(2)} V',
level: fieldStatus.batteryVoltage,
),
_statusRow('RX quality', '${frame.linkQuality}%', level: fieldStatus.rxQuality),
_statusRow('RX SNR', '${frame.snrDb} dB', level: fieldStatus.rxSnr),
_statusRow(
'GPS coordinates',
frame.hasFix
? '${frame.lat.toStringAsFixed(6)}, ${frame.lon.toStringAsFixed(6)}'
: '—',
),
_statusRow('Current draw', '${frame.currentA.toStringAsFixed(1)} A'),
_statusRow('Battery voltage', '${frame.batteryVoltage.toStringAsFixed(2)} V'),
_statusRow('Heading', '${frame.headingDeg.round()}°'),
_statusRow('RX quality / SNR', '${frame.linkQuality}% / ${frame.snrDb} dB'),
// Downlink-Qualitaet/-SNR und die Akku-SoC des Sendemoduls selbst
// (nicht der Drohne) sind ueber keinen MSP-GET-Befehl abrufbar -
// rxLinkStatistics_t (iNAV 9.1, rx.h) hat zwar ein downlinkLQ-Feld,
// das wird aber nur per MSP2_COMMON_SET_MSP_RC_LINK_STATS von
// aussen HINEINgeschrieben, nie per GET zurueckgelesen; eine
// Downlink-SNR oder eine Sender-Akkugroesse existiert im Struct
// ueberhaupt nicht (verifiziert im lokalen iNAV-Checkout,
// release/9.1).
_statusRow('TX battery SoC / quality / SNR', 'Not available (not exposed via MSP)'),
_statusRow('Altitude', '${frame.altitudeM.toStringAsFixed(0)} m'),
_statusRow('Speed', '${frame.speedMs.toStringAsFixed(1)} m/s'),
],
),
);
},
loading: () => const Center(
child: CircularProgressIndicator(color: Color(0xFF4D9DFF)),
),
@@ -148,7 +171,14 @@ class _DroneStatusWarningsPanelState extends ConsumerState<DroneStatusWarningsPa
);
}
Widget _statusRow(String label, String value) {
static const _signalColors = {
SignalLevel.green: DmcColors.flyModeColor,
SignalLevel.yellow: DmcColors.pafAmber,
SignalLevel.red: DmcColors.warnRed,
};
Widget _statusRow(String label, String value, {SignalLevel? level}) {
final valueColor = level != null ? _signalColors[level]! : Colors.white;
return Container(
margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
@@ -159,7 +189,15 @@ class _DroneStatusWarningsPanelState extends ConsumerState<DroneStatusWarningsPa
child: Row(
children: [
Expanded(child: Text(label, style: const TextStyle(color: Colors.white70))),
Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600)),
if (level != null) ...[
Container(
width: 8,
height: 8,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(color: valueColor, shape: BoxShape.circle),
),
],
Text(value, style: TextStyle(color: valueColor, fontWeight: FontWeight.w600)),
],
),
);