Drone Status Menue: Sensor-Status, Arming, Temperaturen und Steig-/Sinkrate hinzugefuegt

Neue Zeile ganz oben mit farblich kodierten Sensor-Badges (ACC/BARO/MAG/
GPS/RNG/OF/PITOT/TEMP, aus dem sensorStatus-Bitfeld von MSP2_INAV_STATUS,
zuvor schon dokumentiert aber ungenutzt), darunter ein Zeilenpaar fuer
Arming-Status und die ersten 3 Temperatursensoren (neu: MSP2_INAV_
TEMPERATURES, 0x201E). Ans Ende der Liste die Steig-/Sinkrate (vario aus
MSP_ALTITUDE, bislang nur die Hoehe selbst wurde daraus gelesen).

- msp_commands.dart: inavTemperatures-Konstante ergaenzt; die Sensor-
  Status-Bits (bisher nur als Doc-Kommentar bei MSP2_INAV_STATUS notiert)
  zu echten Konstanten (MspSensorStatusBits) promoviert, da jetzt
  tatsaechlich gebraucht.
- msp_telemetry_codec.dart: parseMspAltitudeVerticalSpeedMs,
  parseMspInavStatusSensorStatus, parseMspInavTemperaturesC ergaenzt +
  Unit-Tests.
- TelemetryFrame: sensorStatusBits (Bitmaske, protokollneutral
  durchgereicht wie navMode), temperaturesC (erste 3 Sensoren, null je
  nicht konfiguriertem Slot), verticalSpeedMs.
- msp_telemetry_poller.dart: vario kommt aus derselben MSP_ALTITUDE-
  Antwort wie die Hoehe (keine zusaetzliche Anfrage), sensorStatus aus
  derselben MSP2_INAV_STATUS-Antwort wie ARMED; MSP2_INAV_TEMPERATURES neu
  im 2-Hz-Statuszyklus abgefragt.
- MockFlightControllerLink: synthetische Sensor-/Temperatur-/Vario-Werte
  (kein Pitot/Rangefinder/Opflow am T1 Ranger vorgesehen), leicht
  schwankend, damit die neuen Felder auch ohne Hardware sichtbar auf
  Werteaenderungen reagieren.
- drone_status_messages_panel.dart: eigene Sensor-Status-Zeile (lokale
  Bit-Konstanten statt MSP-Import, analog zum bestehenden navMode-Muster
  in domain/telemetry/drone_status.dart, damit die UI protokollneutral
  bleibt), Arming+Temperaturen-Zeilenpaar, Vertical-speed-Zeile am Ende.

