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.maxRangeM,
|
||||||
required this.maxEnduranceMin,
|
required this.maxEnduranceMin,
|
||||||
required this.maxAltitudeM,
|
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.
|
/// Null fuer ein noch nicht gespeichertes (neu angelegtes) Profil.
|
||||||
@@ -34,6 +46,42 @@ class DroneProfile {
|
|||||||
final double maxEnduranceMin;
|
final double maxEnduranceMin;
|
||||||
final double maxAltitudeM;
|
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
|
/// T1 Ranger (Heewing) - Standardprofil (Doku 3.5), analog DEFAULT_DRONE
|
||||||
/// im HTML-Demonstrator.
|
/// im HTML-Demonstrator.
|
||||||
static const _t1RangerName = 'T1 Ranger';
|
static const _t1RangerName = 'T1 Ranger';
|
||||||
@@ -76,6 +124,18 @@ class DroneProfile {
|
|||||||
double? maxRangeM,
|
double? maxRangeM,
|
||||||
double? maxEnduranceMin,
|
double? maxEnduranceMin,
|
||||||
double? maxAltitudeM,
|
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(
|
return DroneProfile(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -91,6 +151,18 @@ class DroneProfile {
|
|||||||
maxRangeM: maxRangeM ?? this.maxRangeM,
|
maxRangeM: maxRangeM ?? this.maxRangeM,
|
||||||
maxEnduranceMin: maxEnduranceMin ?? this.maxEnduranceMin,
|
maxEnduranceMin: maxEnduranceMin ?? this.maxEnduranceMin,
|
||||||
maxAltitudeM: maxAltitudeM ?? this.maxAltitudeM,
|
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 maxRangeM => real()();
|
||||||
RealColumn get maxEnduranceMin => real()();
|
RealColumn get maxEnduranceMin => real()();
|
||||||
RealColumn get maxAltitudeM => 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
|
/// Einzeilige Einstellungstabelle (id fest 0) fuer app-weite Auswahl, die
|
||||||
@@ -110,7 +136,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
AppDatabase.forTesting(super.executor);
|
AppDatabase.forTesting(super.executor);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 7;
|
int get schemaVersion => 8;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -157,6 +183,26 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
if (from < 7) {
|
if (from < 7) {
|
||||||
await m.addColumn(missions, missions.terrainProfileJson);
|
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,
|
maxRangeM: row.maxRangeM,
|
||||||
maxEnduranceMin: row.maxEnduranceMin,
|
maxEnduranceMin: row.maxEnduranceMin,
|
||||||
maxAltitudeM: row.maxAltitudeM,
|
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) =>
|
DroneProfilesCompanion _toCompanion(domain.DroneProfile profile) =>
|
||||||
@@ -76,5 +88,17 @@ class DroneProfileRepository {
|
|||||||
maxRangeM: Value(profile.maxRangeM),
|
maxRangeM: Value(profile.maxRangeM),
|
||||||
maxEnduranceMin: Value(profile.maxEnduranceMin),
|
maxEnduranceMin: Value(profile.maxEnduranceMin),
|
||||||
maxAltitudeM: Value(profile.maxAltitudeM),
|
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,
|
'maxRangeM': profile.maxRangeM,
|
||||||
'maxEnduranceMin': profile.maxEnduranceMin,
|
'maxEnduranceMin': profile.maxEnduranceMin,
|
||||||
'maxAltitudeM': profile.maxAltitudeM,
|
'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);
|
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(
|
final profile = DroneProfile(
|
||||||
name: name,
|
name: name,
|
||||||
minSpeed: (droneData['minSpeed'] as num).toDouble(),
|
minSpeed: (droneData['minSpeed'] as num).toDouble(),
|
||||||
@@ -101,6 +122,24 @@ ParsedDroneImport parseDroneImportJson(String jsonText) {
|
|||||||
maxRangeM: (droneData['maxRangeM'] as num).toDouble(),
|
maxRangeM: (droneData['maxRangeM'] as num).toDouble(),
|
||||||
maxEnduranceMin: (droneData['maxEnduranceMin'] as num).toDouble(),
|
maxEnduranceMin: (droneData['maxEnduranceMin'] as num).toDouble(),
|
||||||
maxAltitudeM: (droneData['maxAltitudeM'] 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?;
|
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'),
|
(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) {
|
double _fieldValue(DroneProfile p, String key) => switch (key) {
|
||||||
@@ -79,6 +125,18 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
|
|||||||
'maxRangeM' => p.maxRangeM,
|
'maxRangeM' => p.maxRangeM,
|
||||||
'maxEnduranceMin' => p.maxEnduranceMin,
|
'maxEnduranceMin' => p.maxEnduranceMin,
|
||||||
'maxAltitudeM' => p.maxAltitudeM,
|
'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),
|
_ => throw ArgumentError(key),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -120,6 +178,18 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
|
|||||||
maxRangeM: v('maxRangeM'),
|
maxRangeM: v('maxRangeM'),
|
||||||
maxEnduranceMin: v('maxEnduranceMin'),
|
maxEnduranceMin: v('maxEnduranceMin'),
|
||||||
maxAltitudeM: v('maxAltitudeM'),
|
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);
|
final repo = ref.read(droneProfileRepositoryProvider);
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import '../../domain/telemetry/drone_event.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 '../dmc_colors.dart';
|
||||||
|
import '../providers/active_drone_profile_provider.dart';
|
||||||
import '../providers/drone_event_log_provider.dart';
|
import '../providers/drone_event_log_provider.dart';
|
||||||
import '../providers/telemetry_provider.dart';
|
import '../providers/telemetry_provider.dart';
|
||||||
|
|
||||||
@@ -109,36 +112,56 @@ class _DroneStatusWarningsPanelState extends ConsumerState<DroneStatusWarningsPa
|
|||||||
|
|
||||||
Widget _statusTab() {
|
Widget _statusTab() {
|
||||||
final telemetry = ref.watch(telemetryProvider);
|
final telemetry = ref.watch(telemetryProvider);
|
||||||
|
final droneProfile = ref.watch(activeDroneProfileProvider);
|
||||||
return telemetry.when(
|
return telemetry.when(
|
||||||
data: (frame) => ListView(
|
data: (frame) {
|
||||||
padding: const EdgeInsets.all(16),
|
final fieldStatus = computeTelemetryFieldStatus(
|
||||||
children: [
|
numSat: frame.numSat,
|
||||||
_statusRow('GPS satellites', '${frame.numSat}'),
|
hasFix: frame.hasFix,
|
||||||
_statusRow(
|
hdop: frame.hdop,
|
||||||
'GPS precision (HDOP)',
|
currentA: frame.currentA,
|
||||||
frame.hasFix ? frame.hdop.toStringAsFixed(2) : '—',
|
batteryVoltage: frame.batteryVoltage,
|
||||||
),
|
linkQuality: frame.linkQuality,
|
||||||
_statusRow(
|
snrDb: frame.snrDb,
|
||||||
'GPS coordinates',
|
profile: droneProfile,
|
||||||
frame.hasFix
|
);
|
||||||
? '${frame.lat.toStringAsFixed(6)}, ${frame.lon.toStringAsFixed(6)}'
|
return ListView(
|
||||||
: '—',
|
padding: const EdgeInsets.all(16),
|
||||||
),
|
children: [
|
||||||
_statusRow('Current draw', '${frame.currentA.toStringAsFixed(1)} A'),
|
// Farbcodierte Felder zuerst (Doku: "drone status felder im
|
||||||
_statusRow('Battery voltage', '${frame.batteryVoltage.toStringAsFixed(2)} V'),
|
// menue auch farblich kodieren", Schwellwerte je Drohnenprofil -
|
||||||
_statusRow('Heading', '${frame.headingDeg.round()}°'),
|
// siehe computeTelemetryFieldStatus), informelle Felder ohne
|
||||||
_statusRow('RX quality / SNR', '${frame.linkQuality}% / ${frame.snrDb} dB'),
|
// Ampel-Bewertung (Koordinaten/Heading/Alt/Speed) danach.
|
||||||
// Downlink-Qualitaet/-SNR und die Akku-SoC des Sendemoduls selbst
|
_statusRow('GPS satellites', '${frame.numSat}', level: fieldStatus.numSat),
|
||||||
// (nicht der Drohne) sind ueber keinen MSP-GET-Befehl abrufbar -
|
_statusRow(
|
||||||
// rxLinkStatistics_t (iNAV 9.1, rx.h) hat zwar ein downlinkLQ-Feld,
|
'GPS precision (HDOP)',
|
||||||
// das wird aber nur per MSP2_COMMON_SET_MSP_RC_LINK_STATS von
|
frame.hasFix ? frame.hdop.toStringAsFixed(2) : '—',
|
||||||
// aussen HINEINgeschrieben, nie per GET zurueckgelesen; eine
|
level: fieldStatus.hdop,
|
||||||
// Downlink-SNR oder eine Sender-Akkugroesse existiert im Struct
|
),
|
||||||
// ueberhaupt nicht (verifiziert im lokalen iNAV-Checkout,
|
_statusRow(
|
||||||
// release/9.1).
|
'Current draw',
|
||||||
_statusRow('TX battery SoC / quality / SNR', 'Not available (not exposed via MSP)'),
|
'${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(
|
loading: () => const Center(
|
||||||
child: CircularProgressIndicator(color: Color(0xFF4D9DFF)),
|
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(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
@@ -159,7 +189,15 @@ class _DroneStatusWarningsPanelState extends ConsumerState<DroneStatusWarningsPa
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text(label, style: const TextStyle(color: Colors.white70))),
|
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