-
Notifications
You must be signed in to change notification settings - Fork 44
Add HDZero-inspired channel scan to the GS menu #150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0474d1c
72579de
15e3eee
07d278c
eb47843
1b23c40
4c69674
29085aa
bd61b57
d347200
1665d27
ad8343a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -399,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(); | ||
|
Comment on lines
+420
to
+423
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5. Channel results can reflect the old setup apply_channel() launches colmenu_exec() and immediately calls colmenu_rescan() without waiting for the worker running the channel-change command to complete. Because rebuilding the dynamic page independently starts the prefetched chanscan_results read, it can collect and display measurements from the previous channel and receiver configuration while the setter is still active, particularly when the change restarts WFB-NG. Agent Prompt
|
||
| } | ||
| 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[] = { | ||
|
|
@@ -431,6 +500,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", .param="dvr_on_signal", .on_change=on_dvr_on_signal }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. The auto record control does nothing colmenu_get() and do_set() do not special-case the runtime-only dvr_on_signal parameter, so the row falls through to gsmenu.sh, whose integrator template has no matching get or set command. Opening the page consequently reads the switch as off, while changing it reaches an unknown shell command whose failure occurs before the deferred on_dvr_on_signal() callback can update dvr_on_signal_enabled. Agent Prompt
|
||
| { .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 +509,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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; } | ||
|
Comment on lines
+848
to
+852
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Live auto record changes miss the signal dvr_set_on_signal() only changes the boolean, while drone_detect_timer() starts or stops recording exclusively when its cached detection state transitions. Enabling the option after a link is already present does not start recording, and disabling it during an automatic recording prevents the later signal-loss transition from stopping that recording. Agent Prompt
|
||
| } | ||
|
|
||
| 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 <codec> - Video codec, should be the same as on VTX (Default: h265 <h264|h265|auto>)\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; | ||
|
|
@@ -1764,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 ) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8. Other 720p100 displays can lose video
🐞 Bug≡ CorrectnessAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools