From 0474d1c155f5ed30cda993f2a707376302101298 Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Thu, 10 Sep 2026 20:16:05 +0300 Subject: [PATCH 01/12] Bound video appsink queues so drop=true actually drops 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. --- src/gstrtpreceiver.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/gstrtpreceiver.cpp b/src/gstrtpreceiver.cpp index 4c6bc9d7..a7411562 100644 --- a/src/gstrtpreceiver.cpp +++ b/src/gstrtpreceiver.cpp @@ -372,7 +372,11 @@ namespace { static std::string create_restream_branch() { std::stringstream ss; ss << " rtp_tee. ! valve name=restream_valve drop=true" - " ! queue leaky=downstream max-size-buffers=0 max-size-bytes=0 max-size-time=1000000000 silent=true" + // Keep the restream branch shallow: leaky=downstream drops the + // oldest buffered data first, but max-size-time was 1s, so a + // stalled/slow restream peer could sit a full second of stale + // video behind live before drops even start. 150ms bounds that. + " ! queue leaky=downstream max-size-buffers=0 max-size-bytes=0 max-size-time=150000000 silent=true" " ! udpsink name=restream_sink host=0.0.0.0 port=5600 sync=false async=false qos=false"; return ss.str(); } @@ -1320,7 +1324,12 @@ std::string GstRtpReceiver::construct_gstreamer_pipeline() } ss< UDP socket), + // at info so it lines up with the [FPS-TRACE] decode/display counters + // without needing --log-level debug for a bottleneck hunt. pkt_counter++; auto now = std::chrono::steady_clock::now(); if (now - last_report >= std::chrono::seconds(1)) { - spdlog::debug("socket pkts/s {}", pkt_counter); + spdlog::info("[FPS-TRACE] net_pkts={}/s", pkt_counter); pkt_counter = 0; last_report = now; } @@ -1555,7 +1566,10 @@ std::string GstRtpReceiver::construct_file_playback_pipeline(const char * file_p ss<<"filesrc location="< Date: Thu, 10 Sep 2026 20:29:38 +0300 Subject: [PATCH 02/12] Remove FPS-TRACE net_pkts instrumentation 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. --- src/gstrtpreceiver.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gstrtpreceiver.cpp b/src/gstrtpreceiver.cpp index a7411562..c4d436f1 100644 --- a/src/gstrtpreceiver.cpp +++ b/src/gstrtpreceiver.cpp @@ -1477,13 +1477,11 @@ static void loop_read_socket(bool& keep_looping, int sock_fd, GstAppSrc* appsrc) break; } - // Log packet rate: earliest pipeline stage (radio link -> UDP socket), - // at info so it lines up with the [FPS-TRACE] decode/display counters - // without needing --log-level debug for a bottleneck hunt. + // Log packet rate (optional) pkt_counter++; auto now = std::chrono::steady_clock::now(); if (now - last_report >= std::chrono::seconds(1)) { - spdlog::info("[FPS-TRACE] net_pkts={}/s", pkt_counter); + spdlog::debug("socket pkts/s {}", pkt_counter); pkt_counter = 0; last_report = now; } From 15e3eee2146b5db8fccf4e67a33bb7c2fffcca0d Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Thu, 10 Sep 2026 20:29:38 +0300 Subject: [PATCH 03/12] Add script documenting the cross-build space-in-path pkg-config fix 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. --- tools/cross_configure_target.sh | 90 +++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100755 tools/cross_configure_target.sh diff --git a/tools/cross_configure_target.sh b/tools/cross_configure_target.sh new file mode 100755 index 00000000..e227d7b1 --- /dev/null +++ b/tools/cross_configure_target.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# +# Configure a PixelPilot_rk build against the sbc-groundstations buildroot +# cross toolchain/sysroot (aarch64), for local dev iteration without a board +# or the container_build.sh Debian package flow. +# +# WHY THIS SCRIPT EXISTS (read before deleting it): +# +# This checkout's parent directory has a literal space in its name +# ("openipc@100hz wifilinkvrx"), sitting above both this repo and +# sbc-groundstations. pkg-config emits its -I/-L flags as one plain, +# unquoted, space-joined string, and CMake's FindPkgConfig naively splits +# that string on whitespace -- so any path containing a space gets torn in +# two and silently loses its -I/-L prefix. Symptom: cross-compiling against +# the buildroot sysroot fails with things like +# "fatal error: drm.h: No such file or directory" from xf86drm.h, and gcc +# warnings like "linker input file not found" naming a bare sysroot include +# path with no -I in front of it -- even though the sysroot itself has every +# header/lib pkg-config claims it does. +# +# Fix: route pkg-config through a pkgconf wrapper whose PKG_CONFIG_* env +# vars are unconditionally forced to point at a space-free /tmp mirror of +# the buildroot output tree (a symlink -- pkg-config only ever sees/prints +# the space-free string; the OS resolves the symlink transparently at +# file-open time). The assignments in the wrapper have to be unconditional, +# not `${VAR:-default}`: buildroot's generated toolchainfile.cmake does +# `set(ENV{PKG_CONFIG_SYSROOT_DIR} "")` before +# pkg-config ever runs, which silently defeats a fallback-style default. +# +# The wrapper + symlinks live under /tmp, so they don't survive a reboot -- +# this script regenerates them and configures a fresh build directory +# against them. Safe to re-run any time. +# +# Usage: +# tools/cross_configure_target.sh [build_dir] [sbc-groundstations repo dir] +# +# Then: cmake --build --target pixelpilot -j"$(nproc)" + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD_DIR="${1:-$REPO_DIR/build_target}" +SBC_DIR="${2:-$REPO_DIR/../sbc-groundstations}" +DEFCONFIG="${DEFCONFIG:-runcam_wifilink_defconfig}" + +TOOLCHAIN_FILE="$SBC_DIR/output/$DEFCONFIG/host/share/buildroot/toolchainfile.cmake" +if [ ! -f "$TOOLCHAIN_FILE" ]; then + echo "error: no buildroot toolchain file at: $TOOLCHAIN_FILE" >&2 + echo " build sbc-groundstations first, or pass its path: $0 " >&2 + exit 1 +fi + +NOSPACE_ROOT="/tmp/sbc-groundstations-nospace" +PKGCONF_WRAPPER="/tmp/pixelpilot-cross-pkgconf.sh" +HOST_BIN="$NOSPACE_ROOT/output/$DEFCONFIG/host/bin" +SYSROOT="$HOST_BIN/../aarch64-buildroot-linux-gnu/sysroot" + +ln -sfn "$SBC_DIR" "$NOSPACE_ROOT" + +cat > "$PKGCONF_WRAPPER" < Date: Thu, 10 Sep 2026 20:33:28 +0300 Subject: [PATCH 04/12] Add hand-tuned 720p@100Hz modeline for Skyzone O4X Pro HDMI-in 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. --- src/drm.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/drm.c b/src/drm.c index bf767001..9eced5f7 100644 --- a/src/drm.c +++ b/src/drm.c @@ -584,6 +584,39 @@ struct modeset_output *modeset_output_create(int fd, drmModeRes *res, drmModeCon goto out_error; } + if (mode_width == 1280 && mode_height == 720 && mode_vrefresh == 100) { + /* Custom timing tuned by hand for this Skyzone O4X Pro unit's HDMI-in lock: + swept horizontal/vertical front porch and back porch against the + connector's stock 720p60 shape until crop on all four edges cleared. */ + drmModeModeInfo custom; + memset(&custom, 0, sizeof(custom)); + custom.clock = 150705; + custom.hdisplay = 1280; + custom.hsync_start = 1380; + custom.hsync_end = 1420; + custom.htotal = 1970; + custom.hskew = 0; + custom.vdisplay = 720; + custom.vsync_start = 740; + custom.vsync_end = 745; + custom.vtotal = 765; + custom.vscan = 0; + custom.vrefresh = 100; + custom.flags = 0; + custom.type = 0x40; /* DRM_MODE_TYPE_USERDEF */ + snprintf(custom.name, sizeof(custom.name), "custom1280x720-100"); + memcpy(&out->mode, &custom, sizeof(out->mode)); + { + uint32_t requested_mhz = mode_refresh_mhz(&custom); + fprintf(stdout, "Using custom 1280x720 modeline: clock=%ukHz htotal=%u vtotal=%u " + "-> requested refresh %u.%03u Hz (this is what we asked the kernel " + "for; it does not confirm the display actually locks at this rate)\n", + custom.clock, custom.htotal, custom.vtotal, + requested_mhz / 1000, requested_mhz % 1000); + } + goto have_mode; + } + int fc = 0; int preferred_fc = -1; if (mode_width>0 && mode_height>0 && mode_vrefresh>0) { @@ -625,6 +658,7 @@ struct modeset_output *modeset_output_create(int fd, drmModeRes *res, drmModeCon printf( "Using screen mode %dx%d@%d (%u.%03u Hz)\n",conn->modes[fc].hdisplay, conn->modes[fc].vdisplay , conn->modes[fc].vrefresh, used_mhz / 1000, used_mhz % 1000 ); } memcpy(&out->mode, &conn->modes[fc], sizeof(out->mode)); +have_mode: if (drmModeCreatePropertyBlob(fd, &out->mode, sizeof(out->mode), &out->mode_blob_id) != 0) { fprintf(stderr, "couldn't create a blob property\n"); goto out_error; @@ -898,6 +932,12 @@ int modeset_atomic_prepare_commit(int fd, struct modeset_output *out, drmModeAto { if (set_drm_object_property(req, &out->connector, "CRTC_ID", out->crtc.id) < 0) return -1; + if (out->mode.hdisplay == 1280 && out->mode.vdisplay == 720 && out->mode.vrefresh == 100) { + /* Force limited-range output for our custom 720p100 timing: colors + looked oversaturated under the driver's default (full range) + output on this connector's non-standard userdef mode. */ + set_drm_object_property(req, &out->connector, "quant_range", 1); + } if (set_drm_object_property(req, &out->crtc, "MODE_ID", out->mode_blob_id) < 0) return -1; if (set_drm_object_property(req, &out->crtc, "ACTIVE", 1) < 0) From eb478435a5a75a2540ee13ab3ed0725b3929d400 Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Thu, 10 Sep 2026 20:33:34 +0300 Subject: [PATCH 05/12] Add --dvr-on-signal: auto-record while the air unit's signal is present 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. --- debian/manpage.md | 6 ++++++ src/dvr.cpp | 3 +++ src/dvr.h | 6 ++++++ src/gsmenu/colmenu_pages.c | 9 ++++++++- src/main.cpp | 16 +++++++++++++++- src/menu.c | 7 +++++++ 6 files changed, 45 insertions(+), 2 deletions(-) diff --git a/debian/manpage.md b/debian/manpage.md index 40e3e9d7..6dbd7e38 100644 --- a/debian/manpage.md +++ b/debian/manpage.md @@ -52,6 +52,9 @@ pixelpilot-rk - OpenIPC video display client for wfb-ng --mavlink-dvr-on-arm - Start recording when armed + --dvr-on-signal - Start recording when the air unit's signal is + acquired, stop when it's lost + --codec - Video codec, should be the same as on VTX (Default: h265 ) --log-level - Log verbosity level, debug|info|warn|error (Default: info) @@ -123,6 +126,9 @@ a complete description, see the **info**(1) files. **--mavlink-dvr-on-arm** : Start recording when armed +**--dvr-on-signal** +: Start recording when the air unit's signal is acquired, stop when it's lost + **--codec \** : Video codec, should be the same as on VTX (Default: h265 ) diff --git a/src/dvr.cpp b/src/dvr.cpp index 86d859d6..bc96821d 100644 --- a/src/dvr.cpp +++ b/src/dvr.cpp @@ -15,6 +15,9 @@ namespace fs = std::filesystem; // Master record flag; defined here, declared extern in dvr.h. int dvr_enabled = 0; +// --dvr-on-signal master switch; defined here, declared extern in dvr.h. +bool dvr_on_signal_enabled = false; + const int SEQUENCE_PADDING = 4; // zero-padding width for --dvr-sequenced-files std::string dvr_next_base_path(const char* filename_template, bool with_sequence) { diff --git a/src/dvr.h b/src/dvr.h index 080671c4..0927de04 100644 --- a/src/dvr.h +++ b/src/dvr.h @@ -14,6 +14,12 @@ enum DvrMode { DVR_MODE_RAW = 0, DVR_MODE_REENCODE = 1, DVR_MODE_BOTH = 2 }; // mirrored to the receiver's recorders. extern int dvr_enabled; +// --dvr-on-signal: auto-start recording when the GS starts receiving data +// from the air unit (tunnel/RX traffic) and auto-stop when it drops out. +// Watched by menu.c's drone_detect_timer, the same signal that greys out the +// drone pages when the VTX isn't seen. +extern bool dvr_on_signal_enabled; + // Resolve the next DVR recording's base path from a filename template WITHOUT // the extension: /[NNNN_] minus a trailing ".mp4". // Honours --dvr-sequenced-files. Returns "" if the directory does not exist. diff --git a/src/gsmenu/colmenu_pages.c b/src/gsmenu/colmenu_pages.c index fbda4b66..8782cfc5 100644 --- a/src/gsmenu/colmenu_pages.c +++ b/src/gsmenu/colmenu_pages.c @@ -52,6 +52,7 @@ void dvr_set_mode(int mode); void dvr_set_max_size(int mb); void dvr_start_all(void); void dvr_stop_all(void); +void dvr_set_on_signal(int enabled); #endif static void on_live_colortrans(const char * value) @@ -106,6 +107,11 @@ static void on_rec_enabled(const char * value) /* start/stop recording now */ if(on) MENU_LIVE(dvr_start_all(), "dvr_start_all()%s", ""); else MENU_LIVE(dvr_stop_all(), "dvr_stop_all()%s", ""); } +static void on_dvr_on_signal(const char * value) /* auto rec while the air unit's signal is present */ +{ + int on = (value && strcmp(value, "on") == 0) ? 1 : 0; + MENU_LIVE(dvr_set_on_signal(on), "dvr_set_on_signal(%d)", on); +} static void on_dvr_osd(const char * value) /* burn OSD into the re-encode */ { int on = (value && strcmp(value, "on") == 0) ? 1 : 0; @@ -431,6 +437,7 @@ static const colmenu_item_t sys_display_items[] = { 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 on Signal", .param="dvr_on_signal", .on_change=on_dvr_on_signal }, { .kind=COLMENU_DROPDOWN, .label="Mode", .param="dvr_mode", .on_change=on_dvr_mode }, { .kind=COLMENU_SLIDER, .label="Max file size (MB)", .param="dvr_max_size", .on_change=on_dvr_max_size, .display_scale=100 }, { .kind=COLMENU_DROPDOWN, .label="Codec", .param="dvr_reenc_codec", .on_change=on_dvr_reenc_codec }, @@ -439,7 +446,7 @@ static const colmenu_item_t sys_dvr_items[] = { { .kind=COLMENU_DROPDOWN, .label="Bitrate (kbps)", .param="dvr_reenc_bitrate", .on_change=on_dvr_reenc_bitrate }, { .kind=COLMENU_SWITCH, .label="Record OSD in DVR", .param="dvr_osd", .on_change=on_dvr_osd }, }; -static const colmenu_page_t sys_dvr_page = { "DVR", "gs", "system", sys_dvr_items, 8 }; +static const colmenu_page_t sys_dvr_page = { "DVR", "gs", "system", sys_dvr_items, 9 }; /* Restream to phone/laptop over the local WiFi. Both rows are served by the app * itself, not gsmenu.sh — colmenu.c intercepts the "restream_" params on read diff --git a/src/main.cpp b/src/main.cpp index bb0050c7..ddbf75a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -516,7 +516,7 @@ void *__DISPLAY_THREAD__(void *param) uint64_t decode_and_handover_display_ms=get_time_ms()-decoding_pts; osd_publish_uint_fact("video.decode_and_handover_ms", NULL, 0, decode_and_handover_display_ms); } -end: +end: spdlog::info("Display thread done."); return nullptr; } @@ -844,6 +844,12 @@ extern "C" { void dvr_reenc_set_mode(int enabled) { dvr_set_mode(enabled ? DVR_MODE_REENCODE : DVR_MODE_RAW); } + + // 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; } + int dvr_get_on_signal(void) { return (int)dvr_on_signal_enabled; } } int decoder_stalled_count=0; @@ -1236,6 +1242,9 @@ void printHelp() { "\n" " --mavlink-dvr-on-arm - Start recording when armed\n" "\n" + " --dvr-on-signal - Start recording when the air unit's signal is\n" + " acquired, stop when it's lost\n" + "\n" " --codec - Video codec, should be the same as on VTX (Default: h265 )\n" "\n" " --audio - Play Opus audio muxed into the RTP stream (same port, by payload type)\n" @@ -1516,6 +1525,11 @@ int main(int argc, char **argv) continue; } + __OnArgument("--dvr-on-signal") { + dvr_on_signal_enabled = true; + continue; + } + __OnArgument("--osd") { enable_osd = 1; mavlink_thread = 1; diff --git a/src/menu.c b/src/menu.c index 91315b2a..a3975082 100644 --- a/src/menu.c +++ b/src/menu.c @@ -25,6 +25,7 @@ enum RXMode RXMODE = WFB; /* DVR backend (defined in main.cpp / dvr.cpp). */ extern int dvr_enabled; +extern bool dvr_on_signal_enabled; /* --dvr-on-signal master switch */ void dvr_start_all(void); void dvr_stop_all(void); @@ -77,6 +78,12 @@ static void drone_detect_timer(lv_timer_t * t) detected = now; setenv("GSMENU_VTX_DETECTED", detected ? "1" : "0", 1); colmenu_set_drone_detected(detected); +#ifndef USE_SIMULATOR + if(dvr_on_signal_enabled) { + if(detected) dvr_start_all(); + else dvr_stop_all(); + } +#endif } } From 1b23c40a8ce96db674099e0bc7cb25f9e0c00ea8 Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Thu, 10 Sep 2026 20:33:40 +0300 Subject: [PATCH 06/12] Add armed/disarmed, compass, and GPS-fix icon widgets to OSD 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). --- config_osd.json | 1122 ++++++++++++++++++++++++++++++++------ src/icons/armed.png | Bin 0 -> 190 bytes src/icons/compass_e.png | Bin 0 -> 159 bytes src/icons/compass_n.png | Bin 0 -> 159 bytes src/icons/compass_ne.png | Bin 0 -> 416 bytes src/icons/compass_nw.png | Bin 0 -> 405 bytes src/icons/compass_s.png | Bin 0 -> 163 bytes src/icons/compass_se.png | Bin 0 -> 382 bytes src/icons/compass_sw.png | Bin 0 -> 415 bytes src/icons/compass_w.png | Bin 0 -> 162 bytes src/icons/disarmed.png | Bin 0 -> 193 bytes src/icons/gps_2d.png | Bin 0 -> 192 bytes src/icons/gps_3d.png | Bin 0 -> 180 bytes src/icons/gps_nofix.png | Bin 0 -> 203 bytes src/icons/gps_none.png | Bin 0 -> 229 bytes 15 files changed, 970 insertions(+), 152 deletions(-) create mode 100644 src/icons/armed.png create mode 100644 src/icons/compass_e.png create mode 100644 src/icons/compass_n.png create mode 100644 src/icons/compass_ne.png create mode 100644 src/icons/compass_nw.png create mode 100644 src/icons/compass_s.png create mode 100644 src/icons/compass_se.png create mode 100644 src/icons/compass_sw.png create mode 100644 src/icons/compass_w.png create mode 100644 src/icons/disarmed.png create mode 100644 src/icons/gps_2d.png create mode 100644 src/icons/gps_3d.png create mode 100644 src/icons/gps_nofix.png create mode 100644 src/icons/gps_none.png diff --git a/config_osd.json b/config_osd.json index 8e556ee1..ec6fa826 100644 --- a/config_osd.json +++ b/config_osd.json @@ -2,256 +2,902 @@ "format": "0.0.1", "assets_dir": "/usr/share/pixelpilot/", "widgets": [ - { + { "name": "Metrics background", "type": "BoxWidget", - "x": -270, + "x": -430, "y": 0, "width": 270, "height": 130, "color": { - "r": 0.0, - "g": 0.0, - "b": 0.0, - "alpha": 0.4 + "r": 0.0, + "g": 0.0, + "b": 0.0, + "alpha": 0.4 }, "facts": [] - }, - { + }, + { "type": "IconSelectorWidget", "name": "RSSI of antenna 1", - "x": -250, + "x": -410, "y": 0, "facts": [ - {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "0", "id": "video rx"}} + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "0", + "id": "video rx" + } + } ], "ranges_and_icons": [ - {"range": [-40, 1], "icon_path": "signal1.png"}, - {"range": [-48, -41], "icon_path": "signal2.png"}, - {"range": [-56, -49], "icon_path": "signal3.png"}, - {"range": [-64, -57], "icon_path": "signal4.png"}, - {"range": [-72, -65], "icon_path": "signal5.png"}, - {"range": [-80, -73], "icon_path": "signal6.png"}, - {"range": [-90, -81], "icon_path": "signal7.png"}, - {"range": [-130, -91], "icon_path": "signal8.png"} + { + "range": [ + -40, + 1 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + -48, + -41 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + -56, + -49 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + -64, + -57 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + -72, + -65 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + -80, + -73 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + -90, + -81 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + -130, + -91 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 2", - "x": -205, + "x": -365, "y": 0, "facts": [ - {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "1", "id": "video rx"}} + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "1", + "id": "video rx" + } + } ], "ranges_and_icons": [ - {"range": [-40, 1], "icon_path": "signal1.png"}, - {"range": [-48, -41], "icon_path": "signal2.png"}, - {"range": [-56, -49], "icon_path": "signal3.png"}, - {"range": [-64, -57], "icon_path": "signal4.png"}, - {"range": [-72, -65], "icon_path": "signal5.png"}, - {"range": [-80, -73], "icon_path": "signal6.png"}, - {"range": [-90, -81], "icon_path": "signal7.png"}, - {"range": [-130, -91], "icon_path": "signal8.png"} + { + "range": [ + -40, + 1 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + -48, + -41 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + -56, + -49 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + -64, + -57 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + -72, + -65 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + -80, + -73 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + -90, + -81 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + -130, + -91 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 3", - "x": -160, + "x": -320, "y": 0, "facts": [ - {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "256", "id": "video rx"}} + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "256", + "id": "video rx" + } + } ], "ranges_and_icons": [ - {"range": [-40, 1], "icon_path": "signal1.png"}, - {"range": [-48, -41], "icon_path": "signal2.png"}, - {"range": [-56, -49], "icon_path": "signal3.png"}, - {"range": [-64, -57], "icon_path": "signal4.png"}, - {"range": [-72, -65], "icon_path": "signal5.png"}, - {"range": [-80, -73], "icon_path": "signal6.png"}, - {"range": [-90, -81], "icon_path": "signal7.png"}, - {"range": [-130, -91], "icon_path": "signal8.png"} + { + "range": [ + -40, + 1 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + -48, + -41 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + -56, + -49 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + -64, + -57 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + -72, + -65 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + -80, + -73 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + -90, + -81 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + -130, + -91 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 4", - "x": -115, + "x": -275, "y": 0, "facts": [ - {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "257", "id": "video rx"}} + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "257", + "id": "video rx" + } + } ], "ranges_and_icons": [ - {"range": [-40, 1], "icon_path": "signal1.png"}, - {"range": [-48, -41], "icon_path": "signal2.png"}, - {"range": [-56, -49], "icon_path": "signal3.png"}, - {"range": [-64, -57], "icon_path": "signal4.png"}, - {"range": [-72, -65], "icon_path": "signal5.png"}, - {"range": [-80, -73], "icon_path": "signal6.png"}, - {"range": [-90, -81], "icon_path": "signal7.png"}, - {"range": [-130, -91], "icon_path": "signal8.png"} + { + "range": [ + -40, + 1 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + -48, + -41 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + -56, + -49 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + -64, + -57 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + -72, + -65 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + -80, + -73 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + -90, + -81 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + -130, + -91 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 5", - "x": -70, + "x": -230, "y": 0, "facts": [ - {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "512", "id": "video rx"}} + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "512", + "id": "video rx" + } + } ], "ranges_and_icons": [ - {"range": [-40, 1], "icon_path": "signal1.png"}, - {"range": [-48, -41], "icon_path": "signal2.png"}, - {"range": [-56, -49], "icon_path": "signal3.png"}, - {"range": [-64, -57], "icon_path": "signal4.png"}, - {"range": [-72, -65], "icon_path": "signal5.png"}, - {"range": [-80, -73], "icon_path": "signal6.png"}, - {"range": [-90, -81], "icon_path": "signal7.png"}, - {"range": [-130, -91], "icon_path": "signal8.png"} + { + "range": [ + -40, + 1 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + -48, + -41 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + -56, + -49 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + -64, + -57 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + -72, + -65 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + -80, + -73 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + -90, + -81 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + -130, + -91 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 6", - "x": -25, + "x": -185, "y": 0, "facts": [ - {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "513", "id": "video rx"}} + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "513", + "id": "video rx" + } + } ], "ranges_and_icons": [ - {"range": [-40, 1], "icon_path": "signal1.png"}, - {"range": [-48, -41], "icon_path": "signal2.png"}, - {"range": [-56, -49], "icon_path": "signal3.png"}, - {"range": [-64, -57], "icon_path": "signal4.png"}, - {"range": [-72, -65], "icon_path": "signal5.png"}, - {"range": [-80, -73], "icon_path": "signal6.png"}, - {"range": [-90, -81], "icon_path": "signal7.png"}, - {"range": [-130, -91], "icon_path": "signal8.png"} + { + "range": [ + -40, + 1 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + -48, + -41 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + -56, + -49 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + -64, + -57 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + -72, + -65 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + -80, + -73 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + -90, + -81 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + -130, + -91 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 1 antenna 1", - "x": -250, + "x": -410, "y": 0, "facts": [ { "__comment": "APFPV counterpart of the six widgets above: same slots, but fed by the WiFi driver instead of wfb-ng. Only one of the two sets is ever published, the other stays hidden. RSSI is a percentage here, not dBm. `adapter` counts the cards from 0 in interface-name order, `type` picks the RF path", - "name": "os_mon.wifi.rssi", "tags": {"adapter": "0", "type": "rssi_a"} + "name": "os_mon.wifi.rssi", + "tags": { + "adapter": "0", + "type": "rssi_a" + } } ], "ranges_and_icons": [ - {"range": [70, 100], "icon_path": "signal1.png"}, - {"range": [60, 69], "icon_path": "signal2.png"}, - {"range": [50, 59], "icon_path": "signal3.png"}, - {"range": [40, 49], "icon_path": "signal4.png"}, - {"range": [30, 39], "icon_path": "signal5.png"}, - {"range": [20, 29], "icon_path": "signal6.png"}, - {"range": [10, 19], "icon_path": "signal7.png"}, - {"range": [1, 9], "icon_path": "signal8.png"} + { + "range": [ + 70, + 100 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + 60, + 69 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + 50, + 59 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + 40, + 49 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + 30, + 39 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + 20, + 29 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + 10, + 19 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + 1, + 9 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 1 antenna 2", - "x": -205, + "x": -365, "y": 0, "facts": [ - {"name": "os_mon.wifi.rssi", "tags": {"adapter": "0", "type": "rssi_b"}} + { + "name": "os_mon.wifi.rssi", + "tags": { + "adapter": "0", + "type": "rssi_b" + } + } ], "ranges_and_icons": [ - {"range": [70, 100], "icon_path": "signal1.png"}, - {"range": [60, 69], "icon_path": "signal2.png"}, - {"range": [50, 59], "icon_path": "signal3.png"}, - {"range": [40, 49], "icon_path": "signal4.png"}, - {"range": [30, 39], "icon_path": "signal5.png"}, - {"range": [20, 29], "icon_path": "signal6.png"}, - {"range": [10, 19], "icon_path": "signal7.png"}, - {"range": [1, 9], "icon_path": "signal8.png"} + { + "range": [ + 70, + 100 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + 60, + 69 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + 50, + 59 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + 40, + 49 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + 30, + 39 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + 20, + 29 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + 10, + 19 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + 1, + 9 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 2 antenna 1", - "x": -160, + "x": -320, "y": 0, "facts": [ - {"name": "os_mon.wifi.rssi", "tags": {"adapter": "1", "type": "rssi_a"}} + { + "name": "os_mon.wifi.rssi", + "tags": { + "adapter": "1", + "type": "rssi_a" + } + } ], "ranges_and_icons": [ - {"range": [70, 100], "icon_path": "signal1.png"}, - {"range": [60, 69], "icon_path": "signal2.png"}, - {"range": [50, 59], "icon_path": "signal3.png"}, - {"range": [40, 49], "icon_path": "signal4.png"}, - {"range": [30, 39], "icon_path": "signal5.png"}, - {"range": [20, 29], "icon_path": "signal6.png"}, - {"range": [10, 19], "icon_path": "signal7.png"}, - {"range": [1, 9], "icon_path": "signal8.png"} + { + "range": [ + 70, + 100 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + 60, + 69 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + 50, + 59 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + 40, + 49 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + 30, + 39 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + 20, + 29 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + 10, + 19 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + 1, + 9 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 2 antenna 2", - "x": -115, + "x": -275, "y": 0, "facts": [ - {"name": "os_mon.wifi.rssi", "tags": {"adapter": "1", "type": "rssi_b"}} + { + "name": "os_mon.wifi.rssi", + "tags": { + "adapter": "1", + "type": "rssi_b" + } + } ], "ranges_and_icons": [ - {"range": [70, 100], "icon_path": "signal1.png"}, - {"range": [60, 69], "icon_path": "signal2.png"}, - {"range": [50, 59], "icon_path": "signal3.png"}, - {"range": [40, 49], "icon_path": "signal4.png"}, - {"range": [30, 39], "icon_path": "signal5.png"}, - {"range": [20, 29], "icon_path": "signal6.png"}, - {"range": [10, 19], "icon_path": "signal7.png"}, - {"range": [1, 9], "icon_path": "signal8.png"} + { + "range": [ + 70, + 100 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + 60, + 69 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + 50, + 59 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + 40, + 49 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + 30, + 39 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + 20, + 29 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + 10, + 19 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + 1, + 9 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 3 antenna 1", - "x": -70, + "x": -230, "y": 0, "facts": [ - {"name": "os_mon.wifi.rssi", "tags": {"adapter": "2", "type": "rssi_a"}} + { + "name": "os_mon.wifi.rssi", + "tags": { + "adapter": "2", + "type": "rssi_a" + } + } ], "ranges_and_icons": [ - {"range": [70, 100], "icon_path": "signal1.png"}, - {"range": [60, 69], "icon_path": "signal2.png"}, - {"range": [50, 59], "icon_path": "signal3.png"}, - {"range": [40, 49], "icon_path": "signal4.png"}, - {"range": [30, 39], "icon_path": "signal5.png"}, - {"range": [20, 29], "icon_path": "signal6.png"}, - {"range": [10, 19], "icon_path": "signal7.png"}, - {"range": [1, 9], "icon_path": "signal8.png"} + { + "range": [ + 70, + 100 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + 60, + 69 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + 50, + 59 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + 40, + 49 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + 30, + 39 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + 20, + 29 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + 10, + 19 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + 1, + 9 + ], + "icon_path": "signal8.png" + } ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 3 antenna 2", - "x": -25, + "x": -185, "y": 0, "facts": [ - {"name": "os_mon.wifi.rssi", "tags": {"adapter": "2", "type": "rssi_b"}} + { + "name": "os_mon.wifi.rssi", + "tags": { + "adapter": "2", + "type": "rssi_b" + } + } ], "ranges_and_icons": [ - {"range": [70, 100], "icon_path": "signal1.png"}, - {"range": [60, 69], "icon_path": "signal2.png"}, - {"range": [50, 59], "icon_path": "signal3.png"}, - {"range": [40, 49], "icon_path": "signal4.png"}, - {"range": [30, 39], "icon_path": "signal5.png"}, - {"range": [20, 29], "icon_path": "signal6.png"}, - {"range": [10, 19], "icon_path": "signal7.png"}, - {"range": [1, 9], "icon_path": "signal8.png"} + { + "range": [ + 70, + 100 + ], + "icon_path": "signal1.png" + }, + { + "range": [ + 60, + 69 + ], + "icon_path": "signal2.png" + }, + { + "range": [ + 50, + 59 + ], + "icon_path": "signal3.png" + }, + { + "range": [ + 40, + 49 + ], + "icon_path": "signal4.png" + }, + { + "range": [ + 30, + 39 + ], + "icon_path": "signal5.png" + }, + { + "range": [ + 20, + 29 + ], + "icon_path": "signal6.png" + }, + { + "range": [ + 10, + 19 + ], + "icon_path": "signal7.png" + }, + { + "range": [ + 1, + 9 + ], + "icon_path": "signal8.png" + } ] }, { "name": "Video FPS and resolution", "type": "VideoWidget", - "x": -250, + "x": -410, "y": 65, "icon_path": "framerate.png", "template": "%u fps | %ux%u", @@ -269,11 +915,11 @@ "name": "video.height" } ] - }, + }, { "name": "Video link throughput", "type": "VideoBitrateWidget", - "x": -250, + "x": -410, "y": 95, "icon_path": "network.png", "template": "%f Mbps", @@ -285,26 +931,30 @@ "name": "gstreamer.received_bytes" } ] - }, + }, { "name": "DVR status", "type": "DvrStatusWidget", - "x": -250, + "x": -410, "y": 125, "icon_path": "sdcard-white.png", "text": "Recording", "facts": [ - {"name": "dvr.recording"} + { + "name": "dvr.recording" + } ] }, { "name": "Custom fading message", "type": "PopupWidget", - "x": 400, + "x": 560, "y": 50, "timeout_ms": 10000, "facts": [ - {"name": "osd.custom_message"} + { + "name": "osd.custom_message" + } ] }, { @@ -327,30 +977,58 @@ "average_ms": 1000, "threshold": 8, "critical": 2, - "facts": [ { "name": "gstreamer.received_bytes" } ] + "facts": [ + { + "name": "gstreamer.received_bytes" + } + ] }, { "name": "APFPV WiFi RF temperature adapter 1", "type": "---IconTplTextWidget", - "x": -250, + "x": -410, "y": 155, "icon_path": "device_thermostat.png", - "template": "RF %i°C / %i°C", + "template": "RF %i\u00b0C / %i\u00b0C", "facts": [ - {"name": "os_mon.wifi.temperature", "tags": {"adapter": "0", "rf_path": "0"}}, - {"name": "os_mon.wifi.temperature", "tags": {"adapter": "0", "rf_path": "1"}} + { + "name": "os_mon.wifi.temperature", + "tags": { + "adapter": "0", + "rf_path": "0" + } + }, + { + "name": "os_mon.wifi.temperature", + "tags": { + "adapter": "0", + "rf_path": "1" + } + } ] }, { "name": "APFPV WiFi RF temperature adapter 2", "type": "---IconTplTextWidget", - "x": -250, + "x": -410, "y": 185, "icon_path": "device_thermostat.png", - "template": "RF %i°C / %i°C", + "template": "RF %i\u00b0C / %i\u00b0C", "facts": [ - {"name": "os_mon.wifi.temperature", "tags": {"adapter": "1", "rf_path": "0"}}, - {"name": "os_mon.wifi.temperature", "tags": {"adapter": "1", "rf_path": "1"}} + { + "name": "os_mon.wifi.temperature", + "tags": { + "adapter": "1", + "rf_path": "0" + } + }, + { + "name": "os_mon.wifi.temperature", + "tags": { + "adapter": "1", + "rf_path": "1" + } + } ] }, { @@ -368,13 +1046,153 @@ "x": 0, "y": 0, "facts": [ - { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "0", "id": "video rx" }}, - { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "1", "id": "video rx" }}, - { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "256", "id": "video rx" }}, - { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "257", "id": "video rx" }}, - { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "512", "id": "video rx" }}, - { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "513", "id": "video rx" }} + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "0", + "id": "video rx" + } + }, + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "1", + "id": "video rx" + } + }, + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "256", + "id": "video rx" + } + }, + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "257", + "id": "video rx" + } + }, + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "512", + "id": "video rx" + } + }, + { + "name": "wfbcli.rx.ant_stats.rssi_avg", + "tags": { + "ant_id": "513", + "id": "video rx" + } + } + ] + }, + { + "type": "IconSelectorWidget", + "name": "PX4 GPS fix icon", + "x": 20, + "y": 150, + "facts": [ + {"name": "mavlink.gps_raw.fix_type"} + ], + "ranges_and_icons": [ + {"range": [0, 0], "icon_path": "gps_none.png"}, + {"range": [1, 1], "icon_path": "gps_nofix.png"}, + {"range": [2, 2], "icon_path": "gps_2d.png"}, + {"range": [3, 10], "icon_path": "gps_3d.png"} + ] + }, + { + "type": "GPSWidget", + "name": "PX4 GPS text", + "x": 50, + "y": 150, + "facts": [ + {"name": "mavlink.gps_raw.fix_type"}, + {"name": "mavlink.gps_raw.lat"}, + {"name": "mavlink.gps_raw.lon"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "PX4 battery icon", + "x": 20, + "y": 180, + "facts": [ + {"name": "mavlink.sys_status.voltage_battery"} + ], + "ranges_and_icons": [ + {"range": [0, 1000000], "icon_path": "battery_android_frame_full.png"} + ] + }, + { + "type": "BatteryCellWidget", + "name": "PX4 battery text", + "x": 50, + "y": 180, + "template": "%.2fV", + "critical_voltage": 3.5, + "max_voltage": 4.2, + "num_cells": 0, + "facts": [ + {"name": "mavlink.sys_status.voltage_battery"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "PX4 armed icon", + "x": 20, + "y": 210, + "facts": [ + {"name": "mavlink.heartbeet.base_mode.armed"} + ], + "ranges_and_icons": [ + {"range": [0, 0], "icon_path": "disarmed.png"}, + {"range": [1, 1], "icon_path": "armed.png"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "PX4 compass icon", + "x": 20, + "y": 240, + "facts": [ + {"name": "mavlink.vfr_hud.heading"} + ], + "ranges_and_icons": [ + {"range": [0, 22], "icon_path": "compass_n.png"}, + {"range": [23, 67], "icon_path": "compass_ne.png"}, + {"range": [68, 112], "icon_path": "compass_e.png"}, + {"range": [113, 157], "icon_path": "compass_se.png"}, + {"range": [158, 202], "icon_path": "compass_s.png"}, + {"range": [203, 247], "icon_path": "compass_sw.png"}, + {"range": [248, 292], "icon_path": "compass_w.png"}, + {"range": [293, 337], "icon_path": "compass_nw.png"}, + {"range": [338, 360], "icon_path": "compass_n.png"} + ] + }, + { + "type": "TplTextWidget", + "name": "PX4 heading text", + "x": 50, + "y": 240, + "template": "HDG %i°", + "facts": [ + {"name": "mavlink.vfr_hud.heading"} + ] + }, + { + "type": "TplTextWidget", + "name": "PX4 telemetry RX RSSI", + "x": 20, + "y": 270, + "template": "RX RSSI %i dBm", + "facts": [ + {"name": "mavlink.radio_status.rssi"} ] } ] -} +} \ No newline at end of file diff --git a/src/icons/armed.png b/src/icons/armed.png new file mode 100644 index 0000000000000000000000000000000000000000..923340696c1d88c822ba066357399b3d6ab12aa2 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjt)4E9Ar*7ZPJPIAK!KyB^v&P+ zn}4}xc?SpIaTJLDT6j%VNHFA;R;(;T-t!+f_0pKaN@ebc1XOnFFvLx}81~<}LHRAi zt!1-fwpe{oE3mxx%E7AZw$2~5lMlJQiv(gn7_YZwkFhzrl0oEGX2S#<1|bmT7iTj0 m*0%$tB3mn?Iu}itE+>CS$l~syjY&WUF?hQAxvX2_(DSZ7 z_4mp7cN2>@Ok-kia`+_bx@c8u`m1a2I8{DaFGvo^bSXBO&hg;pTajv}iS^7$ydvR8 zeswZ3bxqvk$Iz|vVpsF}Rt~PcizHugF$eVOy4Q+w#&DH1xC+=gTv(6|w2{Hn)z4*} HQ$iB}CDAvK literal 0 HcmV?d00001 diff --git a/src/icons/compass_n.png b/src/icons/compass_n.png new file mode 100644 index 0000000000000000000000000000000000000000..cafbdaded245b89f5e28322468434fbbcc4aa0f2 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjX`U{QAr*7ZPITmAP~c$M{Qv(= zKSz$0+FZM*^CeyOTeK?MX!i!@y(^d+nwYN4dCOoC!mwI(Y3Hhg8V{B$UvasdAR>@i z@LYsfWk%)a66Wud_#7m?KC+vu`iAh{TXK5=Lxk{Br4>gRFQxG4ieKrf2inNs>FVdQ I&MBb@0D@XJ{r~^~ literal 0 HcmV?d00001 diff --git a/src/icons/compass_ne.png b/src/icons/compass_ne.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7c7f3c59fb174dc96f5028302b27cc19920870 GIT binary patch literal 416 zcmV;R0bl-!P))E3p zsm*`^Fae%{64+Vc=E8wv;1GCXX%}_zz08`QO1hVXBp)#M{gT%z6?qy|4rR_WNiYGGzFvr)anth!s>Aa?F7@CP+;Z^Zf;|WSYopl*Y;c0000< KMNUMnLSTZ?3%qs! literal 0 HcmV?d00001 diff --git a/src/icons/compass_nw.png b/src/icons/compass_nw.png new file mode 100644 index 0000000000000000000000000000000000000000..58863c1d3c9736fb426400aaf171721ff108c9f4 GIT binary patch literal 405 zcmV;G0c!q01hqZvA7oV?bLO NJYD@<);T3K0RUg(Jh%V= literal 0 HcmV?d00001 diff --git a/src/icons/compass_se.png b/src/icons/compass_se.png new file mode 100644 index 0000000000000000000000000000000000000000..fa12624acd7543384bb16ac8f929bc0df17105d8 GIT binary patch literal 382 zcmV-^0fGLBP) zC$amJqRXGl@3Wor#Q{4pIp?C!v{D{}QuDUlAfS(V+dB<_PXvbBfLOo;xQ@ZIAi(A$ z0b~C33ao*H97xUARsgUItOT?KrohbPjJiVXje*1${`DhquJZOWC$=%TRDepIzXKm~ zep(7Sqp2@B0IrGv0=Y5Eq;=cHBUp-nueE)NpbPPtzxZWE9Ox^ cUq~wV7hRkCuzgCavH$=807*qoM6N<$g21z=1ONa4 literal 0 HcmV?d00001 diff --git a/src/icons/compass_sw.png b/src/icons/compass_sw.png new file mode 100644 index 0000000000000000000000000000000000000000..8c62dbdefff0012c9628d950d0c9fe4020109b3c GIT binary patch literal 415 zcmV;Q0bu@#P)fyBnJUz9b~AnAv&|7j-fP95AhCU;tDzCjWq40LQL>4V*{tJ7BFO z%L>>GKu-~P1dM^L6eD>KB+1MgNuQENZfL`MyS6XMB-Pp3yFb_kzG>H93D{hF)dKzj z-n&?-lkBHp(DD6pUh08>+m5O6S_P7c^jkc(B{h<&V9Y;XN_v&_VrE|@+3!SK(d!L( z%mzmA!)OY|#k)mPABgKvxbXLqP9@c5c2kh-z{e2su{`}8BrU+NV!vP|FVYSbdPkTW1-?PS-2qRT(eDEJ&&CjCoi z6;qT*en#vW|0~`HkQE zH~kjfa%r)3Pnp1xwwjkKpS`>8XPXrBUu!js%gb%~iZ9d{w@4g_m{zvvzmUTvEryv~ zn*(-hOv?FmYQd7Vr>YpL_c1wSTQkh;S!Q-QtZ@GGIT;B9xUaqi>QclqI7q8RG)YJBd{S{$I{c$h&f}YY+1*^jhGrv82Q*n}` z;ok|~^nHKS9v?V3^VRl_Fs=eU#)xxkrj_%Fcw~pJm>{-*HJmY3T$WvE q%Py^yP}!chi?(!kHH)OwgxXy47qEC-Qk4XB5`(9!pUXO@geCxkVM+`D literal 0 HcmV?d00001 diff --git a/src/icons/gps_3d.png b/src/icons/gps_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..28e958e92529cdb1ef25b7e0e9e3b8e3836d5a86 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj)t)YnAr*73PI2TrpuoYh@#X*h zJ3YTs)Ck*OV-0zHlmKHu-&$eA2^ z;+8u@w)?A^#q}BNVNRz`{*u|iaN4ev!^R=x@e23(Mw8#DwmGSul5saTd&S6{87r!@ zgf-)!b+g;b#|br)y!BS`WkfvP<+t)t!V_DG%gheb1g)*zyjB2R$KdJe=d#Wzp$Pz~ CzD*zi literal 0 HcmV?d00001 diff --git a/src/icons/gps_none.png b/src/icons/gps_none.png new file mode 100644 index 0000000000000000000000000000000000000000..69e44ec8e005cebd868204c3174afa950efbc9e4 GIT binary patch literal 229 zcmV^P)OgheV~fEl zzRHIH06-swDv+(QfZmEh*a(r|kK92hpo6$wEVt=DI!WiCi%^5sK_cK)j6mFLrK4h} zlNI0=3Xl-ZI;bF90E^I*TnYwZf$()?NT&l8|5%xj3J`U%M0PuHx2cCzfpn4$qB{VK fjKqn*is=EqhudK^n>Dy+00000NkvXXu0mjf@T*?y literal 0 HcmV?d00001 From 4c696743758278116e11967e0f05fc2f25c2aa37 Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Mon, 14 Sep 2026 09:03:22 +0300 Subject: [PATCH 07/12] Remove armed/disarmed, compass, and GPS-fix icon widgets from OSD Reverts the icon-based widgets and their PNG assets, no longer referenced anywhere in config_osd.json. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01LWYaFJL5iNrmr73dY6Tvuz --- config_osd.json | 1122 ++++++-------------------------------- src/icons/armed.png | Bin 190 -> 0 bytes src/icons/compass_e.png | Bin 159 -> 0 bytes src/icons/compass_n.png | Bin 159 -> 0 bytes src/icons/compass_ne.png | Bin 416 -> 0 bytes src/icons/compass_nw.png | Bin 405 -> 0 bytes src/icons/compass_s.png | Bin 163 -> 0 bytes src/icons/compass_se.png | Bin 382 -> 0 bytes src/icons/compass_sw.png | Bin 415 -> 0 bytes src/icons/compass_w.png | Bin 162 -> 0 bytes src/icons/disarmed.png | Bin 193 -> 0 bytes src/icons/gps_2d.png | Bin 192 -> 0 bytes src/icons/gps_3d.png | Bin 180 -> 0 bytes src/icons/gps_nofix.png | Bin 203 -> 0 bytes src/icons/gps_none.png | Bin 229 -> 0 bytes 15 files changed, 152 insertions(+), 970 deletions(-) delete mode 100644 src/icons/armed.png delete mode 100644 src/icons/compass_e.png delete mode 100644 src/icons/compass_n.png delete mode 100644 src/icons/compass_ne.png delete mode 100644 src/icons/compass_nw.png delete mode 100644 src/icons/compass_s.png delete mode 100644 src/icons/compass_se.png delete mode 100644 src/icons/compass_sw.png delete mode 100644 src/icons/compass_w.png delete mode 100644 src/icons/disarmed.png delete mode 100644 src/icons/gps_2d.png delete mode 100644 src/icons/gps_3d.png delete mode 100644 src/icons/gps_nofix.png delete mode 100644 src/icons/gps_none.png diff --git a/config_osd.json b/config_osd.json index ec6fa826..8e556ee1 100644 --- a/config_osd.json +++ b/config_osd.json @@ -2,902 +2,256 @@ "format": "0.0.1", "assets_dir": "/usr/share/pixelpilot/", "widgets": [ - { + { "name": "Metrics background", "type": "BoxWidget", - "x": -430, + "x": -270, "y": 0, "width": 270, "height": 130, "color": { - "r": 0.0, - "g": 0.0, - "b": 0.0, - "alpha": 0.4 + "r": 0.0, + "g": 0.0, + "b": 0.0, + "alpha": 0.4 }, "facts": [] - }, - { + }, + { "type": "IconSelectorWidget", "name": "RSSI of antenna 1", - "x": -410, + "x": -250, "y": 0, "facts": [ - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "0", - "id": "video rx" - } - } + {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "0", "id": "video rx"}} ], "ranges_and_icons": [ - { - "range": [ - -40, - 1 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - -48, - -41 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - -56, - -49 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - -64, - -57 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - -72, - -65 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - -80, - -73 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - -90, - -81 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - -130, - -91 - ], - "icon_path": "signal8.png" - } + {"range": [-40, 1], "icon_path": "signal1.png"}, + {"range": [-48, -41], "icon_path": "signal2.png"}, + {"range": [-56, -49], "icon_path": "signal3.png"}, + {"range": [-64, -57], "icon_path": "signal4.png"}, + {"range": [-72, -65], "icon_path": "signal5.png"}, + {"range": [-80, -73], "icon_path": "signal6.png"}, + {"range": [-90, -81], "icon_path": "signal7.png"}, + {"range": [-130, -91], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 2", - "x": -365, + "x": -205, "y": 0, "facts": [ - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "1", - "id": "video rx" - } - } + {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "1", "id": "video rx"}} ], "ranges_and_icons": [ - { - "range": [ - -40, - 1 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - -48, - -41 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - -56, - -49 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - -64, - -57 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - -72, - -65 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - -80, - -73 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - -90, - -81 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - -130, - -91 - ], - "icon_path": "signal8.png" - } + {"range": [-40, 1], "icon_path": "signal1.png"}, + {"range": [-48, -41], "icon_path": "signal2.png"}, + {"range": [-56, -49], "icon_path": "signal3.png"}, + {"range": [-64, -57], "icon_path": "signal4.png"}, + {"range": [-72, -65], "icon_path": "signal5.png"}, + {"range": [-80, -73], "icon_path": "signal6.png"}, + {"range": [-90, -81], "icon_path": "signal7.png"}, + {"range": [-130, -91], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 3", - "x": -320, + "x": -160, "y": 0, "facts": [ - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "256", - "id": "video rx" - } - } + {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "256", "id": "video rx"}} ], "ranges_and_icons": [ - { - "range": [ - -40, - 1 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - -48, - -41 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - -56, - -49 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - -64, - -57 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - -72, - -65 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - -80, - -73 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - -90, - -81 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - -130, - -91 - ], - "icon_path": "signal8.png" - } + {"range": [-40, 1], "icon_path": "signal1.png"}, + {"range": [-48, -41], "icon_path": "signal2.png"}, + {"range": [-56, -49], "icon_path": "signal3.png"}, + {"range": [-64, -57], "icon_path": "signal4.png"}, + {"range": [-72, -65], "icon_path": "signal5.png"}, + {"range": [-80, -73], "icon_path": "signal6.png"}, + {"range": [-90, -81], "icon_path": "signal7.png"}, + {"range": [-130, -91], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 4", - "x": -275, + "x": -115, "y": 0, "facts": [ - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "257", - "id": "video rx" - } - } + {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "257", "id": "video rx"}} ], "ranges_and_icons": [ - { - "range": [ - -40, - 1 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - -48, - -41 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - -56, - -49 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - -64, - -57 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - -72, - -65 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - -80, - -73 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - -90, - -81 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - -130, - -91 - ], - "icon_path": "signal8.png" - } + {"range": [-40, 1], "icon_path": "signal1.png"}, + {"range": [-48, -41], "icon_path": "signal2.png"}, + {"range": [-56, -49], "icon_path": "signal3.png"}, + {"range": [-64, -57], "icon_path": "signal4.png"}, + {"range": [-72, -65], "icon_path": "signal5.png"}, + {"range": [-80, -73], "icon_path": "signal6.png"}, + {"range": [-90, -81], "icon_path": "signal7.png"}, + {"range": [-130, -91], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 5", - "x": -230, + "x": -70, "y": 0, "facts": [ - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "512", - "id": "video rx" - } - } + {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "512", "id": "video rx"}} ], "ranges_and_icons": [ - { - "range": [ - -40, - 1 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - -48, - -41 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - -56, - -49 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - -64, - -57 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - -72, - -65 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - -80, - -73 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - -90, - -81 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - -130, - -91 - ], - "icon_path": "signal8.png" - } + {"range": [-40, 1], "icon_path": "signal1.png"}, + {"range": [-48, -41], "icon_path": "signal2.png"}, + {"range": [-56, -49], "icon_path": "signal3.png"}, + {"range": [-64, -57], "icon_path": "signal4.png"}, + {"range": [-72, -65], "icon_path": "signal5.png"}, + {"range": [-80, -73], "icon_path": "signal6.png"}, + {"range": [-90, -81], "icon_path": "signal7.png"}, + {"range": [-130, -91], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "RSSI of antenna 6", - "x": -185, + "x": -25, "y": 0, "facts": [ - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "513", - "id": "video rx" - } - } + {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "513", "id": "video rx"}} ], "ranges_and_icons": [ - { - "range": [ - -40, - 1 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - -48, - -41 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - -56, - -49 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - -64, - -57 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - -72, - -65 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - -80, - -73 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - -90, - -81 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - -130, - -91 - ], - "icon_path": "signal8.png" - } + {"range": [-40, 1], "icon_path": "signal1.png"}, + {"range": [-48, -41], "icon_path": "signal2.png"}, + {"range": [-56, -49], "icon_path": "signal3.png"}, + {"range": [-64, -57], "icon_path": "signal4.png"}, + {"range": [-72, -65], "icon_path": "signal5.png"}, + {"range": [-80, -73], "icon_path": "signal6.png"}, + {"range": [-90, -81], "icon_path": "signal7.png"}, + {"range": [-130, -91], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 1 antenna 1", - "x": -410, + "x": -250, "y": 0, "facts": [ { "__comment": "APFPV counterpart of the six widgets above: same slots, but fed by the WiFi driver instead of wfb-ng. Only one of the two sets is ever published, the other stays hidden. RSSI is a percentage here, not dBm. `adapter` counts the cards from 0 in interface-name order, `type` picks the RF path", - "name": "os_mon.wifi.rssi", - "tags": { - "adapter": "0", - "type": "rssi_a" - } + "name": "os_mon.wifi.rssi", "tags": {"adapter": "0", "type": "rssi_a"} } ], "ranges_and_icons": [ - { - "range": [ - 70, - 100 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - 60, - 69 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - 50, - 59 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - 40, - 49 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - 30, - 39 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - 20, - 29 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - 10, - 19 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - 1, - 9 - ], - "icon_path": "signal8.png" - } + {"range": [70, 100], "icon_path": "signal1.png"}, + {"range": [60, 69], "icon_path": "signal2.png"}, + {"range": [50, 59], "icon_path": "signal3.png"}, + {"range": [40, 49], "icon_path": "signal4.png"}, + {"range": [30, 39], "icon_path": "signal5.png"}, + {"range": [20, 29], "icon_path": "signal6.png"}, + {"range": [10, 19], "icon_path": "signal7.png"}, + {"range": [1, 9], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 1 antenna 2", - "x": -365, + "x": -205, "y": 0, "facts": [ - { - "name": "os_mon.wifi.rssi", - "tags": { - "adapter": "0", - "type": "rssi_b" - } - } + {"name": "os_mon.wifi.rssi", "tags": {"adapter": "0", "type": "rssi_b"}} ], "ranges_and_icons": [ - { - "range": [ - 70, - 100 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - 60, - 69 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - 50, - 59 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - 40, - 49 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - 30, - 39 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - 20, - 29 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - 10, - 19 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - 1, - 9 - ], - "icon_path": "signal8.png" - } + {"range": [70, 100], "icon_path": "signal1.png"}, + {"range": [60, 69], "icon_path": "signal2.png"}, + {"range": [50, 59], "icon_path": "signal3.png"}, + {"range": [40, 49], "icon_path": "signal4.png"}, + {"range": [30, 39], "icon_path": "signal5.png"}, + {"range": [20, 29], "icon_path": "signal6.png"}, + {"range": [10, 19], "icon_path": "signal7.png"}, + {"range": [1, 9], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 2 antenna 1", - "x": -320, + "x": -160, "y": 0, "facts": [ - { - "name": "os_mon.wifi.rssi", - "tags": { - "adapter": "1", - "type": "rssi_a" - } - } + {"name": "os_mon.wifi.rssi", "tags": {"adapter": "1", "type": "rssi_a"}} ], "ranges_and_icons": [ - { - "range": [ - 70, - 100 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - 60, - 69 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - 50, - 59 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - 40, - 49 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - 30, - 39 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - 20, - 29 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - 10, - 19 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - 1, - 9 - ], - "icon_path": "signal8.png" - } + {"range": [70, 100], "icon_path": "signal1.png"}, + {"range": [60, 69], "icon_path": "signal2.png"}, + {"range": [50, 59], "icon_path": "signal3.png"}, + {"range": [40, 49], "icon_path": "signal4.png"}, + {"range": [30, 39], "icon_path": "signal5.png"}, + {"range": [20, 29], "icon_path": "signal6.png"}, + {"range": [10, 19], "icon_path": "signal7.png"}, + {"range": [1, 9], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 2 antenna 2", - "x": -275, + "x": -115, "y": 0, "facts": [ - { - "name": "os_mon.wifi.rssi", - "tags": { - "adapter": "1", - "type": "rssi_b" - } - } + {"name": "os_mon.wifi.rssi", "tags": {"adapter": "1", "type": "rssi_b"}} ], "ranges_and_icons": [ - { - "range": [ - 70, - 100 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - 60, - 69 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - 50, - 59 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - 40, - 49 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - 30, - 39 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - 20, - 29 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - 10, - 19 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - 1, - 9 - ], - "icon_path": "signal8.png" - } + {"range": [70, 100], "icon_path": "signal1.png"}, + {"range": [60, 69], "icon_path": "signal2.png"}, + {"range": [50, 59], "icon_path": "signal3.png"}, + {"range": [40, 49], "icon_path": "signal4.png"}, + {"range": [30, 39], "icon_path": "signal5.png"}, + {"range": [20, 29], "icon_path": "signal6.png"}, + {"range": [10, 19], "icon_path": "signal7.png"}, + {"range": [1, 9], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 3 antenna 1", - "x": -230, + "x": -70, "y": 0, "facts": [ - { - "name": "os_mon.wifi.rssi", - "tags": { - "adapter": "2", - "type": "rssi_a" - } - } + {"name": "os_mon.wifi.rssi", "tags": {"adapter": "2", "type": "rssi_a"}} ], "ranges_and_icons": [ - { - "range": [ - 70, - 100 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - 60, - 69 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - 50, - 59 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - 40, - 49 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - 30, - 39 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - 20, - 29 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - 10, - 19 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - 1, - 9 - ], - "icon_path": "signal8.png" - } + {"range": [70, 100], "icon_path": "signal1.png"}, + {"range": [60, 69], "icon_path": "signal2.png"}, + {"range": [50, 59], "icon_path": "signal3.png"}, + {"range": [40, 49], "icon_path": "signal4.png"}, + {"range": [30, 39], "icon_path": "signal5.png"}, + {"range": [20, 29], "icon_path": "signal6.png"}, + {"range": [10, 19], "icon_path": "signal7.png"}, + {"range": [1, 9], "icon_path": "signal8.png"} ] }, { "type": "IconSelectorWidget", "name": "APFPV RSSI of adapter 3 antenna 2", - "x": -185, + "x": -25, "y": 0, "facts": [ - { - "name": "os_mon.wifi.rssi", - "tags": { - "adapter": "2", - "type": "rssi_b" - } - } + {"name": "os_mon.wifi.rssi", "tags": {"adapter": "2", "type": "rssi_b"}} ], "ranges_and_icons": [ - { - "range": [ - 70, - 100 - ], - "icon_path": "signal1.png" - }, - { - "range": [ - 60, - 69 - ], - "icon_path": "signal2.png" - }, - { - "range": [ - 50, - 59 - ], - "icon_path": "signal3.png" - }, - { - "range": [ - 40, - 49 - ], - "icon_path": "signal4.png" - }, - { - "range": [ - 30, - 39 - ], - "icon_path": "signal5.png" - }, - { - "range": [ - 20, - 29 - ], - "icon_path": "signal6.png" - }, - { - "range": [ - 10, - 19 - ], - "icon_path": "signal7.png" - }, - { - "range": [ - 1, - 9 - ], - "icon_path": "signal8.png" - } + {"range": [70, 100], "icon_path": "signal1.png"}, + {"range": [60, 69], "icon_path": "signal2.png"}, + {"range": [50, 59], "icon_path": "signal3.png"}, + {"range": [40, 49], "icon_path": "signal4.png"}, + {"range": [30, 39], "icon_path": "signal5.png"}, + {"range": [20, 29], "icon_path": "signal6.png"}, + {"range": [10, 19], "icon_path": "signal7.png"}, + {"range": [1, 9], "icon_path": "signal8.png"} ] }, { "name": "Video FPS and resolution", "type": "VideoWidget", - "x": -410, + "x": -250, "y": 65, "icon_path": "framerate.png", "template": "%u fps | %ux%u", @@ -915,11 +269,11 @@ "name": "video.height" } ] - }, + }, { "name": "Video link throughput", "type": "VideoBitrateWidget", - "x": -410, + "x": -250, "y": 95, "icon_path": "network.png", "template": "%f Mbps", @@ -931,30 +285,26 @@ "name": "gstreamer.received_bytes" } ] - }, + }, { "name": "DVR status", "type": "DvrStatusWidget", - "x": -410, + "x": -250, "y": 125, "icon_path": "sdcard-white.png", "text": "Recording", "facts": [ - { - "name": "dvr.recording" - } + {"name": "dvr.recording"} ] }, { "name": "Custom fading message", "type": "PopupWidget", - "x": 560, + "x": 400, "y": 50, "timeout_ms": 10000, "facts": [ - { - "name": "osd.custom_message" - } + {"name": "osd.custom_message"} ] }, { @@ -977,58 +327,30 @@ "average_ms": 1000, "threshold": 8, "critical": 2, - "facts": [ - { - "name": "gstreamer.received_bytes" - } - ] + "facts": [ { "name": "gstreamer.received_bytes" } ] }, { "name": "APFPV WiFi RF temperature adapter 1", "type": "---IconTplTextWidget", - "x": -410, + "x": -250, "y": 155, "icon_path": "device_thermostat.png", - "template": "RF %i\u00b0C / %i\u00b0C", + "template": "RF %i°C / %i°C", "facts": [ - { - "name": "os_mon.wifi.temperature", - "tags": { - "adapter": "0", - "rf_path": "0" - } - }, - { - "name": "os_mon.wifi.temperature", - "tags": { - "adapter": "0", - "rf_path": "1" - } - } + {"name": "os_mon.wifi.temperature", "tags": {"adapter": "0", "rf_path": "0"}}, + {"name": "os_mon.wifi.temperature", "tags": {"adapter": "0", "rf_path": "1"}} ] }, { "name": "APFPV WiFi RF temperature adapter 2", "type": "---IconTplTextWidget", - "x": -410, + "x": -250, "y": 185, "icon_path": "device_thermostat.png", - "template": "RF %i\u00b0C / %i\u00b0C", + "template": "RF %i°C / %i°C", "facts": [ - { - "name": "os_mon.wifi.temperature", - "tags": { - "adapter": "1", - "rf_path": "0" - } - }, - { - "name": "os_mon.wifi.temperature", - "tags": { - "adapter": "1", - "rf_path": "1" - } - } + {"name": "os_mon.wifi.temperature", "tags": {"adapter": "1", "rf_path": "0"}}, + {"name": "os_mon.wifi.temperature", "tags": {"adapter": "1", "rf_path": "1"}} ] }, { @@ -1046,153 +368,13 @@ "x": 0, "y": 0, "facts": [ - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "0", - "id": "video rx" - } - }, - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "1", - "id": "video rx" - } - }, - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "256", - "id": "video rx" - } - }, - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "257", - "id": "video rx" - } - }, - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "512", - "id": "video rx" - } - }, - { - "name": "wfbcli.rx.ant_stats.rssi_avg", - "tags": { - "ant_id": "513", - "id": "video rx" - } - } - ] - }, - { - "type": "IconSelectorWidget", - "name": "PX4 GPS fix icon", - "x": 20, - "y": 150, - "facts": [ - {"name": "mavlink.gps_raw.fix_type"} - ], - "ranges_and_icons": [ - {"range": [0, 0], "icon_path": "gps_none.png"}, - {"range": [1, 1], "icon_path": "gps_nofix.png"}, - {"range": [2, 2], "icon_path": "gps_2d.png"}, - {"range": [3, 10], "icon_path": "gps_3d.png"} - ] - }, - { - "type": "GPSWidget", - "name": "PX4 GPS text", - "x": 50, - "y": 150, - "facts": [ - {"name": "mavlink.gps_raw.fix_type"}, - {"name": "mavlink.gps_raw.lat"}, - {"name": "mavlink.gps_raw.lon"} - ] - }, - { - "type": "IconSelectorWidget", - "name": "PX4 battery icon", - "x": 20, - "y": 180, - "facts": [ - {"name": "mavlink.sys_status.voltage_battery"} - ], - "ranges_and_icons": [ - {"range": [0, 1000000], "icon_path": "battery_android_frame_full.png"} - ] - }, - { - "type": "BatteryCellWidget", - "name": "PX4 battery text", - "x": 50, - "y": 180, - "template": "%.2fV", - "critical_voltage": 3.5, - "max_voltage": 4.2, - "num_cells": 0, - "facts": [ - {"name": "mavlink.sys_status.voltage_battery"} - ] - }, - { - "type": "IconSelectorWidget", - "name": "PX4 armed icon", - "x": 20, - "y": 210, - "facts": [ - {"name": "mavlink.heartbeet.base_mode.armed"} - ], - "ranges_and_icons": [ - {"range": [0, 0], "icon_path": "disarmed.png"}, - {"range": [1, 1], "icon_path": "armed.png"} - ] - }, - { - "type": "IconSelectorWidget", - "name": "PX4 compass icon", - "x": 20, - "y": 240, - "facts": [ - {"name": "mavlink.vfr_hud.heading"} - ], - "ranges_and_icons": [ - {"range": [0, 22], "icon_path": "compass_n.png"}, - {"range": [23, 67], "icon_path": "compass_ne.png"}, - {"range": [68, 112], "icon_path": "compass_e.png"}, - {"range": [113, 157], "icon_path": "compass_se.png"}, - {"range": [158, 202], "icon_path": "compass_s.png"}, - {"range": [203, 247], "icon_path": "compass_sw.png"}, - {"range": [248, 292], "icon_path": "compass_w.png"}, - {"range": [293, 337], "icon_path": "compass_nw.png"}, - {"range": [338, 360], "icon_path": "compass_n.png"} - ] - }, - { - "type": "TplTextWidget", - "name": "PX4 heading text", - "x": 50, - "y": 240, - "template": "HDG %i°", - "facts": [ - {"name": "mavlink.vfr_hud.heading"} - ] - }, - { - "type": "TplTextWidget", - "name": "PX4 telemetry RX RSSI", - "x": 20, - "y": 270, - "template": "RX RSSI %i dBm", - "facts": [ - {"name": "mavlink.radio_status.rssi"} + { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "0", "id": "video rx" }}, + { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "1", "id": "video rx" }}, + { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "256", "id": "video rx" }}, + { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "257", "id": "video rx" }}, + { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "512", "id": "video rx" }}, + { "name": "wfbcli.rx.ant_stats.rssi_avg", "tags": { "ant_id": "513", "id": "video rx" }} ] } ] -} \ No newline at end of file +} diff --git a/src/icons/armed.png b/src/icons/armed.png deleted file mode 100644 index 923340696c1d88c822ba066357399b3d6ab12aa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjt)4E9Ar*7ZPJPIAK!KyB^v&P+ zn}4}xc?SpIaTJLDT6j%VNHFA;R;(;T-t!+f_0pKaN@ebc1XOnFFvLx}81~<}LHRAi zt!1-fwpe{oE3mxx%E7AZw$2~5lMlJQiv(gn7_YZwkFhzrl0oEGX2S#<1|bmT7iTj0 m*0%$tB3mn?Iu}itE+>CS$l~syjY&WUF?hQAxvX2_(DSZ7 z_4mp7cN2>@Ok-kia`+_bx@c8u`m1a2I8{DaFGvo^bSXBO&hg;pTajv}iS^7$ydvR8 zeswZ3bxqvk$Iz|vVpsF}Rt~PcizHugF$eVOy4Q+w#&DH1xC+=gTv(6|w2{Hn)z4*} HQ$iB}CDAvK diff --git a/src/icons/compass_n.png b/src/icons/compass_n.png deleted file mode 100644 index cafbdaded245b89f5e28322468434fbbcc4aa0f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjX`U{QAr*7ZPITmAP~c$M{Qv(= zKSz$0+FZM*^CeyOTeK?MX!i!@y(^d+nwYN4dCOoC!mwI(Y3Hhg8V{B$UvasdAR>@i z@LYsfWk%)a66Wud_#7m?KC+vu`iAh{TXK5=Lxk{Br4>gRFQxG4ieKrf2inNs>FVdQ I&MBb@0D@XJ{r~^~ diff --git a/src/icons/compass_ne.png b/src/icons/compass_ne.png deleted file mode 100644 index 1c7c7f3c59fb174dc96f5028302b27cc19920870..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 416 zcmV;R0bl-!P))E3p zsm*`^Fae%{64+Vc=E8wv;1GCXX%}_zz08`QO1hVXBp)#M{gT%z6?qy|4rR_WNiYGGzFvr)anth!s>Aa?F7@CP+;Z^Zf;|WSYopl*Y;c0000< KMNUMnLSTZ?3%qs! diff --git a/src/icons/compass_nw.png b/src/icons/compass_nw.png deleted file mode 100644 index 58863c1d3c9736fb426400aaf171721ff108c9f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405 zcmV;G0c!q01hqZvA7oV?bLO NJYD@<);T3K0RUg(Jh%V= diff --git a/src/icons/compass_se.png b/src/icons/compass_se.png deleted file mode 100644 index fa12624acd7543384bb16ac8f929bc0df17105d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 382 zcmV-^0fGLBP) zC$amJqRXGl@3Wor#Q{4pIp?C!v{D{}QuDUlAfS(V+dB<_PXvbBfLOo;xQ@ZIAi(A$ z0b~C33ao*H97xUARsgUItOT?KrohbPjJiVXje*1${`DhquJZOWC$=%TRDepIzXKm~ zep(7Sqp2@B0IrGv0=Y5Eq;=cHBUp-nueE)NpbPPtzxZWE9Ox^ cUq~wV7hRkCuzgCavH$=807*qoM6N<$g21z=1ONa4 diff --git a/src/icons/compass_sw.png b/src/icons/compass_sw.png deleted file mode 100644 index 8c62dbdefff0012c9628d950d0c9fe4020109b3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 415 zcmV;Q0bu@#P)fyBnJUz9b~AnAv&|7j-fP95AhCU;tDzCjWq40LQL>4V*{tJ7BFO z%L>>GKu-~P1dM^L6eD>KB+1MgNuQENZfL`MyS6XMB-Pp3yFb_kzG>H93D{hF)dKzj z-n&?-lkBHp(DD6pUh08>+m5O6S_P7c^jkc(B{h<&V9Y;XN_v&_VrE|@+3!SK(d!L( z%mzmA!)OY|#k)mPABgKvxbXLqP9@c5c2kh-z{e2su{`}8BrU+NV!vP|FVYSbdPkTW1-?PS-2qRT(eDEJ&&CjCoi z6;qT*en#vW|0~`HkQE zH~kjfa%r)3Pnp1xwwjkKpS`>8XPXrBUu!js%gb%~iZ9d{w@4g_m{zvvzmUTvEryv~ zn*(-hOv?FmYQd7Vr>YpL_c1wSTQkh;S!Q-QtZ@GGIT;B9xUaqi>QclqI7q8RG)YJBd{S{$I{c$h&f}YY+1*^jhGrv82Q*n}` z;ok|~^nHKS9v?V3^VRl_Fs=eU#)xxkrj_%Fcw~pJm>{-*HJmY3T$WvE q%Py^yP}!chi?(!kHH)OwgxXy47qEC-Qk4XB5`(9!pUXO@geCxkVM+`D diff --git a/src/icons/gps_3d.png b/src/icons/gps_3d.png deleted file mode 100644 index 28e958e92529cdb1ef25b7e0e9e3b8e3836d5a86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj)t)YnAr*73PI2TrpuoYh@#X*h zJ3YTs)Ck*OV-0zHlmKHu-&$eA2^ z;+8u@w)?A^#q}BNVNRz`{*u|iaN4ev!^R=x@e23(Mw8#DwmGSul5saTd&S6{87r!@ zgf-)!b+g;b#|br)y!BS`WkfvP<+t)t!V_DG%gheb1g)*zyjB2R$KdJe=d#Wzp$Pz~ CzD*zi diff --git a/src/icons/gps_none.png b/src/icons/gps_none.png deleted file mode 100644 index 69e44ec8e005cebd868204c3174afa950efbc9e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 229 zcmV^P)OgheV~fEl zzRHIH06-swDv+(QfZmEh*a(r|kK92hpo6$wEVt=DI!WiCi%^5sK_cK)j6mFLrK4h} zlNI0=3Xl-ZI;bF90E^I*TnYwZf$()?NT&l8|5%xj3J`U%M0PuHx2cCzfpn4$qB{VK fjKqn*is=EqhudK^n>Dy+00000NkvXXu0mjf@T*?y From 29085aa02c666f6b623c7bd9bed27872a1f73f64 Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Mon, 14 Sep 2026 09:03:50 +0300 Subject: [PATCH 08/12] Add air unit SoC temperature sensor over the wfb-ng tunnel 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 Claude-Session: https://claude.ai/code/session_01LWYaFJL5iNrmr73dY6Tvuz --- src/main.cpp | 4 ++ src/os_mon.cpp | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ src/os_mon.hpp | 3 ++ 3 files changed, 107 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index ddbf75a9..3fc06317 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1778,6 +1778,10 @@ int main(int argc, char **argv) std::cerr << "Configuration error: " << e.what() << std::endl; } + // Independent of the os_sensors config above -- the VTX's SoC temp isn't + // local hardware, it arrives over its own small TCP feed on the tunnel. + os_sensors.addVtxTemp(15552); + spdlog::info("disable_vsync: {}", disable_vsync); if (enable_osd == 0 ) { diff --git a/src/os_mon.cpp b/src/os_mon.cpp index 2a192a66..cb308cb6 100644 --- a/src/os_mon.cpp +++ b/src/os_mon.cpp @@ -5,6 +5,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include "spdlog/spdlog.h" @@ -377,6 +383,96 @@ class TemperatureSensor : public ISensor { }; +/** + * @brief Receives the air unit's SoC temperature over a plain TCP connection. + * + * A small script on the VTX periodically opens a TCP connection to this port + * (over the wfb-ng tunnel) and writes the temperature in degrees C as ASCII, + * then closes. There's no MSP/DisplayPort framing here at all -- this is + * intentionally independent of the msposd/MspDisplayPortWidget telemetry + * path, since the air unit's SoC temp isn't something the flight controller + * knows about. + */ +class VtxTempSensor : public ISensor { +public: + explicit VtxTempSensor(int port) : port(port) { + server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + spdlog::error("VtxTempSensor: socket() failed"); + return; + } + int opt = 1; + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(static_cast(port)); + if (bind(server_fd, reinterpret_cast(&addr), sizeof(addr)) < 0) { + spdlog::error("VtxTempSensor: bind() failed on port {}", port); + close(server_fd); + server_fd = -1; + return; + } + listen(server_fd, 4); + is_valid_sensor = true; + worker = std::thread(&VtxTempSensor::listenLoop, this); + } + + ~VtxTempSensor() override { + running = false; + if (server_fd >= 0) { + shutdown(server_fd, SHUT_RDWR); + close(server_fd); + } + if (worker.joinable()) { + worker.join(); + } + } + + void run() override { + if (!is_valid_sensor) { + return; + } + osd_tag tags[1]; + strcpy(tags[0].key, "name"); + strcpy(tags[0].val, "vtx"); + osd_publish_double_fact("vtx.temperature", tags, 1, latest.load()); + } + + bool is_valid() override { + return is_valid_sensor; + } + +private: + void listenLoop() { + while (running) { + sockaddr_in client{}; + socklen_t len = sizeof(client); + int fd = accept(server_fd, reinterpret_cast(&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) { + try { + latest.store(std::stod(std::string(buf, static_cast(n)))); + } catch (...) { + spdlog::warn("VtxTempSensor: got unparseable data on port {}", port); + } + } + close(fd); + } + } + + int port; + int server_fd = -1; + bool is_valid_sensor = false; + std::atomic running{true}; + std::atomic latest{0.0}; + std::thread worker; +}; + void OsSensors::addCPU() { sensors.push_back(std::make_shared()); @@ -408,6 +504,10 @@ std::size_t OsSensors::discoverTemperature() { return sensors.size() - before; } +void OsSensors::addVtxTemp(int port) { + sensors.push_back(std::make_shared(port)); +} + std::size_t OsSensors::autodiscover() { return discoverCPU() + discoverPower() + discoverTemperature(); } diff --git a/src/os_mon.hpp b/src/os_mon.hpp index 7be90f0b..5b9ee015 100644 --- a/src/os_mon.hpp +++ b/src/os_mon.hpp @@ -23,6 +23,9 @@ class OsSensors { // Adds temperature sensor void addTemperature(const std::string &thermal_zone); std::size_t discoverTemperature(); + // Adds a TCP-listening sensor that receives the air unit's SoC temperature, + // sent over the wfb-ng tunnel by a small script on the VTX (see openipc-vtx/NOTES.md) + void addVtxTemp(int port); // Automatically detects and adds all CPU, power and temperature sensors; returns the number // of discovered sensors std::size_t autodiscover(); From bd61b5736d2fc1661304b893d9295e2231bfc59e Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Mon, 14 Sep 2026 09:03:50 +0300 Subject: [PATCH 09/12] Fix MSP OSD UDP bind failures and truncated aggregated packets 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 Claude-Session: https://claude.ai/code/session_01LWYaFJL5iNrmr73dY6Tvuz --- src/osd.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/osd.cpp b/src/osd.cpp index 8c993082..f693f3d7 100644 --- a/src/osd.cpp +++ b/src/osd.cpp @@ -1814,6 +1814,17 @@ class MspDisplayPortWidget: public Widget { return; } + // This port has been observed intermittently held by an unrelated + // process at boot on some boards (wpa_supplicant/udhcpc sharing a + // duplicated fd -- see project notes), which fails our bind() and + // silently aborts widget setup, leaving the startup preview stuck + // forever with nothing left to clear it. SO_REUSEADDR lets our + // bind succeed regardless. + int reuse = 1; + if (setsockopt(udp_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { + SPDLOG_WARN("Failed to set SO_REUSEADDR: {}", strerror(errno)); + } + struct sockaddr_in addr {}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); @@ -2006,7 +2017,13 @@ class MspDisplayPortWidget: public Widget { void udpReaderLoop() { pthread_setname_np(pthread_self(), "MSP-UDP-Reader"); spdlog::info("MSP UDP reader thread started"); - uint8_t buffer[1024]; + // msposd aggregates multiple MSP DisplayPort commands per UDP datagram + // (wfb-ng's radio_mtu is 1445 bytes) -- a full-screen OSD update can + // exceed 1024 bytes. recvfrom() silently truncates anything larger + // than the buffer with no error, which drops trailing commands + // (often the terminating DRAW_SCREEN) and leaves stale glyphs + // on screen. Sized well above any realistic aggregated packet. + uint8_t buffer[8192]; struct sockaddr_in src_addr; socklen_t src_len = sizeof(src_addr); @@ -2014,6 +2031,9 @@ class MspDisplayPortWidget: public Widget { int recv_len = recvfrom(udp_socket, buffer, sizeof(buffer), 0, (struct sockaddr*)&src_addr, &src_len); if (recv_len > 0) { + if ((size_t)recv_len == sizeof(buffer)) { + spdlog::warn("MSP UDP packet filled the {}-byte buffer -- likely truncated", sizeof(buffer)); + } last_data_ms.store(nowMs(), std::memory_order_relaxed); // link is alive static uint32_t packet_count = 0; packet_count++; From d347200b53c1c49c871ef9498531857013a6353e Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Wed, 16 Sep 2026 12:01:00 +0300 Subject: [PATCH 10/12] Rename DVR "Auto-record on Signal" menu label to "Auto Record" Co-Authored-By: Claude Sonnet 5 --- src/gsmenu/colmenu_pages.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gsmenu/colmenu_pages.c b/src/gsmenu/colmenu_pages.c index 8782cfc5..685467c8 100644 --- a/src/gsmenu/colmenu_pages.c +++ b/src/gsmenu/colmenu_pages.c @@ -437,7 +437,7 @@ static const colmenu_item_t sys_display_items[] = { 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 on Signal", .param="dvr_on_signal", .on_change=on_dvr_on_signal }, + { .kind=COLMENU_SWITCH, .label="Auto Record", .param="dvr_on_signal", .on_change=on_dvr_on_signal }, { .kind=COLMENU_DROPDOWN, .label="Mode", .param="dvr_mode", .on_change=on_dvr_mode }, { .kind=COLMENU_SLIDER, .label="Max file size (MB)", .param="dvr_max_size", .on_change=on_dvr_max_size, .display_scale=100 }, { .kind=COLMENU_DROPDOWN, .label="Codec", .param="dvr_reenc_codec", .on_change=on_dvr_reenc_codec }, From 1665d27371d78065bd08d8f0598546f1fd280e6f Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Wed, 16 Sep 2026 13:10:00 +0300 Subject: [PATCH 11/12] Add HDZero-inspired channel scan page to GS WFB-NG menu Adds System -> WFB-NG -> Channel Scan: a colmenu dynamic page (build/ prefetch, same pattern as the existing WiFi-networks page) that reads per-channel RF activity from gsmenu.sh (channel:count lines), sorts noisiest-first, and renders each as a selectable row with a bullet-dot bar. Selecting a row applies that channel via the same "set gs wfbng gs_channel" command the existing Channel dropdown already uses, so it also pushes to the air unit when a drone link is detected. The actual scan (hopping the secondary RX NIC, sampling rx_packets, restoring the live channel) lives in the integrator's gsmenu.sh, not here -- see debian/manpage.md's sibling gsmenu.sh template for the new "chanscan_results" protocol entry this page expects. Co-Authored-By: Claude Sonnet 5 --- src/gsmenu/colmenu_pages.c | 65 +++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/gsmenu/colmenu_pages.c b/src/gsmenu/colmenu_pages.c index 685467c8..33d2209a 100644 --- a/src/gsmenu/colmenu_pages.c +++ b/src/gsmenu/colmenu_pages.c @@ -405,15 +405,78 @@ static const colmenu_page_t gs_actions_page = { .title="Actions", .type="gs", /* ── GS ────────────────────────────────────────────────────────────────────── */ +/* Channel scan (HDZero-style bar graph). gsmenu.sh's chanscan_run() hops the GS's + * secondary wfb-ng RX NIC across every channel the adapter supports and reports + * back raw per-channel frame counts as "channel:count" lines (see its comment for + * why frame-count rather than `iw survey dump`, and why the primary NIC/video + * feed isn't touched). Normalizing against the scan's own max here, rather than + * in the shell, keeps the bar scale correct regardless of how many channels or + * how much traffic a given scan sees. */ +static void chanscan_rescan(void * ctx) { (void)ctx; colmenu_rescan(); } +/* Selecting a scanned channel applies it exactly like the "Channel" dropdown + * (same "set gs wfbng gs_channel" command) — which already both reconfigures + * the GS locally and, when a drone is linked, SSHes the air unit to switch its + * channel too and restarts wifibroadcast on both ends. */ +static void apply_channel(void * ctx) +{ + colmenu_exec((const char *)ctx); + colmenu_rescan(); +} +static void build_chanscan(colmenu_emit_t * e) +{ + char * out = colmenu_get("gs", "wfbng", "chanscan_results", NULL); + int chans[64], counts[64], n = 0, max_count = 1; + if(out) { + char * save = NULL; + for(char * line = strtok_r(out, "\n", &save); line && n < 64; line = strtok_r(NULL, "\n", &save)) { + int ch, count; + if(sscanf(line, "%d:%d", &ch, &count) != 2) continue; + chans[n] = ch; counts[n] = count; n++; + if(count > max_count) max_count = count; + } + } + free(out); + /* Noisiest first — the point of the scan is to surface congestion, and the + * OSD column only shows a handful of rows at a time without scrolling. */ + for(int i = 1; i < n; i++) { + int kc = counts[i], kh = chans[i], j = i - 1; + while(j >= 0 && counts[j] < kc) { counts[j+1] = counts[j]; chans[j+1] = chans[j]; j--; } + counts[j+1] = kc; chans[j+1] = kh; + } + for(int i = 0; i < n; i++) { + int pct = counts[i] * 100 / max_count; + /* Montserrat's compiled cmap only carries a handful of non-ASCII glyphs + * (the LV_SYMBOL_* icon set plus the bullet), no Unicode block elements — + * "\xE2\x80\xA2" (U+2022 bullet) is the closest thing to a solid/filled + * cell that's actually in the font, verified against + * lv_font_montserrat_20.c's unicode_list rather than assumed. */ + char bar[64]; + int filled = pct / 10, pos = 0; + for(int j = 0; j < filled; j++) { memcpy(bar + pos, "\xE2\x80\xA2", 3); pos += 3; } + for(int j = filled; j < 10; j++) bar[pos++] = '.'; + bar[pos] = '\0'; + char label[80]; + snprintf(label, sizeof(label), "Ch %-3d %s %d", chans[i], bar, counts[i]); + char cmd[64]; + snprintf(cmd, sizeof(cmd), "gsmenu.sh set gs wfbng gs_channel %d", chans[i]); + colmenu_emit_action(e, LV_SYMBOL_WIFI, label, apply_channel, strdup(cmd)); + } + if(n == 0) colmenu_emit_label(e, "No scan results yet"); + colmenu_emit_action(e, LV_SYMBOL_REFRESH, "Rescan", chanscan_rescan, NULL); +} +static const char * const chanscan_prefetch[] = { "chanscan_results", NULL }; +static const colmenu_page_t gs_chanscan_page = { .title="Channel Scan", .type="gs", .page="wfbng", .build=build_chanscan, .prefetch=chanscan_prefetch }; + static const colmenu_item_t gs_wfbng_items[] = { { .kind=COLMENU_LABEL, .label="WFB-NG" }, { .kind=COLMENU_DROPDOWN, .icon=LV_SYMBOL_SETTINGS, .label="Channel", .param="gs_channel" }, { .kind=COLMENU_DROPDOWN, .icon=LV_SYMBOL_SETTINGS, .label="Bandwidth", .param="bandwidth" }, { .kind=COLMENU_SLIDER, .icon=LV_SYMBOL_SETTINGS, .label="TXPower (%)", .param="txpower" }, + { .kind=COLMENU_SUBMENU, .icon=LV_SYMBOL_WIFI, .label="Channel Scan", .sub=&gs_chanscan_page }, { .kind=COLMENU_LABEL, .label="Adaptive Link" }, { .kind=COLMENU_SWITCH, .icon=LV_SYMBOL_SETTINGS, .label="Enabled", .param="adaptivelink" }, }; -static const colmenu_page_t gs_wfbng_page = { "WFB-NG", "gs", "wfbng", gs_wfbng_items, 6 }; +static const colmenu_page_t gs_wfbng_page = { "WFB-NG", "gs", "wfbng", gs_wfbng_items, 7 }; /* System → Receiver / Display / DVR */ static const colmenu_item_t sys_receiver_items[] = { From ad8343a55edbdc13ee4c23fb45fe5c06bf3e4545 Mon Sep 17 00:00:00 2001 From: Ori Mor Date: Wed, 16 Sep 2026 13:10:14 +0300 Subject: [PATCH 12/12] Document chanscan_results in the gsmenu.sh integrator template Co-Authored-By: Claude Sonnet 5 --- gsmenu.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gsmenu.sh b/gsmenu.sh index 02c8ae10..cb958056 100755 --- a/gsmenu.sh +++ b/gsmenu.sh @@ -377,6 +377,14 @@ case "$@" in "get gs wfbng adaptivelink") echo 0 ;; + "get gs wfbng chanscan_results") + # integrator: hop your secondary RX NIC across every supported channel, + # sampling per-channel RF activity (e.g. rx_packets delta over a short + # dwell, since `iw survey dump` isn't implemented by every driver), and + # print one "channel:count" line per channel. See PixelPilot's GS menu + # "WFB-NG -> Channel Scan" page, which normalizes and bar-graphs this. + printf "" + ;; "set gs wfbng gs_channel"*) : ;; "set gs wfbng bandwidth"*) : ;;