Conversation
out_appsink (live RTP pipeline and DVR file playback) set drop=true with no max-buffers, which defaults to 0 (unlimited) in GStreamer, so drop=true was a no-op: a decode/pull stall let frames pile up with no bound, and the picture would catch up in fast-forward once the stall cleared instead of skipping ahead to live. Add max-buffers=1. Also trim the restream branch's queue from max-size-time=1s to 150ms so a slow/stalled restream peer bounds out sooner, and bump the socket-read packet-rate log from debug to an [FPS-TRACE] info line so it lines up with the decode/display bottleneck counters.
Drop the once/sec [FPS-TRACE] socket packet-rate log added while bottleneck-hunting; the decode/handoff/display counters it was meant to line up with (main.cpp) were never committed and have also been removed. Back to the original debug-level packet-rate log.
The dev checkout's parent directory has a literal space in its name
("openipc@100hz wifilinkvrx"), which sits above both this repo and
sbc-groundstations. pkg-config emits -I/-L flags as one plain,
unquoted, space-joined string; CMake's FindPkgConfig naively splits
that on whitespace, so any path containing a space loses its -I/-L
prefix. Symptom when configuring a target (aarch64) build against the
sbc-groundstations buildroot sysroot: "fatal error: drm.h: No such
file or directory" from xf86drm.h, plus gcc warnings about sysroot
include paths being passed as bare linker inputs.
tools/cross_configure_target.sh documents the cause and regenerates a
pkg-config wrapper that forces PKG_CONFIG_* to a space-free /tmp
mirror of the buildroot output tree, then configures a build directory
against it. The wrapper's env assignments must be unconditional, not
${VAR:-default}: buildroot's generated toolchainfile.cmake exports
PKG_CONFIG_SYSROOT_DIR (pointing at the real, space-having path) before
pkg-config ever runs, which silently defeats a fallback-style default.
The kernel-generated CVT-RB and standard CVT timings for 1280x720@100 either corrupt the picture (cropping/miscoloring/striping) or produce no signal at all on this RK3566 board's HDMI-in on a Skyzone O4X Pro (firmware 4.2.1). Even the "textbook" CEA-family timing captured from an EDID describing a real Walksnail VRX's expected output still crops on this unit. Add the modeline arrived at by hand-tuning porches via live modetest against this specific VRX+goggles pairing (mainly a larger horizontal back porch and adjusted vertical front porch), applied only when the requested mode is exactly 1280x720@100. Also force quant_range=1 (limited range) on the connector in the same atomic commit as the modeset -- a non-atomic property write was silently ignored and left colors oversaturated. This is flagged experimental/hardware-specific, not a general default: directly verified not to generalize to the textbook timing presumably correct for other Walksnail VRX + goggles pairings. See issue_draft.md for the full writeup and the open question about actual Walksnail VRX HDMI output timing.
Adds a master switch (dvr_on_signal_enabled) that starts DVR recording when the GS starts receiving data from the air unit and stops it when that signal drops out. Wired into menu.c's drone_detect_timer -- the same detected/lost transition that already greys out the drone pages -- so no separate polling is needed, plus a live GS-menu toggle (Auto-record on Signal, on the DVR settings page) and a --dvr-on-signal CLI flag for starting enabled from launch.
New IconSelectorWidget entries in config_osd.json driven by the added src/icons/*.png set: an armed/disarmed indicator, an 8-point compass heading icon, and a GPS fix-quality icon (2D/3D/no-fix/none).
Reverts the icon-based widgets and their PNG assets, no longer referenced anywhere in config_osd.json. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LWYaFJL5iNrmr73dY6Tvuz
The VTX's SoC temp isn't local hardware and isn't known to the flight controller, so it can't go through the existing os_sensors/MSP paths. A small script on the VTX opens a plain TCP connection over the wfb-ng tunnel and writes the temperature as ASCII; VtxTempSensor listens for it independently of MspDisplayPortWidget telemetry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LWYaFJL5iNrmr73dY6Tvuz
SO_REUSEADDR: on some boards an unrelated process (wpa_supplicant/ udhcpc sharing a duplicated fd) intermittently holds this port at boot, failing bind() and silently aborting widget setup -- the startup preview then stays stuck with nothing left to clear it. Buffer: msposd aggregates multiple MSP DisplayPort commands per UDP datagram (wfb-ng's radio_mtu is 1445 bytes), so a full-screen OSD update can exceed the previous 1024-byte buffer. recvfrom() silently truncates oversized datagrams, dropping trailing commands (often the terminating DRAW_SCREEN) and leaving stale glyphs on screen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LWYaFJL5iNrmr73dY6Tvuz
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PR Summary by QodoAdd signal-driven DVR and improve VRX video, OSD, and telemetry
AI Description
Diagram
High-Level Assessment
Files changed (12)
|
Code Review by Qodo
1. Auto record ignores an existing signal
|
| // Live toggle for --dvr-on-signal, so the GS menu can flip it without a | ||
| // restart (dvr_on_signal_enabled itself is read by menu.c's | ||
| // drone_detect_timer on every signal-acquired/lost transition). | ||
| void dvr_set_on_signal(int enabled) { dvr_on_signal_enabled = (bool)enabled; } |
There was a problem hiding this comment.
1. Auto record ignores an existing signal 🐞 Bug ≡ Correctness
dvr_set_on_signal only changes the feature flag, while drone_detect_timer starts recording exclusively inside a later detected-state transition. Enabling Auto Record after the air unit is already detected therefore leaves recording stopped until the signal is lost and acquired again.
Agent Prompt
## Issue description
Enabling Auto Record while signal detection is already active does not start recording because the setter does not reconcile the current detected state.
## Fix Focus Areas
- src/main.cpp[848-852]
- src/menu.c[62-87]
- src/gsmenu/colmenu.c[1099-1111]
## Recommended Fix
Store or expose the current signal state to the DVR automation and reconcile recording ownership immediately when the feature is enabled, rather than waiting for another signal transition.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if(dvr_on_signal_enabled) { | ||
| if(detected) dvr_start_all(); | ||
| else dvr_stop_all(); | ||
| } |
There was a problem hiding this comment.
2. Signal loss cuts off armed recordings 🐞 Bug ≡ Correctness
drone_detect_timer calls the global dvr_stop_all whenever detection is lost, without tracking whether signal automation started the active recording. When signal automation coexists with manual recording or recording-on-arm, a transient two-second link loss clears the shared recording state and stops both recorder branches.
Agent Prompt
## Issue description
Signal-loss automation stops the shared DVR unconditionally, including recordings requested manually or by the armed-state automation.
## Fix Focus Areas
- src/menu.c[32-45]
- src/menu.c[62-87]
- src/mavlink.c[144-154]
- src/main.cpp[738-758]
## Recommended Fix
Represent manual, signal, and armed recording requests as independent intents, derive the effective recording state from their union, and stop the backends only when no intent remains active.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| osd_tag tags[1]; | ||
| strcpy(tags[0].key, "name"); | ||
| strcpy(tags[0].val, "vtx"); | ||
| osd_publish_double_fact("vtx.temperature", tags, 1, latest.load()); |
There was a problem hiding this comment.
5. Temperature readouts show stale data 🐞 Bug ≡ Correctness
VtxTempSensor initializes latest to zero and publishes it on every run without recording whether a valid sample exists or when it arrived. Before the first connection the readout reports zero, and after the feed disappears it republishes the final temperature indefinitely as current data.
Agent Prompt
## Issue description
The remote temperature sensor publishes an initial zero and indefinitely republishes old samples without any validity or freshness state.
## Fix Focus Areas
- src/os_mon.cpp[432-440]
- src/os_mon.cpp[455-472]
- src/main.cpp[1081-1089]
## Recommended Fix
Track whether a complete valid sample has arrived and its timestamp; suppress or invalidate the fact until the first sample and after a configured freshness timeout.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| BUILD_DIR="${1:-$REPO_DIR/build_target}" | ||
| SBC_DIR="${2:-$REPO_DIR/../sbc-groundstations}" |
There was a problem hiding this comment.
6. Relative paths break cross-build setup 🐞 Bug ≡ Correctness
cross_configure_target.sh preserves an explicitly supplied relative SBC_DIR and uses it directly as the target of a symlink located under /tmp. Because that target is resolved relative to /tmp, a valid relative argument can pass the initial toolchain check but produce a broken mirror and an unusable pkg-config wrapper.
Agent Prompt
## Issue description
A relative SBC repository argument is reused as a `/tmp` symlink target, where it resolves from a different directory and breaks the generated wrapper paths.
## Fix Focus Areas
- tools/cross_configure_target.sh[41-59]
- tools/cross_configure_target.sh[61-72]
## Recommended Fix
After validating the directory, canonicalize `SBC_DIR` to an absolute path with `cd` and `pwd` before deriving the toolchain path or creating the `/tmp` symlink.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| static const colmenu_page_t sys_display_page = { "Display", "gs", "system", sys_display_items, 5 }; | ||
| static const colmenu_item_t sys_dvr_items[] = { | ||
| { .kind=COLMENU_SWITCH, .label="Enabled", .param="rec_enabled", .on_change=on_rec_enabled }, | ||
| { .kind=COLMENU_SWITCH, .label="Auto Record", .param="dvr_on_signal", .on_change=on_dvr_on_signal }, |
There was a problem hiding this comment.
3. The menu cannot enable auto recording 🐞 Bug ≡ Correctness
do_set() routes the new dvr_on_signal row through the generic gsmenu.sh command path before invoking on_dvr_on_signal(), but the bundled script defines neither a getter nor a setter for that parameter. Opening or toggling Auto Record reaches the script’s unknown-command failure, and because the dispatcher runs callbacks only after a successful command, dvr_set_on_signal never updates dvr_on_signal_enabled on hardware.
Agent Prompt
## Issue description
The new Auto Record menu row uses `dvr_on_signal`, but the bundled `gsmenu.sh` command interface does not support reading or writing this parameter. Consequently, reads fail and asynchronous writes complete unsuccessfully before the app-side callback can update the runtime flag.
## Fix Focus Areas
- src/gsmenu/colmenu_pages.c[110-114]
- src/gsmenu/colmenu_pages.c[438-449]
- src/gsmenu/colmenu_pages.c[440-440]
- gsmenu.sh[414-473]
- gsmenu.sh[414-470]
- src/gsmenu/colmenu.c[399-420]
## Recommended Fix
Add matching `get gs system dvr_on_signal` and `set gs system dvr_on_signal` cases to `gsmenu.sh`, following the project’s intended persistent GS configuration mechanism. Ensure the getter returns the persisted switch value and the setter exits successfully so `on_dvr_on_signal()` executes; alternatively, handle this parameter entirely in the app like the existing runtime-only menu parameters.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| int fd = accept(server_fd, reinterpret_cast<sockaddr *>(&client), &len); | ||
| if (fd < 0) { | ||
| continue; // server_fd was closed (shutdown) -> loop exits on `running` check | ||
| } | ||
| char buf[64] = {0}; | ||
| ssize_t n = read(fd, buf, sizeof(buf) - 1); | ||
| if (n > 0) { |
There was a problem hiding this comment.
4. Shutdown hangs on idle temperature client 🐞 Bug ☼ Reliability
VtxTempSensor::listenLoop() performs a blocking read() on an accepted client descriptor, while its destructor sets running, shuts down only the listening descriptor, and then joins the worker thread. A client that connects without sending data or closing its connection leaves the worker blocked on the separate accepted descriptor, so process teardown waits indefinitely in worker.join().
Agent Prompt
## Issue description
The VTX temperature worker can block indefinitely while reading from an accepted TCP client. Shutdown changes `running` and closes only the listening socket, so it cannot wake a worker blocked on an idle accepted socket, preventing the destructor from joining the thread.
## Fix Focus Areas
- src/os_mon.cpp[421-430]
- src/os_mon.cpp[447-465]
## Recommended Fix
Make accepted-client reads interruptible during shutdown. Retain the active client descriptor under synchronization and shut it down from the destructor before joining, and/or use a finite receive timeout or nonblocking I/O with polling that checks `running`. Ensure the client descriptor is closed on every exit path before the worker terminates.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
--dvr-on-signal: auto-start DVR recording when the air unit's signal is acquired, auto-stop when it's lost (mirrors the existing--mavlink-dvr-on-armbehavior but doesn't require a MAVLink link). Live-toggleable from the GS menu (System → DVR → Auto Record) without a restart.msposd's multi-command datagrams were being silently truncated by an undersized receive buffer, and the socket bind could fail if the port was transiently held at boot).drop=true max-buffers=1) so a decode/pull stall skips straight to live instead of buffering and fast-forwarding once it clears.Test plan
--dvr-on-signalstarts/stops recording on link acquire/loss and the live GS-menu toggle persists correctly.🤖 Generated with Claude Code