Warning thresholds added in drone profile. traffic light indication in drone satus menue added. layout and content optimized
This commit is contained in:
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
_statusRow('GPS satellites', '${frame.numSat}'),
|
||||
_statusRow(
|
||||
'GPS precision (HDOP)',
|
||||
frame.hasFix ? frame.hdop.toStringAsFixed(2) : '—',
|
||||
),
|
||||
_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)'),
|
||||
],
|
||||
),
|
||||
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: [
|
||||
// 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('Heading', '${frame.headingDeg.round()}°'),
|
||||
_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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user