Auf dem Pixel_10a-Emulator verifiziert: Sensor-Badges gruen/grau je nach
Bitmaske, Arming/Temperaturen-Paar, Vertical speed am Listenende.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Constantin Leue
2026-08-06 15:32:56 +02:00
co-authored by Claude Sonnet 5
parent 08791cac0c
commit 9cece7dbb6
9 changed files with 324 additions and 21 deletions
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/telemetry/drone_status.dart';
import '../../domain/telemetry/system_message.dart';
import '../../domain/telemetry/telemetry_field_status.dart';
import '../../transport/flight_controller_link.dart';
import '../dmc_colors.dart';
import '../providers/active_drone_profile_provider.dart';
import '../providers/system_message_log_provider.dart';
@@ -136,13 +137,30 @@ class _DroneStatusMessagesPanelState extends ConsumerState<DroneStatusMessagesPa
);
return ListView(
padding: const EdgeInsets.all(16),
children: _statusGrid([
// 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. Je 2 Felder pro
// Zeile (Doku: "platz effizienter nutzen").
_statusRow('GPS satellites', frame != null ? '${frame.numSat}' : '—', level: fieldStatus.numSat),
children: [
// Eigene, volle Zeile ganz oben statt Teil des 2-Spalten-Rasters
// (Doku: "eine zeile ganz oben fuer sensor status") - die einzelnen
// Sensor-Badges brauchen mehr horizontalen Platz als eine
// Wertkachel bieten wuerde.
_sensorStatusRow(frame),
const SizedBox(height: 8),
..._statusGrid([
// Arming + erste 3 Temperaturen als eigenes Zeilenpaar direkt
// unter der Sensor-Status-Zeile (Doku), noch vor den
// farbcodierten Feldern.
_statusRow(
'Arming',
frame == null ? '—' : (frame.armed ? 'Armed' : 'Disarmed'),
level: frame?.armed == true ? SignalLevel.green : null,
),
_statusRow('Temperatures', _formatTemperatures(frame)),
// Farbcodierte Felder (Doku: "drone status felder im menue auch
// farblich kodieren", Schwellwerte je Drohnenprofil - siehe
// computeTelemetryFieldStatus), informelle Felder ohne Ampel-
// Bewertung (Koordinaten/Heading/Alt/Speed/Vertical speed)
// danach. Je 2 Felder pro Zeile (Doku: "platz effizienter
// nutzen").
_statusRow('GPS satellites', frame != null ? '${frame.numSat}' : '—', level: fieldStatus.numSat),
_statusRow(
'GPS precision (HDOP)',
frame != null && frame.hasFix ? frame.hdop.toStringAsFixed(2) : '—',
@@ -169,7 +187,12 @@ class _DroneStatusMessagesPanelState extends ConsumerState<DroneStatusMessagesPa
_statusRow('Heading', frame != null ? '${frame.headingDeg.round()}°' : '—'),
_statusRow('Altitude', frame != null ? '${frame.altitudeM.toStringAsFixed(0)} m' : '—'),
_statusRow('Speed', frame != null ? '${frame.speedMs.toStringAsFixed(1)} m/s' : '—'),
]),
// Ans Ende der Liste (Doku: "ans ende der liste noch die
// steig/sinkrate hinzufuegen") - bildet mit "Speed" das letzte
// Zeilenpaar.
_statusRow('Vertical speed', _formatVerticalSpeed(frame)),
]),
],
);
}
@@ -253,6 +276,89 @@ class _DroneStatusMessagesPanelState extends ConsumerState<DroneStatusMessagesPa
);
}
// --- Sensor status ---
/// Lokale Kopie der Sensor-Bit-Bedeutung (siehe `MspSensorStatusBits` in
/// transport/msp/msp_commands.dart) - bewusst nicht von dort importiert,
/// damit dieses UI-Widget kein MSP-/Protokollwissen braucht.
/// [TelemetryFrame.sensorStatusBits] ist protokollneutral durchgereicht,
/// genau wie `navMode` (siehe die entsprechenden lokalen Konstanten in
/// domain/telemetry/drone_status.dart).
static const _sensorBits = [
(bit: 1 << 0, label: 'ACC'),
(bit: 1 << 1, label: 'BARO'),
(bit: 1 << 2, label: 'MAG'),
(bit: 1 << 3, label: 'GPS'),
(bit: 1 << 4, label: 'RNG'),
(bit: 1 << 5, label: 'OF'),
(bit: 1 << 6, label: 'PITOT'),
(bit: 1 << 7, label: 'TEMP'),
];
static const _sensorHardwareFailureBit = 1 << 15;
Widget _sensorStatusRow(TelemetryFrame? frame) {
final bits = frame?.sensorStatusBits ?? 0;
final hardwareFailure = (bits & _sensorHardwareFailureBit) != 0;
return Container(
margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.04),
borderRadius: BorderRadius.circular(10),
border: hardwareFailure
? Border.all(color: DmcColors.warnRed.withValues(alpha: 0.6))
: null,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Sensor Status',
style: TextStyle(color: Colors.white70, fontSize: 12),
),
const SizedBox(height: 6),
Wrap(
spacing: 6,
runSpacing: 6,
children: [
for (final s in _sensorBits)
_sensorChip(s.label, frame != null && (bits & s.bit) != 0),
if (hardwareFailure) _sensorChip('HW FAULT', true, color: DmcColors.warnRed),
],
),
],
),
);
}
Widget _sensorChip(String label, bool active, {Color? color}) {
final chipColor = color ?? (active ? DmcColors.flyModeColor : Colors.white38);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: chipColor.withValues(alpha: active ? 0.18 : 0.06),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: chipColor.withValues(alpha: active ? 0.8 : 0.3)),
),
child: Text(
label,
style: TextStyle(color: chipColor, fontSize: 11, fontWeight: FontWeight.w600),
),
);
}
String _formatTemperatures(TelemetryFrame? frame) {
if (frame == null) return '—';
return frame.temperaturesC.map((t) => t == null ? '—' : '${t.round()}°').join(' / ');
}
String _formatVerticalSpeed(TelemetryFrame? frame) {
if (frame == null) return '—';
final v = frame.verticalSpeedMs;
final sign = v >= 0 ? '+' : '-';
return '$sign${v.abs().toStringAsFixed(1)} m/s';
}
// --- System Messages ---
Widget _messagesTab() {