Give the Fly App a GO 4-style flight HUD
The new flight screen (lib/ui/fly/fly_screen.dart) is built from layers. Each readout listens to its own telemetry sub-model, so a 10 Hz tick only repaints what changed. It adds a status bar with a status chip and tip marquee, a smart battery bar with land/RTH markers, a camera info strip, an attitude ball, an H/D/VS/HS/VPS strip, and a minimap that swaps places with the video when tapped. The HUD is laid out in design pixels and scaled to phone or tablet inside the safe area. What the HUD shows depends on the product: dual or multi battery bars, SSD status, a thermal spot reading, a master/slave RC badge, and Wi-Fi bars in place of RC/HD. The bridges now report the data for these: the camera's ssd/thermal groups and the RC's master/slave mode. The old FlightControlPage can still be opened from Settings > About > Classic flight screen until the P3 safety flows land. The same tab also gets a toggle for the performance overlay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f6bbb1cfee
commit
590f2a6152
26 files changed
+2822
-11
No files matched your search
@@ -13,11 +13,15 @@ import io.flutter.plugin.common.MethodChannel
|
||||
*
|
||||
* ```
|
||||
* links: {kind: ocusync|lightbridge|wifi, uplinkQuality, downlinkQuality}
|
||||
* rc: {batteryPercent, batteryMah, isCharging}
|
||||
* rc: {batteryPercent, batteryMah, isCharging, mode, masterSlaveConnected}
|
||||
* ```
|
||||
*
|
||||
* Wi-Fi products (Spark, Mavic Air on Wi-Fi) report quality through their
|
||||
* `WiFiLink`; the RC links report through `AirLink` itself.
|
||||
*
|
||||
* `rc.mode` is the `RCMode` name (`MASTER` / `SLAVE` / `NORMAL` …) and only
|
||||
* appears on remotes that support dual-operator flying, where the HUD shows a
|
||||
* master / slave badge.
|
||||
*/
|
||||
class AirLinkBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
|
||||
@@ -62,7 +66,15 @@ class AirLinkBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
"batteryMah" to s.remainingChargeInmAh,
|
||||
"isCharging" to s.isCharging,
|
||||
)
|
||||
ctx.mainHandler.post { ctx.hub.putSlow("rc", values) }
|
||||
ctx.mainHandler.post { ctx.hub.patchSlow("rc", values) }
|
||||
}
|
||||
}
|
||||
if (safe { rc.isMasterSlaveModeSupported } == true) {
|
||||
safe {
|
||||
rc.setMasterSlaveStateCallback { s ->
|
||||
val values = mapOf("mode" to s.rcMode?.name, "masterSlaveConnected" to s.isConnected)
|
||||
ctx.mainHandler.post { ctx.hub.patchSlow("rc", values) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.pilotvault.flyapp
|
||||
|
||||
import dji.common.camera.ExposureSettings
|
||||
import dji.common.camera.ResolutionAndFrameRate
|
||||
import dji.common.camera.SSDState
|
||||
import dji.common.camera.SettingsDefinitions
|
||||
import dji.common.camera.StorageState
|
||||
import dji.common.camera.SystemState
|
||||
@@ -26,7 +27,12 @@ import io.flutter.plugin.common.MethodChannel
|
||||
* availableCaptureCount, availableRecordingSeconds}
|
||||
* cameraSettings: {aeLock, focusMode, photoAspectRatio, photoFileFormat,
|
||||
* videoResolution, videoFrameRate, videoFileFormat, colorProfile}
|
||||
* ssd: {connected, state, capacity, remainingMB, availableRecordingSeconds}
|
||||
* thermal: {spotTemperature}
|
||||
* ```
|
||||
* `ssd` only on cameras with an SSD slot (X5S / X7 on Inspire 2), `thermal` only
|
||||
* on thermal cameras (XT / XT2 / Mavic 2 Enterprise Dual); the HUD's SSD and
|
||||
* thermal readouts (plan P2) are hidden on everything else.
|
||||
* The settings are watched keys, so a change made from the RC's dial or the
|
||||
* settings sheet shows up without a re-read.
|
||||
*/
|
||||
@@ -112,6 +118,22 @@ class CameraBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
} catch (_: Throwable) {
|
||||
// A camera without removable or internal storage reports nothing.
|
||||
}
|
||||
if (safe { camera.isSSDSupported } == true) {
|
||||
safe {
|
||||
camera.setSSDStateCallback { s: SSDState ->
|
||||
val values = ssdMap(s)
|
||||
ctx.mainHandler.post { ctx.hub.putSlow("ssd", values) }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (safe { camera.isThermalCamera } == true) {
|
||||
safe {
|
||||
camera.setThermalTemperatureCallback { celsius: Float ->
|
||||
val t = if (celsius.isNaN()) null else celsius.toDouble()
|
||||
ctx.mainHandler.post { ctx.hub.putSlow("thermal", mapOf("spotTemperature" to t)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val index = try {
|
||||
camera.index
|
||||
@@ -156,6 +178,14 @@ class CameraBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
"availableRecordingSeconds" to s.availableRecordingTimeInSeconds,
|
||||
)
|
||||
|
||||
private fun ssdMap(s: SSDState): Map<String, Any?> = mapOf(
|
||||
"connected" to s.isConnected,
|
||||
"state" to s.ssdOperationState?.name,
|
||||
"capacity" to s.capacity?.name,
|
||||
"remainingMB" to s.remainingSpaceInMB,
|
||||
"availableRecordingSeconds" to s.availableRecordingTimeInSeconds,
|
||||
)
|
||||
|
||||
private fun emitSystem(state: SystemState) {
|
||||
ctx.emit(
|
||||
mapOf(
|
||||
@@ -241,4 +271,10 @@ class CameraBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> safe(block: () -> T): T? = try {
|
||||
block()
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import android.os.SystemClock
|
||||
*
|
||||
* - **fast** sources ([putFast]: flight-controller state, vision) at up to
|
||||
* [FAST_MS] (10 Hz), only when one of them changed;
|
||||
* - **slow** sources ([putSlow]: battery, links, RC, storage, camera settings)
|
||||
* - **slow** sources ([putSlow]: battery, links, RC, storage, SSD, thermal,
|
||||
* camera settings)
|
||||
* folded in at most every [SLOW_MS] (2 Hz), so a chatty battery callback
|
||||
* cannot drive the tick rate on its own.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user