System messages fuer GPS-Fix und Drone connected/disconnected, Batterie-Schwellwerte auf Pro-Zelle umgestellt

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Constantin Leue
2026-08-06 13:06:49 +02:00
co-authored by Claude Sonnet 5
parent b68c16d8e6
commit e902197462
15 changed files with 632 additions and 58 deletions
+40 -10
View File
@@ -23,8 +23,9 @@ class DroneProfile {
this.hdopRedMax = defaultHdopRedMax,
this.currentGreenMaxA = defaultCurrentGreenMaxA,
this.currentRedMaxA = defaultCurrentRedMaxA,
this.batteryVoltageGreenMinV = defaultBatteryVoltageGreenMinV,
this.batteryVoltageRedMinV = defaultBatteryVoltageRedMinV,
this.batteryCellCount = defaultBatteryCellCount,
this.batteryVoltageGreenMinPerCellV = defaultBatteryVoltageGreenMinPerCellV,
this.batteryVoltageRedMinPerCellV = defaultBatteryVoltageRedMinPerCellV,
this.rxQualityGreenMin = defaultRxQualityGreenMin,
this.rxQualityRedMin = defaultRxQualityRedMin,
this.rxSnrGreenMinDb = defaultRxSnrGreenMinDb,
@@ -62,8 +63,23 @@ class DroneProfile {
final double hdopRedMax;
final double currentGreenMaxA;
final double currentRedMaxA;
final double batteryVoltageGreenMinV;
final double batteryVoltageRedMinV;
/// Zellenzahl des LiPo-Akkus (z.B. 4 fuer "4S") - bestimmt zusammen mit
/// den beiden *PerCellV-Schwellwerten die tatsaechliche Alarmspannung des
/// Packs (siehe [batteryVoltageGreenMinV]/[batteryVoltageRedMinV]). Vorher
/// war die Alarmspannung als feste Pack-Gesamtspannung hinterlegt - bei
/// einem Wechsel der Zellenzahl (z.B. 4S statt 6S) waeren die alten
/// Schwellwerte dann fuer den neuen Pack sinnlos gewesen, ohne dass beim
/// Bearbeiten des Profils ein Hinweis darauf bestanden haette.
final int batteryCellCount;
/// Alarm-Schwellwerte PRO ZELLE (LiPo-Standardwerte als Default: 3.4V
/// gruen/"noch sicher", 3.2V rot/"jetzt landen") - siehe
/// [batteryVoltageGreenMinV]/[batteryVoltageRedMinV] fuer die daraus
/// abgeleitete tatsaechliche Pack-Alarmspannung.
final double batteryVoltageGreenMinPerCellV;
final double batteryVoltageRedMinPerCellV;
final double rxQualityGreenMin;
final double rxQualityRedMin;
final double rxSnrGreenMinDb;
@@ -75,13 +91,22 @@ class DroneProfile {
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 defaultBatteryCellCount = 4;
static const defaultBatteryVoltageGreenMinPerCellV = 3.4;
static const defaultBatteryVoltageRedMinPerCellV = 3.2;
static const defaultRxQualityGreenMin = 70.0;
static const defaultRxQualityRedMin = 50.0;
static const defaultRxSnrGreenMinDb = 0.0;
static const defaultRxSnrRedMinDb = -10.0;
/// Tatsaechliche Pack-Alarmspannung, aus Zellenzahl x Pro-Zelle-Schwelle
/// berechnet - [telemetry_field_status.dart] vergleicht damit die vom FC
/// gemeldete Gesamtspannung, kennt selbst aber keine Zellenzahl.
double get batteryVoltageGreenMinV =>
batteryCellCount * batteryVoltageGreenMinPerCellV;
double get batteryVoltageRedMinV =>
batteryCellCount * batteryVoltageRedMinPerCellV;
/// T1 Ranger (Heewing) - Standardprofil (Doku 3.5), analog DEFAULT_DRONE
/// im HTML-Demonstrator.
static const _t1RangerName = 'T1 Ranger';
@@ -99,6 +124,7 @@ class DroneProfile {
maxRangeM: 15000,
maxEnduranceMin: 45,
maxAltitudeM: 2000,
batteryCellCount: 4,
);
/// Einzeilige Zusammenfassung fuer die Profilliste (Doku 3.5), analog
@@ -130,8 +156,9 @@ class DroneProfile {
double? hdopRedMax,
double? currentGreenMaxA,
double? currentRedMaxA,
double? batteryVoltageGreenMinV,
double? batteryVoltageRedMinV,
int? batteryCellCount,
double? batteryVoltageGreenMinPerCellV,
double? batteryVoltageRedMinPerCellV,
double? rxQualityGreenMin,
double? rxQualityRedMin,
double? rxSnrGreenMinDb,
@@ -157,8 +184,11 @@ class DroneProfile {
hdopRedMax: hdopRedMax ?? this.hdopRedMax,
currentGreenMaxA: currentGreenMaxA ?? this.currentGreenMaxA,
currentRedMaxA: currentRedMaxA ?? this.currentRedMaxA,
batteryVoltageGreenMinV: batteryVoltageGreenMinV ?? this.batteryVoltageGreenMinV,
batteryVoltageRedMinV: batteryVoltageRedMinV ?? this.batteryVoltageRedMinV,
batteryCellCount: batteryCellCount ?? this.batteryCellCount,
batteryVoltageGreenMinPerCellV:
batteryVoltageGreenMinPerCellV ?? this.batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV:
batteryVoltageRedMinPerCellV ?? this.batteryVoltageRedMinPerCellV,
rxQualityGreenMin: rxQualityGreenMin ?? this.rxQualityGreenMin,
rxQualityRedMin: rxQualityRedMin ?? this.rxQualityRedMin,
rxSnrGreenMinDb: rxSnrGreenMinDb ?? this.rxSnrGreenMinDb,
+11 -3
View File
@@ -3,9 +3,9 @@ import '../../transport/flight_controller_link.dart';
/// Schweregrad eines Eintrags im System-Messages-Log (Doku: "log von
/// kritischen ereignissen mit time stamp ... connection lost, battery
/// low/critical, fail safe mode activated", erweitert um allgemeine
/// Systemmeldungen wie Verbindungsauf-/-abbau, Verbindungsart-Wechsel und
/// Missionsuebertragung). [info] ist fuer Meldungen ohne Alarmcharakter -
/// reine Statusinformation, kein Warn-/Fehlerzustand.
/// Systemmeldungen wie Verbindungsauf-/-abbau, GPS-Fix, Verbindungsart-
/// Wechsel und Missionsuebertragung). [info] ist fuer Meldungen ohne
/// Alarmcharakter - reine Statusinformation, kein Warn-/Fehlerzustand.
enum SystemMessageSeverity { info, warning, critical }
/// Ein einzelner Log-Eintrag mit Zeitstempel (Doku: "System Messages Tab
@@ -72,5 +72,13 @@ List<SystemMessage> detectSystemMessages({
));
}
if (previous != null && !previous.hasFix && current.hasFix) {
events.add(SystemMessage(
timestamp: now,
message: 'GPS fix acquired (${current.numSat} satellites)',
severity: SystemMessageSeverity.info,
));
}
return events;
}
+28 -1
View File
@@ -64,10 +64,26 @@ class DroneProfiles extends Table {
real().withDefault(const Constant(20.0))();
RealColumn get currentRedMaxA =>
real().withDefault(const Constant(30.0))();
/// Alte, feste Pack-Alarmspannungen (Schema v8) - seit Schema v9 durch
/// batteryCellCount + die *PerCellV-Spalten ersetzt (siehe DroneProfile-
/// Doku). Bewusst NICHT geloescht (diese Codebase migriert additiv, siehe
/// die anderen Schema-Schritte hier), aber von Repository/App nicht mehr
/// gelesen oder geschrieben.
RealColumn get batteryVoltageGreenMinV =>
real().withDefault(const Constant(20.4))();
RealColumn get batteryVoltageRedMinV =>
real().withDefault(const Constant(19.2))();
/// Zellenzahl des LiPo-Akkus (Schema v9, siehe DroneProfile-Doku) - Default
/// 4 passend zum neuen T1-Ranger-Standardprofil (4S).
IntColumn get batteryCellCount =>
integer().withDefault(const Constant(4))();
RealColumn get batteryVoltageGreenMinPerCellV =>
real().withDefault(const Constant(3.4))();
RealColumn get batteryVoltageRedMinPerCellV =>
real().withDefault(const Constant(3.2))();
RealColumn get rxQualityGreenMin =>
real().withDefault(const Constant(70.0))();
RealColumn get rxQualityRedMin =>
@@ -136,7 +152,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase.forTesting(super.executor);
@override
int get schemaVersion => 8;
int get schemaVersion => 9;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -203,6 +219,17 @@ class AppDatabase extends _$AppDatabase {
await m.addColumn(droneProfiles, droneProfiles.rxSnrGreenMinDb);
await m.addColumn(droneProfiles, droneProfiles.rxSnrRedMinDb);
}
if (from < 9) {
await m.addColumn(droneProfiles, droneProfiles.batteryCellCount);
await m.addColumn(
droneProfiles,
droneProfiles.batteryVoltageGreenMinPerCellV,
);
await m.addColumn(
droneProfiles,
droneProfiles.batteryVoltageRedMinPerCellV,
);
}
},
);
@@ -817,6 +817,42 @@ class $DroneProfilesTable extends DroneProfiles
requiredDuringInsert: false,
defaultValue: const Constant(19.2),
);
static const VerificationMeta _batteryCellCountMeta = const VerificationMeta(
'batteryCellCount',
);
@override
late final GeneratedColumn<int> batteryCellCount = GeneratedColumn<int>(
'battery_cell_count',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(4),
);
static const VerificationMeta _batteryVoltageGreenMinPerCellVMeta =
const VerificationMeta('batteryVoltageGreenMinPerCellV');
@override
late final GeneratedColumn<double> batteryVoltageGreenMinPerCellV =
GeneratedColumn<double>(
'battery_voltage_green_min_per_cell_v',
aliasedName,
false,
type: DriftSqlType.double,
requiredDuringInsert: false,
defaultValue: const Constant(3.4),
);
static const VerificationMeta _batteryVoltageRedMinPerCellVMeta =
const VerificationMeta('batteryVoltageRedMinPerCellV');
@override
late final GeneratedColumn<double> batteryVoltageRedMinPerCellV =
GeneratedColumn<double>(
'battery_voltage_red_min_per_cell_v',
aliasedName,
false,
type: DriftSqlType.double,
requiredDuringInsert: false,
defaultValue: const Constant(3.2),
);
static const VerificationMeta _rxQualityGreenMinMeta = const VerificationMeta(
'rxQualityGreenMin',
);
@@ -889,6 +925,9 @@ class $DroneProfilesTable extends DroneProfiles
currentRedMaxA,
batteryVoltageGreenMinV,
batteryVoltageRedMinV,
batteryCellCount,
batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV,
rxQualityGreenMin,
rxQualityRedMin,
rxSnrGreenMinDb,
@@ -1101,6 +1140,33 @@ class $DroneProfilesTable extends DroneProfiles
),
);
}
if (data.containsKey('battery_cell_count')) {
context.handle(
_batteryCellCountMeta,
batteryCellCount.isAcceptableOrUnknown(
data['battery_cell_count']!,
_batteryCellCountMeta,
),
);
}
if (data.containsKey('battery_voltage_green_min_per_cell_v')) {
context.handle(
_batteryVoltageGreenMinPerCellVMeta,
batteryVoltageGreenMinPerCellV.isAcceptableOrUnknown(
data['battery_voltage_green_min_per_cell_v']!,
_batteryVoltageGreenMinPerCellVMeta,
),
);
}
if (data.containsKey('battery_voltage_red_min_per_cell_v')) {
context.handle(
_batteryVoltageRedMinPerCellVMeta,
batteryVoltageRedMinPerCellV.isAcceptableOrUnknown(
data['battery_voltage_red_min_per_cell_v']!,
_batteryVoltageRedMinPerCellVMeta,
),
);
}
if (data.containsKey('rx_quality_green_min')) {
context.handle(
_rxQualityGreenMinMeta,
@@ -1230,6 +1296,18 @@ class $DroneProfilesTable extends DroneProfiles
DriftSqlType.double,
data['${effectivePrefix}battery_voltage_red_min_v'],
)!,
batteryCellCount: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}battery_cell_count'],
)!,
batteryVoltageGreenMinPerCellV: attachedDatabase.typeMapping.read(
DriftSqlType.double,
data['${effectivePrefix}battery_voltage_green_min_per_cell_v'],
)!,
batteryVoltageRedMinPerCellV: attachedDatabase.typeMapping.read(
DriftSqlType.double,
data['${effectivePrefix}battery_voltage_red_min_per_cell_v'],
)!,
rxQualityGreenMin: attachedDatabase.typeMapping.read(
DriftSqlType.double,
data['${effectivePrefix}rx_quality_green_min'],
@@ -1280,8 +1358,20 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
final double hdopRedMax;
final double currentGreenMaxA;
final double currentRedMaxA;
/// Alte, feste Pack-Alarmspannungen (Schema v8) - seit Schema v9 durch
/// batteryCellCount + die *PerCellV-Spalten ersetzt (siehe DroneProfile-
/// Doku). Bewusst NICHT geloescht (diese Codebase migriert additiv, siehe
/// die anderen Schema-Schritte hier), aber von Repository/App nicht mehr
/// gelesen oder geschrieben.
final double batteryVoltageGreenMinV;
final double batteryVoltageRedMinV;
/// Zellenzahl des LiPo-Akkus (Schema v9, siehe DroneProfile-Doku) - Default
/// 4 passend zum neuen T1-Ranger-Standardprofil (4S).
final int batteryCellCount;
final double batteryVoltageGreenMinPerCellV;
final double batteryVoltageRedMinPerCellV;
final double rxQualityGreenMin;
final double rxQualityRedMin;
final double rxSnrGreenMinDb;
@@ -1308,6 +1398,9 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
required this.currentRedMaxA,
required this.batteryVoltageGreenMinV,
required this.batteryVoltageRedMinV,
required this.batteryCellCount,
required this.batteryVoltageGreenMinPerCellV,
required this.batteryVoltageRedMinPerCellV,
required this.rxQualityGreenMin,
required this.rxQualityRedMin,
required this.rxSnrGreenMinDb,
@@ -1339,6 +1432,13 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
batteryVoltageGreenMinV,
);
map['battery_voltage_red_min_v'] = Variable<double>(batteryVoltageRedMinV);
map['battery_cell_count'] = Variable<int>(batteryCellCount);
map['battery_voltage_green_min_per_cell_v'] = Variable<double>(
batteryVoltageGreenMinPerCellV,
);
map['battery_voltage_red_min_per_cell_v'] = Variable<double>(
batteryVoltageRedMinPerCellV,
);
map['rx_quality_green_min'] = Variable<double>(rxQualityGreenMin);
map['rx_quality_red_min'] = Variable<double>(rxQualityRedMin);
map['rx_snr_green_min_db'] = Variable<double>(rxSnrGreenMinDb);
@@ -1369,6 +1469,9 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
currentRedMaxA: Value(currentRedMaxA),
batteryVoltageGreenMinV: Value(batteryVoltageGreenMinV),
batteryVoltageRedMinV: Value(batteryVoltageRedMinV),
batteryCellCount: Value(batteryCellCount),
batteryVoltageGreenMinPerCellV: Value(batteryVoltageGreenMinPerCellV),
batteryVoltageRedMinPerCellV: Value(batteryVoltageRedMinPerCellV),
rxQualityGreenMin: Value(rxQualityGreenMin),
rxQualityRedMin: Value(rxQualityRedMin),
rxSnrGreenMinDb: Value(rxSnrGreenMinDb),
@@ -1407,6 +1510,13 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
batteryVoltageRedMinV: serializer.fromJson<double>(
json['batteryVoltageRedMinV'],
),
batteryCellCount: serializer.fromJson<int>(json['batteryCellCount']),
batteryVoltageGreenMinPerCellV: serializer.fromJson<double>(
json['batteryVoltageGreenMinPerCellV'],
),
batteryVoltageRedMinPerCellV: serializer.fromJson<double>(
json['batteryVoltageRedMinPerCellV'],
),
rxQualityGreenMin: serializer.fromJson<double>(json['rxQualityGreenMin']),
rxQualityRedMin: serializer.fromJson<double>(json['rxQualityRedMin']),
rxSnrGreenMinDb: serializer.fromJson<double>(json['rxSnrGreenMinDb']),
@@ -1440,6 +1550,13 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
batteryVoltageGreenMinV,
),
'batteryVoltageRedMinV': serializer.toJson<double>(batteryVoltageRedMinV),
'batteryCellCount': serializer.toJson<int>(batteryCellCount),
'batteryVoltageGreenMinPerCellV': serializer.toJson<double>(
batteryVoltageGreenMinPerCellV,
),
'batteryVoltageRedMinPerCellV': serializer.toJson<double>(
batteryVoltageRedMinPerCellV,
),
'rxQualityGreenMin': serializer.toJson<double>(rxQualityGreenMin),
'rxQualityRedMin': serializer.toJson<double>(rxQualityRedMin),
'rxSnrGreenMinDb': serializer.toJson<double>(rxSnrGreenMinDb),
@@ -1469,6 +1586,9 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
double? currentRedMaxA,
double? batteryVoltageGreenMinV,
double? batteryVoltageRedMinV,
int? batteryCellCount,
double? batteryVoltageGreenMinPerCellV,
double? batteryVoltageRedMinPerCellV,
double? rxQualityGreenMin,
double? rxQualityRedMin,
double? rxSnrGreenMinDb,
@@ -1496,6 +1616,11 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
batteryVoltageGreenMinV:
batteryVoltageGreenMinV ?? this.batteryVoltageGreenMinV,
batteryVoltageRedMinV: batteryVoltageRedMinV ?? this.batteryVoltageRedMinV,
batteryCellCount: batteryCellCount ?? this.batteryCellCount,
batteryVoltageGreenMinPerCellV:
batteryVoltageGreenMinPerCellV ?? this.batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV:
batteryVoltageRedMinPerCellV ?? this.batteryVoltageRedMinPerCellV,
rxQualityGreenMin: rxQualityGreenMin ?? this.rxQualityGreenMin,
rxQualityRedMin: rxQualityRedMin ?? this.rxQualityRedMin,
rxSnrGreenMinDb: rxSnrGreenMinDb ?? this.rxSnrGreenMinDb,
@@ -1556,6 +1681,16 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
batteryVoltageRedMinV: data.batteryVoltageRedMinV.present
? data.batteryVoltageRedMinV.value
: this.batteryVoltageRedMinV,
batteryCellCount: data.batteryCellCount.present
? data.batteryCellCount.value
: this.batteryCellCount,
batteryVoltageGreenMinPerCellV:
data.batteryVoltageGreenMinPerCellV.present
? data.batteryVoltageGreenMinPerCellV.value
: this.batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV: data.batteryVoltageRedMinPerCellV.present
? data.batteryVoltageRedMinPerCellV.value
: this.batteryVoltageRedMinPerCellV,
rxQualityGreenMin: data.rxQualityGreenMin.present
? data.rxQualityGreenMin.value
: this.rxQualityGreenMin,
@@ -1595,6 +1730,13 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
..write('currentRedMaxA: $currentRedMaxA, ')
..write('batteryVoltageGreenMinV: $batteryVoltageGreenMinV, ')
..write('batteryVoltageRedMinV: $batteryVoltageRedMinV, ')
..write('batteryCellCount: $batteryCellCount, ')
..write(
'batteryVoltageGreenMinPerCellV: $batteryVoltageGreenMinPerCellV, ',
)
..write(
'batteryVoltageRedMinPerCellV: $batteryVoltageRedMinPerCellV, ',
)
..write('rxQualityGreenMin: $rxQualityGreenMin, ')
..write('rxQualityRedMin: $rxQualityRedMin, ')
..write('rxSnrGreenMinDb: $rxSnrGreenMinDb, ')
@@ -1626,6 +1768,9 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
currentRedMaxA,
batteryVoltageGreenMinV,
batteryVoltageRedMinV,
batteryCellCount,
batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV,
rxQualityGreenMin,
rxQualityRedMin,
rxSnrGreenMinDb,
@@ -1656,6 +1801,11 @@ class DroneProfile extends DataClass implements Insertable<DroneProfile> {
other.currentRedMaxA == this.currentRedMaxA &&
other.batteryVoltageGreenMinV == this.batteryVoltageGreenMinV &&
other.batteryVoltageRedMinV == this.batteryVoltageRedMinV &&
other.batteryCellCount == this.batteryCellCount &&
other.batteryVoltageGreenMinPerCellV ==
this.batteryVoltageGreenMinPerCellV &&
other.batteryVoltageRedMinPerCellV ==
this.batteryVoltageRedMinPerCellV &&
other.rxQualityGreenMin == this.rxQualityGreenMin &&
other.rxQualityRedMin == this.rxQualityRedMin &&
other.rxSnrGreenMinDb == this.rxSnrGreenMinDb &&
@@ -1684,6 +1834,9 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
final Value<double> currentRedMaxA;
final Value<double> batteryVoltageGreenMinV;
final Value<double> batteryVoltageRedMinV;
final Value<int> batteryCellCount;
final Value<double> batteryVoltageGreenMinPerCellV;
final Value<double> batteryVoltageRedMinPerCellV;
final Value<double> rxQualityGreenMin;
final Value<double> rxQualityRedMin;
final Value<double> rxSnrGreenMinDb;
@@ -1710,6 +1863,9 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
this.currentRedMaxA = const Value.absent(),
this.batteryVoltageGreenMinV = const Value.absent(),
this.batteryVoltageRedMinV = const Value.absent(),
this.batteryCellCount = const Value.absent(),
this.batteryVoltageGreenMinPerCellV = const Value.absent(),
this.batteryVoltageRedMinPerCellV = const Value.absent(),
this.rxQualityGreenMin = const Value.absent(),
this.rxQualityRedMin = const Value.absent(),
this.rxSnrGreenMinDb = const Value.absent(),
@@ -1737,6 +1893,9 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
this.currentRedMaxA = const Value.absent(),
this.batteryVoltageGreenMinV = const Value.absent(),
this.batteryVoltageRedMinV = const Value.absent(),
this.batteryCellCount = const Value.absent(),
this.batteryVoltageGreenMinPerCellV = const Value.absent(),
this.batteryVoltageRedMinPerCellV = const Value.absent(),
this.rxQualityGreenMin = const Value.absent(),
this.rxQualityRedMin = const Value.absent(),
this.rxSnrGreenMinDb = const Value.absent(),
@@ -1775,6 +1934,9 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
Expression<double>? currentRedMaxA,
Expression<double>? batteryVoltageGreenMinV,
Expression<double>? batteryVoltageRedMinV,
Expression<int>? batteryCellCount,
Expression<double>? batteryVoltageGreenMinPerCellV,
Expression<double>? batteryVoltageRedMinPerCellV,
Expression<double>? rxQualityGreenMin,
Expression<double>? rxQualityRedMin,
Expression<double>? rxSnrGreenMinDb,
@@ -1804,6 +1966,11 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
'battery_voltage_green_min_v': batteryVoltageGreenMinV,
if (batteryVoltageRedMinV != null)
'battery_voltage_red_min_v': batteryVoltageRedMinV,
if (batteryCellCount != null) 'battery_cell_count': batteryCellCount,
if (batteryVoltageGreenMinPerCellV != null)
'battery_voltage_green_min_per_cell_v': batteryVoltageGreenMinPerCellV,
if (batteryVoltageRedMinPerCellV != null)
'battery_voltage_red_min_per_cell_v': batteryVoltageRedMinPerCellV,
if (rxQualityGreenMin != null) 'rx_quality_green_min': rxQualityGreenMin,
if (rxQualityRedMin != null) 'rx_quality_red_min': rxQualityRedMin,
if (rxSnrGreenMinDb != null) 'rx_snr_green_min_db': rxSnrGreenMinDb,
@@ -1833,6 +2000,9 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
Value<double>? currentRedMaxA,
Value<double>? batteryVoltageGreenMinV,
Value<double>? batteryVoltageRedMinV,
Value<int>? batteryCellCount,
Value<double>? batteryVoltageGreenMinPerCellV,
Value<double>? batteryVoltageRedMinPerCellV,
Value<double>? rxQualityGreenMin,
Value<double>? rxQualityRedMin,
Value<double>? rxSnrGreenMinDb,
@@ -1862,6 +2032,11 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
batteryVoltageGreenMinV ?? this.batteryVoltageGreenMinV,
batteryVoltageRedMinV:
batteryVoltageRedMinV ?? this.batteryVoltageRedMinV,
batteryCellCount: batteryCellCount ?? this.batteryCellCount,
batteryVoltageGreenMinPerCellV:
batteryVoltageGreenMinPerCellV ?? this.batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV:
batteryVoltageRedMinPerCellV ?? this.batteryVoltageRedMinPerCellV,
rxQualityGreenMin: rxQualityGreenMin ?? this.rxQualityGreenMin,
rxQualityRedMin: rxQualityRedMin ?? this.rxQualityRedMin,
rxSnrGreenMinDb: rxSnrGreenMinDb ?? this.rxSnrGreenMinDb,
@@ -1939,6 +2114,19 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
batteryVoltageRedMinV.value,
);
}
if (batteryCellCount.present) {
map['battery_cell_count'] = Variable<int>(batteryCellCount.value);
}
if (batteryVoltageGreenMinPerCellV.present) {
map['battery_voltage_green_min_per_cell_v'] = Variable<double>(
batteryVoltageGreenMinPerCellV.value,
);
}
if (batteryVoltageRedMinPerCellV.present) {
map['battery_voltage_red_min_per_cell_v'] = Variable<double>(
batteryVoltageRedMinPerCellV.value,
);
}
if (rxQualityGreenMin.present) {
map['rx_quality_green_min'] = Variable<double>(rxQualityGreenMin.value);
}
@@ -1978,6 +2166,13 @@ class DroneProfilesCompanion extends UpdateCompanion<DroneProfile> {
..write('currentRedMaxA: $currentRedMaxA, ')
..write('batteryVoltageGreenMinV: $batteryVoltageGreenMinV, ')
..write('batteryVoltageRedMinV: $batteryVoltageRedMinV, ')
..write('batteryCellCount: $batteryCellCount, ')
..write(
'batteryVoltageGreenMinPerCellV: $batteryVoltageGreenMinPerCellV, ',
)
..write(
'batteryVoltageRedMinPerCellV: $batteryVoltageRedMinPerCellV, ',
)
..write('rxQualityGreenMin: $rxQualityGreenMin, ')
..write('rxQualityRedMin: $rxQualityRedMin, ')
..write('rxSnrGreenMinDb: $rxSnrGreenMinDb, ')
@@ -3018,6 +3213,9 @@ typedef $$DroneProfilesTableCreateCompanionBuilder =
Value<double> currentRedMaxA,
Value<double> batteryVoltageGreenMinV,
Value<double> batteryVoltageRedMinV,
Value<int> batteryCellCount,
Value<double> batteryVoltageGreenMinPerCellV,
Value<double> batteryVoltageRedMinPerCellV,
Value<double> rxQualityGreenMin,
Value<double> rxQualityRedMin,
Value<double> rxSnrGreenMinDb,
@@ -3046,6 +3244,9 @@ typedef $$DroneProfilesTableUpdateCompanionBuilder =
Value<double> currentRedMaxA,
Value<double> batteryVoltageGreenMinV,
Value<double> batteryVoltageRedMinV,
Value<int> batteryCellCount,
Value<double> batteryVoltageGreenMinPerCellV,
Value<double> batteryVoltageRedMinPerCellV,
Value<double> rxQualityGreenMin,
Value<double> rxQualityRedMin,
Value<double> rxSnrGreenMinDb,
@@ -3166,6 +3367,22 @@ class $$DroneProfilesTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get batteryCellCount => $composableBuilder(
column: $table.batteryCellCount,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<double> get batteryVoltageGreenMinPerCellV =>
$composableBuilder(
column: $table.batteryVoltageGreenMinPerCellV,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<double> get batteryVoltageRedMinPerCellV => $composableBuilder(
column: $table.batteryVoltageRedMinPerCellV,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<double> get rxQualityGreenMin => $composableBuilder(
column: $table.rxQualityGreenMin,
builder: (column) => ColumnFilters(column),
@@ -3301,6 +3518,23 @@ class $$DroneProfilesTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get batteryCellCount => $composableBuilder(
column: $table.batteryCellCount,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<double> get batteryVoltageGreenMinPerCellV =>
$composableBuilder(
column: $table.batteryVoltageGreenMinPerCellV,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<double> get batteryVoltageRedMinPerCellV =>
$composableBuilder(
column: $table.batteryVoltageRedMinPerCellV,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<double> get rxQualityGreenMin => $composableBuilder(
column: $table.rxQualityGreenMin,
builder: (column) => ColumnOrderings(column),
@@ -3426,6 +3660,23 @@ class $$DroneProfilesTableAnnotationComposer
builder: (column) => column,
);
GeneratedColumn<int> get batteryCellCount => $composableBuilder(
column: $table.batteryCellCount,
builder: (column) => column,
);
GeneratedColumn<double> get batteryVoltageGreenMinPerCellV =>
$composableBuilder(
column: $table.batteryVoltageGreenMinPerCellV,
builder: (column) => column,
);
GeneratedColumn<double> get batteryVoltageRedMinPerCellV =>
$composableBuilder(
column: $table.batteryVoltageRedMinPerCellV,
builder: (column) => column,
);
GeneratedColumn<double> get rxQualityGreenMin => $composableBuilder(
column: $table.rxQualityGreenMin,
builder: (column) => column,
@@ -3499,6 +3750,11 @@ class $$DroneProfilesTableTableManager
Value<double> currentRedMaxA = const Value.absent(),
Value<double> batteryVoltageGreenMinV = const Value.absent(),
Value<double> batteryVoltageRedMinV = const Value.absent(),
Value<int> batteryCellCount = const Value.absent(),
Value<double> batteryVoltageGreenMinPerCellV =
const Value.absent(),
Value<double> batteryVoltageRedMinPerCellV =
const Value.absent(),
Value<double> rxQualityGreenMin = const Value.absent(),
Value<double> rxQualityRedMin = const Value.absent(),
Value<double> rxSnrGreenMinDb = const Value.absent(),
@@ -3525,6 +3781,9 @@ class $$DroneProfilesTableTableManager
currentRedMaxA: currentRedMaxA,
batteryVoltageGreenMinV: batteryVoltageGreenMinV,
batteryVoltageRedMinV: batteryVoltageRedMinV,
batteryCellCount: batteryCellCount,
batteryVoltageGreenMinPerCellV: batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV: batteryVoltageRedMinPerCellV,
rxQualityGreenMin: rxQualityGreenMin,
rxQualityRedMin: rxQualityRedMin,
rxSnrGreenMinDb: rxSnrGreenMinDb,
@@ -3553,6 +3812,11 @@ class $$DroneProfilesTableTableManager
Value<double> currentRedMaxA = const Value.absent(),
Value<double> batteryVoltageGreenMinV = const Value.absent(),
Value<double> batteryVoltageRedMinV = const Value.absent(),
Value<int> batteryCellCount = const Value.absent(),
Value<double> batteryVoltageGreenMinPerCellV =
const Value.absent(),
Value<double> batteryVoltageRedMinPerCellV =
const Value.absent(),
Value<double> rxQualityGreenMin = const Value.absent(),
Value<double> rxQualityRedMin = const Value.absent(),
Value<double> rxSnrGreenMinDb = const Value.absent(),
@@ -3579,6 +3843,9 @@ class $$DroneProfilesTableTableManager
currentRedMaxA: currentRedMaxA,
batteryVoltageGreenMinV: batteryVoltageGreenMinV,
batteryVoltageRedMinV: batteryVoltageRedMinV,
batteryCellCount: batteryCellCount,
batteryVoltageGreenMinPerCellV: batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV: batteryVoltageRedMinPerCellV,
rxQualityGreenMin: rxQualityGreenMin,
rxQualityRedMin: rxQualityRedMin,
rxSnrGreenMinDb: rxSnrGreenMinDb,
@@ -66,8 +66,9 @@ class DroneProfileRepository {
hdopRedMax: row.hdopRedMax,
currentGreenMaxA: row.currentGreenMaxA,
currentRedMaxA: row.currentRedMaxA,
batteryVoltageGreenMinV: row.batteryVoltageGreenMinV,
batteryVoltageRedMinV: row.batteryVoltageRedMinV,
batteryCellCount: row.batteryCellCount,
batteryVoltageGreenMinPerCellV: row.batteryVoltageGreenMinPerCellV,
batteryVoltageRedMinPerCellV: row.batteryVoltageRedMinPerCellV,
rxQualityGreenMin: row.rxQualityGreenMin,
rxQualityRedMin: row.rxQualityRedMin,
rxSnrGreenMinDb: row.rxSnrGreenMinDb,
@@ -94,8 +95,9 @@ class DroneProfileRepository {
hdopRedMax: Value(profile.hdopRedMax),
currentGreenMaxA: Value(profile.currentGreenMaxA),
currentRedMaxA: Value(profile.currentRedMaxA),
batteryVoltageGreenMinV: Value(profile.batteryVoltageGreenMinV),
batteryVoltageRedMinV: Value(profile.batteryVoltageRedMinV),
batteryCellCount: Value(profile.batteryCellCount),
batteryVoltageGreenMinPerCellV: Value(profile.batteryVoltageGreenMinPerCellV),
batteryVoltageRedMinPerCellV: Value(profile.batteryVoltageRedMinPerCellV),
rxQualityGreenMin: Value(profile.rxQualityGreenMin),
rxQualityRedMin: Value(profile.rxQualityRedMin),
rxSnrGreenMinDb: Value(profile.rxSnrGreenMinDb),
@@ -30,8 +30,9 @@ String buildDroneExportJson(DroneProfile profile) {
'hdopRedMax': profile.hdopRedMax,
'currentGreenMaxA': profile.currentGreenMaxA,
'currentRedMaxA': profile.currentRedMaxA,
'batteryVoltageGreenMinV': profile.batteryVoltageGreenMinV,
'batteryVoltageRedMinV': profile.batteryVoltageRedMinV,
'batteryCellCount': profile.batteryCellCount,
'batteryVoltageGreenMinPerCellV': profile.batteryVoltageGreenMinPerCellV,
'batteryVoltageRedMinPerCellV': profile.batteryVoltageRedMinPerCellV,
'rxQualityGreenMin': profile.rxQualityGreenMin,
'rxQualityRedMin': profile.rxQualityRedMin,
'rxSnrGreenMinDb': profile.rxSnrGreenMinDb,
@@ -109,6 +110,11 @@ ParsedDroneImport parseDroneImportJson(String jsonText) {
return v is num ? v.toDouble() : fallback;
}
int intOrDefault(String field, int fallback) {
final v = droneData[field];
return v is num ? v.round() : fallback;
}
final profile = DroneProfile(
name: name,
minSpeed: (droneData['minSpeed'] as num).toDouble(),
@@ -128,13 +134,22 @@ ParsedDroneImport parseDroneImportJson(String jsonText) {
hdopRedMax: numOrDefault('hdopRedMax', DroneProfile.defaultHdopRedMax),
currentGreenMaxA: numOrDefault('currentGreenMaxA', DroneProfile.defaultCurrentGreenMaxA),
currentRedMaxA: numOrDefault('currentRedMaxA', DroneProfile.defaultCurrentRedMaxA),
batteryVoltageGreenMinV: numOrDefault(
'batteryVoltageGreenMinV',
DroneProfile.defaultBatteryVoltageGreenMinV,
// batteryCellCount/*PerCellV sind Schema-v9-Felder (Doku: "zellen anzahl
// ... hinzufuegen") - Dateien, die vor deren Einfuehrung exportiert
// wurden, haben stattdessen ggf. noch die alten absoluten
// batteryVoltage*MinV-Felder (feste Pack-Spannung). Die werden hier
// bewusst NICHT mehr gelesen (koennten ohne bekannte Zellenzahl der
// Quelldrohne nicht sinnvoll in Pro-Zelle-Werte umgerechnet werden) -
// stattdessen gelten wie bei allen anderen Schwellwerten die
// DroneProfile.default*-Werte.
batteryCellCount: intOrDefault('batteryCellCount', DroneProfile.defaultBatteryCellCount),
batteryVoltageGreenMinPerCellV: numOrDefault(
'batteryVoltageGreenMinPerCellV',
DroneProfile.defaultBatteryVoltageGreenMinPerCellV,
),
batteryVoltageRedMinV: numOrDefault(
'batteryVoltageRedMinV',
DroneProfile.defaultBatteryVoltageRedMinV,
batteryVoltageRedMinPerCellV: numOrDefault(
'batteryVoltageRedMinPerCellV',
DroneProfile.defaultBatteryVoltageRedMinPerCellV,
),
rxQualityGreenMin: numOrDefault('rxQualityGreenMin', DroneProfile.defaultRxQualityGreenMin),
rxQualityRedMin: numOrDefault('rxQualityRedMin', DroneProfile.defaultRxQualityRedMin),
@@ -14,9 +14,9 @@ import 'telemetry_provider.dart';
/// telemetryProvider beobachten, risse schon ein simpler Missionswechsel im
/// Plan-Modus ungewollt den kompletten Telemetrie-/Transport-Stack (inkl.
/// echtem MSP/UDP-Transport) mit hoch. Die automatische Erkennung
/// (Connected/Lost/Verbindungstyp-Wechsel) sitzt deshalb separat in
/// [systemMessageAutoLogProvider], die ausschliesslich der Fly-Modus
/// beobachtet.
/// (Drone connected/disconnected/GPS-Fix/Batterie/Failsafe/Verbindungstyp-
/// Wechsel) sitzt deshalb separat in [systemMessageAutoLogProvider], die
/// ausschliesslich der Fly-Modus beobachtet.
class SystemMessageLogNotifier extends Notifier<List<SystemMessage>> {
@override
List<SystemMessage> build() => [];
@@ -39,9 +39,10 @@ final systemMessageLogProvider =
NotifierProvider<SystemMessageLogNotifier, List<SystemMessage>>(
SystemMessageLogNotifier.new);
/// Automatische Erkennung von Connected/Connection-lost/Batterie-/Failsafe-/
/// Verbindungstyp-Aenderungen (Doku: "wenn die Verbindung hergestellt
/// wird/verloren geht, der verbindungstyp geaendert wird") - getrennt von
/// Automatische Erkennung von Drone-connected/-disconnected/GPS-Fix/
/// Batterie-/Failsafe-/Verbindungstyp-Aenderungen (Doku: "wenn die
/// Verbindung hergestellt wird/verloren geht, der verbindungstyp geaendert
/// wird, gps fix aquired") - getrennt von
/// [systemMessageLogProvider] selbst (siehe dessen Doku), damit Plan-Modus-
/// Code, der nur log() fuer eine manuelle Meldung braucht, nicht versehentlich
/// telemetryProvider mit hochzieht. autoDispose: nur beobachtet, waehrend
@@ -56,12 +57,17 @@ final systemMessageAutoLogProvider = Provider.autoDispose<void>((ref) {
next.when(
data: (frame) {
final log = ref.read(systemMessageLogProvider.notifier);
// Flankengetriggert wie "Connection lost" unten, nur umgekehrt -
// Flankengetriggert wie "Drone disconnected" unten, nur umgekehrt -
// anders als dort ist hier kein Sonderfall fuer die allererste
// Verbindung noetig: der Uebergang "noch keine Daten" -> "erste
// Daten" ist immer ein echtes "Connected", auch beim allerersten Mal.
// Daten" ist immer ein echtes "Drone connected", auch beim
// allerersten Mal. Bewusst an das Eintreffen eines echten
// Telemetrie-Frames gebunden statt an den rohen Socket-/Link-
// Zustand (Doku: "drone connected/disconnected (receiving valid
// telemetry)") - ein offener Socket ohne auswertbare MSP-Antworten
// zaehlt also nicht als "connected".
if (!hadData) {
log.log('Connected');
log.log('Drone connected');
}
final events = detectSystemMessages(
previous: previousFrame,
@@ -77,10 +83,10 @@ final systemMessageAutoLogProvider = Provider.autoDispose<void>((ref) {
error: (error, stackTrace) {
// Nur melden, wenn zuvor bereits Frames flossen - sonst wuerde
// schon das (normale) Ausbleiben der allerersten Verbindung als
// "Connection lost" auftauchen.
// "Drone disconnected" auftauchen.
if (hadData) {
ref.read(systemMessageLogProvider.notifier)
.log('Connection lost', severity: SystemMessageSeverity.critical);
.log('Drone disconnected', severity: SystemMessageSeverity.critical);
}
previousFrame = null;
hadData = false;
+10 -7
View File
@@ -91,10 +91,11 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
],
),
(
title: 'Battery voltage alarm (V)',
title: 'Battery voltage alarm (V/cell)',
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'),
(key: 'batteryCellCount', label: 'Cells', min: 1.0, max: 16.0, stepBig: 1.0, stepSmall: 1.0, decimals: 0, unit: 'S'),
(key: 'batteryVoltageGreenMinPerCellV', label: 'Green from', min: 2.5, max: 4.3, stepBig: 0.2, stepSmall: 0.05, decimals: 2, unit: 'V'),
(key: 'batteryVoltageRedMinPerCellV', label: 'Red below', min: 2.5, max: 4.3, stepBig: 0.2, stepSmall: 0.05, decimals: 2, unit: 'V'),
],
),
(
@@ -131,8 +132,9 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
'hdopRedMax' => p.hdopRedMax,
'currentGreenMaxA' => p.currentGreenMaxA,
'currentRedMaxA' => p.currentRedMaxA,
'batteryVoltageGreenMinV' => p.batteryVoltageGreenMinV,
'batteryVoltageRedMinV' => p.batteryVoltageRedMinV,
'batteryCellCount' => p.batteryCellCount.toDouble(),
'batteryVoltageGreenMinPerCellV' => p.batteryVoltageGreenMinPerCellV,
'batteryVoltageRedMinPerCellV' => p.batteryVoltageRedMinPerCellV,
'rxQualityGreenMin' => p.rxQualityGreenMin,
'rxQualityRedMin' => p.rxQualityRedMin,
'rxSnrGreenMinDb' => p.rxSnrGreenMinDb,
@@ -184,8 +186,9 @@ class _DroneProfileEditorState extends ConsumerState<DroneProfileEditor> {
hdopRedMax: v('hdopRedMax'),
currentGreenMaxA: v('currentGreenMaxA'),
currentRedMaxA: v('currentRedMaxA'),
batteryVoltageGreenMinV: v('batteryVoltageGreenMinV'),
batteryVoltageRedMinV: v('batteryVoltageRedMinV'),
batteryCellCount: v('batteryCellCount').round(),
batteryVoltageGreenMinPerCellV: v('batteryVoltageGreenMinPerCellV'),
batteryVoltageRedMinPerCellV: v('batteryVoltageRedMinPerCellV'),
rxQualityGreenMin: v('rxQualityGreenMin'),
rxQualityRedMin: v('rxQualityRedMin'),
rxSnrGreenMinDb: v('rxSnrGreenMinDb'),
+48 -3
View File
@@ -3,12 +3,17 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:dmc_app/domain/telemetry/system_message.dart';
import 'package:dmc_app/transport/flight_controller_link.dart';
TelemetryFrame _frame({int batteryPercent = 80, int navMode = 0}) {
TelemetryFrame _frame({
int batteryPercent = 80,
int navMode = 0,
bool hasFix = true,
int numSat = 10,
}) {
return TelemetryFrame(
lat: 52.5,
lon: 13.4,
hasFix: true,
numSat: 10,
hasFix: hasFix,
numSat: numSat,
altitudeM: 100,
speedMs: 15,
headingDeg: 0,
@@ -109,4 +114,44 @@ void main() {
expect(events, isEmpty);
});
});
group('detectSystemMessages - GPS fix', () {
test('kein Ereignis ohne vorherigen Frame, auch wenn hasFix true ist', () {
final events = detectSystemMessages(
previous: null,
current: _frame(hasFix: true, numSat: 9),
now: now,
);
expect(events, isEmpty);
});
test('"GPS fix acquired" beim Uebergang von keinem zu einem Fix', () {
final events = detectSystemMessages(
previous: _frame(hasFix: false, numSat: 2),
current: _frame(hasFix: true, numSat: 9),
now: now,
);
expect(events, hasLength(1));
expect(events.single.severity, SystemMessageSeverity.info);
expect(events.single.message, 'GPS fix acquired (9 satellites)');
});
test('kein Ereignis, solange der Fix bereits vorher bestand', () {
final events = detectSystemMessages(
previous: _frame(hasFix: true, numSat: 8),
current: _frame(hasFix: true, numSat: 11),
now: now,
);
expect(events, isEmpty);
});
test('kein Ereignis beim Verlust des Fixes', () {
final events = detectSystemMessages(
previous: _frame(hasFix: true, numSat: 8),
current: _frame(hasFix: false, numSat: 3),
now: now,
);
expect(events, isEmpty);
});
});
}
@@ -76,7 +76,13 @@ void main() {
expect(all, hasLength(1));
expect(all.first.name, 'T1 Ranger');
expect(all.first.gpsSatGreenMin, 8.0);
expect(all.first.batteryVoltageRedMinV, 19.2);
// batteryVoltageRedMinV ist seit Schema v9 ein berechneter Getter
// (batteryCellCount * batteryVoltageRedMinPerCellV) statt eines
// gespeicherten Absolutwerts - die alte v8-Spalte wird von diesem Pfad
// (v7 -> aktuell) durchlaufen, aber nicht mehr gelesen. Erwartet wird
// deshalb der neue v9-Default: 4 Zellen * 3.2 V.
expect(all.first.batteryCellCount, 4);
expect(all.first.batteryVoltageRedMinV, 12.8);
await db.close();
await dir.delete(recursive: true);
@@ -0,0 +1,106 @@
import 'dart:io';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sqlite3/sqlite3.dart' as sqlite3;
import 'package:dmc_app/services/database/app_database.dart';
import 'package:dmc_app/services/database/drone_profile_repository.dart';
/// Reproduziert das Upgrade eines Geraets auf Schema v8 (DroneProfiles mit
/// den Ampel-Schwellwert-Spalten, aber noch der alten festen Pack-Spannung
/// batteryVoltageGreenMinV/batteryVoltageRedMinV statt Zellenzahl +
/// Pro-Zelle-Schwellwerten) auf das aktuelle Schema (v9, Doku: "zellen
/// anzahl der batterie im drone profile hinzu fuegen") - analog
/// migration_v7_to_v8_repro_test.dart: baut die v8-Tabellenstruktur samt
/// Bestandszeile roh per sqlite3 auf, oeffnet die Datei dann ganz normal
/// ueber AppDatabase und prueft, dass die neuen Spalten mit den erwarteten
/// Default-Werten befuellt werden.
void main() {
test('Upgrade von Schema v8 auf v9 befuellt neue Spalten mit Defaults', () async {
final dir = await Directory.systemTemp.createTemp('dmc_migration_test');
final file = File('${dir.path}/test.sqlite');
final raw = sqlite3.sqlite3.open(file.path);
raw.execute('''
CREATE TABLE missions (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
location TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
waypoints_json TEXT NOT NULL,
is_dirty INTEGER NOT NULL DEFAULT 1,
server_id TEXT,
terrain_profile_json TEXT
);
CREATE TABLE drone_profiles (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
min_speed REAL NOT NULL,
cruise_speed REAL NOT NULL,
max_speed REAL NOT NULL,
min_turn_radius REAL NOT NULL,
loiter_radius REAL NOT NULL,
catch_radius REAL NOT NULL,
max_climb_rate REAL NOT NULL,
max_descent_rate REAL NOT NULL,
max_range_m REAL NOT NULL,
max_endurance_min REAL NOT NULL,
max_altitude_m REAL NOT NULL,
gps_sat_green_min REAL NOT NULL DEFAULT 8.0,
gps_sat_red_min REAL NOT NULL DEFAULT 6.0,
hdop_green_max REAL NOT NULL DEFAULT 2.0,
hdop_red_max REAL NOT NULL DEFAULT 5.0,
current_green_max_a REAL NOT NULL DEFAULT 20.0,
current_red_max_a REAL NOT NULL DEFAULT 30.0,
battery_voltage_green_min_v REAL NOT NULL DEFAULT 20.4,
battery_voltage_red_min_v REAL NOT NULL DEFAULT 19.2,
rx_quality_green_min REAL NOT NULL DEFAULT 70.0,
rx_quality_red_min REAL NOT NULL DEFAULT 50.0,
rx_snr_green_min_db REAL NOT NULL DEFAULT 0.0,
rx_snr_red_min_db REAL NOT NULL DEFAULT -10.0
);
CREATE TABLE app_settings_table (
id INTEGER NOT NULL PRIMARY KEY,
active_drone_profile_id INTEGER,
current_mission_id INTEGER,
connection_device_address TEXT,
connection_auto_connect INTEGER NOT NULL DEFAULT 1,
connection_type TEXT NOT NULL DEFAULT 'wifi',
connection_wifi_host TEXT,
connection_wifi_port INTEGER NOT NULL DEFAULT 14550,
connection_wifi_ssid_prefix TEXT NOT NULL DEFAULT 'mLRS-',
connection_wifi_last_ssid TEXT
);
INSERT INTO drone_profiles (
id, name, min_speed, cruise_speed, max_speed, min_turn_radius,
loiter_radius, catch_radius, max_climb_rate, max_descent_rate,
max_range_m, max_endurance_min, max_altitude_m,
battery_voltage_green_min_v, battery_voltage_red_min_v
) VALUES (
1, 'T1 Ranger', 13, 15, 25, 35, 100, 60, 5, 6, 15000, 45, 2000,
20.4, 19.2
);
PRAGMA user_version = 8;
''');
raw.close();
final db = AppDatabase.forTesting(NativeDatabase(file));
final repo = DroneProfileRepository(db);
final all = await repo.loadAll();
expect(all, hasLength(1));
expect(all.first.name, 'T1 Ranger');
// Neue v9-Spalten sollen mit den DroneProfile.default*-Werten befuellt
// werden, unabhaengig von der alten (jetzt toten) Pack-Spannung der
// Bestandszeile.
expect(all.first.batteryCellCount, 4);
expect(all.first.batteryVoltageGreenMinPerCellV, 3.4);
expect(all.first.batteryVoltageRedMinPerCellV, 3.2);
expect(all.first.batteryVoltageGreenMinV, closeTo(13.6, 0.0001));
expect(all.first.batteryVoltageRedMinV, closeTo(12.8, 0.0001));
await db.close();
await dir.delete(recursive: true);
});
}
@@ -24,6 +24,7 @@ void main() {
expect(all.single.id, id);
expect(all.single.name, 'T1 Ranger');
expect(all.single.cruiseSpeed, 15);
expect(all.single.batteryCellCount, 4);
});
test('ensureSeeded ist ein no-op, wenn bereits Profile existieren', () async {
@@ -16,9 +16,41 @@ void main() {
expect(parsed.profile.minSpeed, profile.minSpeed);
expect(parsed.profile.maxSpeed, profile.maxSpeed);
expect(parsed.profile.maxAltitudeM, profile.maxAltitudeM);
expect(parsed.profile.batteryCellCount, profile.batteryCellCount);
expect(
parsed.profile.batteryVoltageGreenMinPerCellV,
profile.batteryVoltageGreenMinPerCellV,
);
expect(
parsed.profile.batteryVoltageRedMinPerCellV,
profile.batteryVoltageRedMinPerCellV,
);
expect(parsed.versionMismatch, isNull);
});
test('alte Exportdatei ohne batteryCellCount faellt auf die neuen '
'Defaults zurueck, statt die alte Pack-Spannung zu uebernehmen', () {
final json = buildDroneExportJson(profile).replaceFirst(
RegExp(
r',\s*"batteryCellCount": [\d.]+,\s*'
r'"batteryVoltageGreenMinPerCellV": [\d.]+,\s*'
r'"batteryVoltageRedMinPerCellV": [\d.]+',
),
'',
);
final parsed = parseDroneImportJson(json);
expect(parsed.profile.batteryCellCount, DroneProfile.defaultBatteryCellCount);
expect(
parsed.profile.batteryVoltageGreenMinPerCellV,
DroneProfile.defaultBatteryVoltageGreenMinPerCellV,
);
expect(
parsed.profile.batteryVoltageRedMinPerCellV,
DroneProfile.defaultBatteryVoltageRedMinPerCellV,
);
});
test('erkennt eine abweichende Hauptversion als Mismatch', () {
final json = buildDroneExportJson(profile).replaceFirst('"1.0.0"', '"2.0.0"');
final parsed = parseDroneImportJson(json);
@@ -10,11 +10,11 @@ import 'package:dmc_app/ui/providers/connection_settings_provider.dart';
import 'package:dmc_app/ui/providers/system_message_log_provider.dart';
import 'package:dmc_app/ui/providers/telemetry_provider.dart';
TelemetryFrame _frame() => const TelemetryFrame(
TelemetryFrame _frame({bool hasFix = true, int numSat = 10}) => TelemetryFrame(
lat: 52.5,
lon: 13.4,
hasFix: true,
numSat: 10,
hasFix: hasFix,
numSat: numSat,
altitudeM: 100,
speedMs: 15,
headingDeg: 0,
@@ -54,8 +54,8 @@ _ConnectionSettings _settings(ConnectionType type) => (
);
void main() {
test('loggt "Connected" beim ersten Telemetrie-Frame und "Connection '
'lost" bei einem anschliessenden Stream-Fehler', () async {
test('loggt "Drone connected" beim ersten Telemetrie-Frame und "Drone '
'disconnected" bei einem anschliessenden Stream-Fehler', () async {
final controller = StreamController<TelemetryFrame>();
addTearDown(controller.close);
final container = ProviderContainer(overrides: [
@@ -71,18 +71,44 @@ void main() {
final afterConnect = container.read(systemMessageLogProvider);
expect(afterConnect, hasLength(1));
expect(afterConnect.single.message, 'Connected');
expect(afterConnect.single.message, 'Drone connected');
expect(afterConnect.single.severity, SystemMessageSeverity.info);
controller.addError(StateError('link down'));
await Future<void>.delayed(Duration.zero);
final afterLost = container.read(systemMessageLogProvider);
expect(afterLost.first.message, 'Connection lost');
expect(afterLost.first.message, 'Drone disconnected');
expect(afterLost.first.severity, SystemMessageSeverity.critical);
});
test('loggt keine "Connected"-Meldung, solange nie ein Frame ankommt', () async {
test('loggt "GPS fix acquired" beim Uebergang auf einen Fix, aber nicht '
'beim ersten Frame selbst', () async {
final controller = StreamController<TelemetryFrame>();
addTearDown(controller.close);
final container = ProviderContainer(overrides: [
telemetryProvider.overrideWith((ref) => controller.stream),
connectionSettingsProvider
.overrideWith((ref) async => _settings(ConnectionType.mock)),
]);
addTearDown(container.dispose);
container.listen(systemMessageAutoLogProvider, (previous, next) {});
// Erster Frame ohne Fix: nur "Drone connected", noch kein GPS-Ereignis
// (detectSystemMessages() braucht einen vorherigen Frame zum Vergleich).
controller.add(_frame(hasFix: false, numSat: 2));
await Future<void>.delayed(Duration.zero);
expect(container.read(systemMessageLogProvider), hasLength(1));
controller.add(_frame(hasFix: true, numSat: 9));
await Future<void>.delayed(Duration.zero);
final log = container.read(systemMessageLogProvider);
expect(log.first.message, 'GPS fix acquired (9 satellites)');
expect(log.first.severity, SystemMessageSeverity.info);
});
test('loggt keine "Drone connected"-Meldung, solange nie ein Frame ankommt', () async {
final container = ProviderContainer(overrides: [
telemetryProvider.overrideWith((ref) => const Stream<TelemetryFrame>.empty()),
connectionSettingsProvider
+2 -2
View File
@@ -1122,7 +1122,7 @@ void main() {
testWidgets(
'Drohnen-Status-Pille oeffnet "Drone Status & System Messages", '
'System-Messages-Tab zeigt "Connected" nach der ersten Telemetrie',
'System-Messages-Tab zeigt "Drone connected" nach der ersten Telemetrie',
(tester) async {
final container = _testContainer();
addTearDown(container.dispose);
@@ -1152,7 +1152,7 @@ void main() {
await tester.tap(find.text('System Messages'));
await tester.pumpAndSettle();
expect(find.text('Connected'), findsOneWidget);
expect(find.text('Drone connected'), findsOneWidget);
await _stopTelemetry(container);
});