From 1f98e38e0111666986ee4e7a76cddafc9a2472ca Mon Sep 17 00:00:00 2001 From: Henk Wiedig Date: Sun, 2 Aug 2026 19:59:51 +0200 Subject: [PATCH 1/3] rework WiFiRSSIMonitor to WiFiMonitor --- CMakeLists.txt | 4 +- README.md | 21 +- config_osd.json | 141 +++++++++++ src/WiFiMonitor.cpp | 300 +++++++++++++++++++++++ src/{WiFiRSSIMonitor.h => WiFiMonitor.h} | 2 +- src/WiFiMonitor.hpp | 86 +++++++ src/WiFiRSSIMonitor.cpp | 180 -------------- src/WiFiRSSIMonitor.hpp | 36 --- src/gsmenu/colmenu_pages.c | 2 +- src/gsmenu/helper.c | 4 +- src/main.cpp | 4 +- src/osd.cpp | 9 +- 12 files changed, 561 insertions(+), 228 deletions(-) create mode 100644 src/WiFiMonitor.cpp rename src/{WiFiRSSIMonitor.h => WiFiMonitor.h} (74%) create mode 100644 src/WiFiMonitor.hpp delete mode 100644 src/WiFiRSSIMonitor.cpp delete mode 100644 src/WiFiRSSIMonitor.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e6f3422a..a376093c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,8 +115,8 @@ set(LIB_SOURCE_FILES src/mavlink.c src/wfbcli.hpp src/wfbcli.cpp - src/WiFiRSSIMonitor.hpp - src/WiFiRSSIMonitor.cpp + src/WiFiMonitor.hpp + src/WiFiMonitor.cpp src/scheduling_helper.hpp src/gstrtpreceiver.cpp src/gstrtpreceiver.h) diff --git a/README.md b/README.md index 341d05d7..44b61253 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Typical OSD config looks like: "x": 10, "y": 60, "icon_path": "device_thermostat.png", - "template": "CPU: %.0f⁰C, GPU: %.0f⁰C", + "template": "CPU: %.0f°C, GPU: %.0f°C", "facts": [ {"name": "os_mon.temperature", "tags": {"name": "soc-thermal"}, @@ -222,7 +222,20 @@ lot of facts to which widgets can subscribe to: | `video.decoder_feed_time_ms` | uint | Time to feed the video packet to hardware decoder | | `gstreamer.received_bytes` | uint | Number of bytes received from gstreamer (published for each packet) | | `osd.custom_message` | str | The custom message passed via `--osd-custom-message` feature | -| `os_mon.wifi.rssi` | uint | rssi as reported from /proc/net/rtl88x2eu//trx_info_debug | +| `os_mon.wifi.rssi` | int | rssi as reported from /proc/net/``/``/trx_info_debug | +| `os_mon.wifi.temperature` | int | RF chip temperature, degrees C, from /proc/net/``/``/thermal_state | + +The `os_mon.wifi.*` facts are only published in APFPV mode (in WFB mode the same data comes from +wfb-ng as `wfbcli.*`). They are read from the proc interface of the Realtek drivers - every +`/proc/net/rtl*` directory is scanned, so all card families (rtl88x2eu, rtl88x2cu, rtl88x2bu, ...) +are covered; cards whose driver has no `thermal_state` simply publish no temperature. +Both are tagged with `interface` (eg `wlx0013eff604e2`) and `adapter`, plus `type` +(`rssi_a`/`rssi_b`/`rssi_percent`/`connected`) for the rssi and `rf_path` (`0`/`1`) for the +temperature. `adapter` is the index of the card among all adapters found, counted from 0 in +interface-name order - bind widgets to it (`{"adapter": "0", "type": "rssi_a"}`) rather than to the +MAC-derived interface name, which differs per ground station. Note that the index of a card changes +when other adapters are plugged in or removed. Temperature is sampled every 10 seconds, because +reading it makes the driver trigger an ADC conversion on the RF chip. There are many facts based on Mavlink telemetry, see `mavlink.c`. All of them have tags "sysid" and "compid", but some have extra tags. @@ -333,6 +346,10 @@ to display any fact (as long as datatype matches): * `{"type": "DebugWidget"}` - displays debug information (name, type, tags, value) about fact(s) * `{"type": "IconSelectorWidget"}` - display a icon based on a fact's value +Text is rendered with LVGL's built-in Montserrat font, which only covers ASCII plus the degree sign +`°` (U+00B0) and a few symbols. Any other character comes out as an empty box - watch out for +look-alikes such as `⁰` (U+2070, superscript zero). + Specific widgets expect quite concrete facts as input: * `DvrStatusWidget` - shows up when DVR is recording and is hidden when not. diff --git a/config_osd.json b/config_osd.json index 01f44c1c..8e556ee1 100644 --- a/config_osd.json +++ b/config_osd.json @@ -131,6 +131,123 @@ {"range": [-130, -91], "icon_path": "signal8.png"} ] }, + { + "type": "IconSelectorWidget", + "name": "APFPV RSSI of adapter 1 antenna 1", + "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"} + } + ], + "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"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "APFPV RSSI of adapter 1 antenna 2", + "x": -205, + "y": 0, + "facts": [ + {"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"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "APFPV RSSI of adapter 2 antenna 1", + "x": -160, + "y": 0, + "facts": [ + {"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"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "APFPV RSSI of adapter 2 antenna 2", + "x": -115, + "y": 0, + "facts": [ + {"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"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "APFPV RSSI of adapter 3 antenna 1", + "x": -70, + "y": 0, + "facts": [ + {"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"} + ] + }, + { + "type": "IconSelectorWidget", + "name": "APFPV RSSI of adapter 3 antenna 2", + "x": -25, + "y": 0, + "facts": [ + {"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"} + ] + }, { "name": "Video FPS and resolution", "type": "VideoWidget", @@ -212,6 +329,30 @@ "critical": 2, "facts": [ { "name": "gstreamer.received_bytes" } ] }, + { + "name": "APFPV WiFi RF temperature adapter 1", + "type": "---IconTplTextWidget", + "x": -250, + "y": 155, + "icon_path": "device_thermostat.png", + "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": "APFPV WiFi RF temperature adapter 2", + "type": "---IconTplTextWidget", + "x": -250, + "y": 185, + "icon_path": "device_thermostat.png", + "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": "MspDisplayPort", "type": "---MspDisplayPortWidget", diff --git a/src/WiFiMonitor.cpp b/src/WiFiMonitor.cpp new file mode 100644 index 00000000..404281f8 --- /dev/null +++ b/src/WiFiMonitor.cpp @@ -0,0 +1,300 @@ +#include "WiFiMonitor.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "spdlog/spdlog.h" + +extern "C" { +#include "osd.h" +} + +namespace fs = std::filesystem; + +namespace { + +/* Realtek drivers register their proc directory under the driver name: + * rtl88x2eu, rtl88x2cu, rtl88x2bu, rtl8812au, ... */ +constexpr const char* DRIVER_DIR_PREFIX = "rtl"; + +void set_tag(osd_tag* tag, const char* key, const std::string& val) { + strncpy(tag->key, key, TAG_MAX_LEN - 1); + strncpy(tag->val, val.c_str(), TAG_MAX_LEN - 1); + tag->key[TAG_MAX_LEN - 1] = '\0'; + tag->val[TAG_MAX_LEN - 1] = '\0'; +} + +std::string trim(const std::string& s) { + const auto begin = s.find_first_not_of(" \t\r\n"); + if (begin == std::string::npos) { + return ""; + } + return s.substr(begin, s.find_last_not_of(" \t\r\n") - begin + 1); +} + +} // namespace + +// Constructor implementation +WiFiMonitor::WiFiMonitor() + : base_path_("/proc/net"), last_thermal_read_(), warned_no_driver_(false) {} + +// WiFiStats constructor implementation +WiFiMonitor::WiFiStats::WiFiStats() : rssi_a(0), rssi_b(0), rssi_min(0), rssi_percent(0), is_linked(false) {} + +std::vector WiFiMonitor::find_interfaces() { + std::vector interfaces; + std::error_code ec; + + // directory_iterator(ec) yields end() instead of throwing when /proc/net or a + // driver directory disappears (USB adapter unplugged) while we walk it. + for (const auto& driver : fs::directory_iterator(base_path_, ec)) { + if (!driver.is_directory(ec)) continue; + if (driver.path().filename().string().rfind(DRIVER_DIR_PREFIX, 0) != 0) continue; + + for (const auto& interface : fs::directory_iterator(driver.path(), ec)) { + if (interface.is_directory(ec)) { + interfaces.push_back(interface.path()); + } + } + } + + // The position in this list is published as the `adapter` tag, so sort by + // interface name: readdir order is not guaranteed, and cards may sit below + // different drivers. + std::sort(interfaces.begin(), interfaces.end(), + [](const fs::path& a, const fs::path& b) { return a.filename() < b.filename(); }); + + return interfaces; +} + +void WiFiMonitor::run() { + + std::vector interfaces = find_interfaces(); + if (interfaces.empty()) { + if (!warned_no_driver_) { + spdlog::error("No Realtek WiFi driver found below {}, no wifi stats will be published", base_path_); + warned_no_driver_ = true; + } + return; + } + warned_no_driver_ = false; + + // Temperature is sampled on its own, slower schedule (see THERMAL_INTERVAL) + const auto now = std::chrono::steady_clock::now(); + const bool read_thermal = (now - last_thermal_read_) >= THERMAL_INTERVAL; + if (read_thermal) { + last_thermal_read_ = now; + } + + // Initialize batch - estimate 6 facts per interface + void* batch = osd_batch_init(24); + + // Collect the stats of every interface of every driver + for (size_t adapter = 0; adapter < interfaces.size(); adapter++) { + const fs::path& interface = interfaces[adapter]; + + osd_tag base_tags[BASE_TAGS]; + make_base_tags(base_tags, interface.filename(), (int)adapter); + + std::string debug_file = interface / "trx_info_debug"; + if (fs::exists(debug_file)) { + WiFiStats stats = parse_interface_stats(debug_file); + add_interface_stats_to_batch(batch, base_tags, stats); + } + + std::string thermal_file = interface / "thermal_state"; + if (read_thermal && fs::exists(thermal_file)) { + for (const auto& thermal : parse_thermal_state(thermal_file)) { + add_temperature_fact_to_batch(batch, base_tags, thermal.rf_path, thermal.temperature); + } + } + } + + // Publish all collected facts + osd_publish_batch(batch); +} + +WiFiMonitor::WiFiStats WiFiMonitor::parse_interface_stats(const std::string& file_path) { + + WiFiStats stats; + std::ifstream file(file_path); + + if (!file.is_open()) { + return stats; + } + + std::string line; + while (std::getline(file, line)) { + // Parse RSSI A and B + if (line.find("rssi_a =") != std::string::npos) { + std::regex rssi_ab_regex(R"(rssi_a\s*=\s*(\d+)\(%\),\s*rssi_b\s*=\s*(\d+)\(%\))"); + std::smatch match; + if (std::regex_search(line, match, rssi_ab_regex) && match.size() == 3) { + stats.rssi_a = std::stoi(match[1]); + stats.rssi_b = std::stoi(match[2]); + } + } + // Parse RSSI percentage + else if (line.find("rssi :") != std::string::npos) { + std::regex rssi_regex(R"(rssi\s*:\s*(\d+)\s*\(\%\))"); + std::smatch match; + if (std::regex_search(line, match, rssi_regex) && match.size() > 1) { + stats.rssi_percent = std::stoi(match[1]); + } + } + else if (line.find("is_linked =") != std::string::npos) { + std::regex linked_regex(R"(is_linked\s*=\s*(\d+))"); + std::smatch match; + if (std::regex_search(line, match, linked_regex) && match.size() > 1) { + stats.is_linked = (std::stoi(match[1]) == 1); + } + } + } + + file.close(); + return stats; +} + +std::vector WiFiMonitor::parse_thermal_state(const std::string& file_path) { + // One line per RF path, e.g. + // rf_path: 0, thermal_value: 53, offset: 21, temperature: 112 + // Parsed as generic comma-separated "key: value" pairs (like wfb-ng does), so + // additional or reordered fields don't break the parser. + std::vector thermals; + std::ifstream file(file_path); + + if (!file.is_open()) { + return thermals; + } + + std::string line; + while (std::getline(file, line)) { + std::unordered_map fields; + std::stringstream fields_stream(line); + std::string field; + + while (std::getline(fields_stream, field, ',')) { + const auto colon = field.find(':'); + if (colon == std::string::npos) continue; + try { + fields[trim(field.substr(0, colon))] = std::stoi(field.substr(colon + 1)); + } catch (const std::exception&) { + // Not a numeric field, ignore it + } + } + + const auto rf_path = fields.find("rf_path"); + const auto temperature = fields.find("temperature"); + if (rf_path != fields.end() && temperature != fields.end()) { + thermals.push_back({rf_path->second, temperature->second}); + } + } + + file.close(); + return thermals; +} + +void WiFiMonitor::make_base_tags(osd_tag* tags, const std::string& interface_name, int adapter) { + set_tag(&tags[0], "interface", interface_name); + set_tag(&tags[1], "adapter", std::to_string(adapter)); +} + +void WiFiMonitor::add_interface_stats_to_batch(void* batch, const osd_tag* base_tags, const WiFiStats& stats) { + if (!stats.is_linked) { + return; // Don't publish stats for disconnected interfaces + } + + // Publish RSSI A + add_rssi_fact_to_batch(batch, base_tags, "rssi_a", stats.rssi_a); + + // Publish RSSI B + add_rssi_fact_to_batch(batch, base_tags, "rssi_b", stats.rssi_b); + + // Publish RSSI Overall Percentage + add_rssi_fact_to_batch(batch, base_tags, "rssi_percent", stats.rssi_percent); + + // Publish connection status + add_rssi_fact_to_batch(batch, base_tags, "connected", 1); +} + +void WiFiMonitor::add_rssi_fact_to_batch(void* batch, const osd_tag* base_tags, const std::string& rssi_type, int value) { + osd_tag tags[N_TAGS]; + + // Copy interface and adapter tags + memcpy(tags, base_tags, BASE_TAGS * sizeof(osd_tag)); + + // Add type tag + set_tag(&tags[BASE_TAGS], "type", rssi_type); + + // Add fact to batch + osd_add_int_fact(batch, "os_mon.wifi.rssi", tags, N_TAGS, value); +} + +void WiFiMonitor::add_temperature_fact_to_batch(void* batch, const osd_tag* base_tags, int rf_path, int value) { + osd_tag tags[N_TAGS]; + + // Copy interface and adapter tags + memcpy(tags, base_tags, BASE_TAGS * sizeof(osd_tag)); + + // Add the RF path the temperature was measured on + set_tag(&tags[BASE_TAGS], "rf_path", std::to_string(rf_path)); + + // Add fact to batch, degrees C + osd_add_int_fact(batch, "os_mon.wifi.temperature", tags, N_TAGS, value); +} + +void WiFiMonitor::publish_reset() { + std::vector interfaces = find_interfaces(); + if (interfaces.empty()) { + spdlog::warn("No Realtek WiFi driver found below {} for reset", base_path_); + return; + } + + // Initialize batch - estimate 6 facts per interface + void* batch = osd_batch_init(24); + + // Publish reset values for all interfaces of all drivers + for (size_t adapter = 0; adapter < interfaces.size(); adapter++) { + publish_interface_reset(batch, interfaces[adapter], (int)adapter); + } + + // Publish all reset facts + osd_publish_batch(batch); + + spdlog::debug("Published WiFi reset values for all interfaces"); +} + +void WiFiMonitor::publish_interface_reset(void* batch, const fs::path& interface_path, int adapter) { + // Prepare common tags + osd_tag base_tags[BASE_TAGS]; + make_base_tags(base_tags, interface_path.filename(), adapter); + + // Publish all RSSI values as -1 (reset/error value) + add_rssi_fact_to_batch(batch, base_tags, "rssi_a", -1); + add_rssi_fact_to_batch(batch, base_tags, "rssi_b", -1); + add_rssi_fact_to_batch(batch, base_tags, "rssi_min", -1); + add_rssi_fact_to_batch(batch, base_tags, "rssi_percent", -1); + add_rssi_fact_to_batch(batch, base_tags, "connected", 0); // 0 = disconnected + + // Same for the temperature of every RF path the card reports + std::string thermal_file = interface_path / "thermal_state"; + if (fs::exists(thermal_file)) { + for (const auto& thermal : parse_thermal_state(thermal_file)) { + add_temperature_fact_to_batch(batch, base_tags, thermal.rf_path, -1); + } + } +} + +// C-callable function implementations +extern "C" { + +void wifi_monitor_reset(void) { + static WiFiMonitor monitor; + monitor.publish_reset(); +} + +} // extern "C" diff --git a/src/WiFiRSSIMonitor.h b/src/WiFiMonitor.h similarity index 74% rename from src/WiFiRSSIMonitor.h rename to src/WiFiMonitor.h index 1f925ceb..3dbb8f8f 100644 --- a/src/WiFiRSSIMonitor.h +++ b/src/WiFiMonitor.h @@ -5,7 +5,7 @@ extern "C" { #endif -void wifi_rssi_monitor_reset(void); +void wifi_monitor_reset(void); #ifdef __cplusplus } diff --git a/src/WiFiMonitor.hpp b/src/WiFiMonitor.hpp new file mode 100644 index 00000000..02e20bfd --- /dev/null +++ b/src/WiFiMonitor.hpp @@ -0,0 +1,86 @@ +#ifndef WIFI_MONITOR_H +#define WIFI_MONITOR_H + +#include +#include +#include +#include + +extern "C" { +#include "osd.h" +} + +/* + * Link stats of the directly attached WiFi adapters, published as os_mon.wifi.* + * facts. Used in APFPV mode, where wfb-ng (and with it the wfbcli.* facts) does + * not run. + * + * The Realtek rtlwifi-family drivers expose per-interface stats under + * /proc/net///: + * trx_info_debug - RSSI per RF path and link state + * thermal_state - RF chip temperature per RF path + * is the driver name and therefore differs per card family (rtl88x2eu, + * rtl88x2cu, rtl88x2bu, rtl8812au, ...), so every rtl* directory is scanned + * instead of hardcoding one. Files that a given driver does not provide are + * skipped (thermal_state only exists on the libc0607-derived 88x2 drivers). + * + * Every fact carries an `adapter` tag, the index of the card in the (sorted, so + * stable) list of all adapters found. Without it a widget can only match on + * `interface`, whose name is derived from the card's MAC - so an OSD binding + * would have to be written per ground station, and a binding that leaves the + * interface open would be fed by all cards in turn. This is the APFPV + * counterpart of the `ant_id` tag wfb-ng puts on the wfbcli.* facts. + */ +class WiFiMonitor { +public: + WiFiMonitor(); + void run(); + void publish_reset(); + +private: + struct WiFiStats { + int rssi_a; + int rssi_b; + int rssi_min; + int rssi_percent; + bool is_linked; + + WiFiStats(); + }; + + /* One RF path of a card; temperature is in degrees C, as computed by the driver. */ + struct ThermalStats { + int rf_path; + int temperature; + }; + + /* Reading thermal_state makes the driver trigger an ADC conversion on the RF + * chip, so it is sampled far slower than run() is called. Matches wfb-ng's + * temp_measurement_interval default. */ + static constexpr std::chrono::seconds THERMAL_INTERVAL{10}; + + std::string base_path_; + std::chrono::steady_clock::time_point last_thermal_read_; + bool warned_no_driver_; + + /* Number of tags every fact carries: `interface` and `adapter`, plus the one + * describing the value itself (`type` / `rf_path`). Kept identical for all + * facts of the same name so a widget can filter on any of them. */ + static constexpr int BASE_TAGS = 2; + static constexpr int N_TAGS = BASE_TAGS + 1; + + /* All /proc/net// directories currently present, sorted + * by interface name so the adapter index of a card doesn't depend on readdir + * order (it does change when adapters are added or removed, though). */ + std::vector find_interfaces(); + WiFiStats parse_interface_stats(const std::string& file_path); + std::vector parse_thermal_state(const std::string& file_path); + void make_base_tags(osd_tag* tags, const std::string& interface_name, int adapter); + void add_interface_stats_to_batch(void* batch, const osd_tag* base_tags, const WiFiStats& stats); + void add_rssi_fact_to_batch(void* batch, const osd_tag* base_tags, const std::string& rssi_type, int value); + void add_temperature_fact_to_batch(void* batch, const osd_tag* base_tags, int rf_path, int value); + void publish_interface_reset(void* batch, const std::filesystem::path& interface_path, int adapter); + +}; + +#endif diff --git a/src/WiFiRSSIMonitor.cpp b/src/WiFiRSSIMonitor.cpp deleted file mode 100644 index eb5d3803..00000000 --- a/src/WiFiRSSIMonitor.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "WiFiRSSIMonitor.hpp" -#include -#include -#include -#include -#include -#include -#include -#include "spdlog/spdlog.h" - -extern "C" { -#include "osd.h" -} - -namespace fs = std::filesystem; - -// Constructor implementation -WiFiRSSIMonitor::WiFiRSSIMonitor() : base_path_("/proc/net/rtl88x2eu") {} - -// WiFiStats constructor implementation -WiFiRSSIMonitor::WiFiStats::WiFiStats() : rssi_a(0), rssi_b(0), rssi_min(0), rssi_percent(0), is_linked(false) {} - -void WiFiRSSIMonitor::run() { - - if (!fs::exists(base_path_)) { - spdlog::error("RTL88x2eu proc path not found: {}", base_path_); - return; - } - - // Initialize batch - estimate 5 facts per interface - void* batch = osd_batch_init(20); - - // Find all WiFi interfaces and collect their stats - for (const auto& entry : fs::directory_iterator(base_path_)) { - if (!entry.is_directory()) continue; - - std::string interface_name = entry.path().filename(); - std::string debug_file = entry.path() / "trx_info_debug"; - - if (fs::exists(debug_file)) { - WiFiStats stats = parse_interface_stats(debug_file); - add_interface_stats_to_batch(batch, interface_name, stats); - } - } - - // Publish all collected facts - osd_publish_batch(batch); -} - -WiFiRSSIMonitor::WiFiStats WiFiRSSIMonitor::parse_interface_stats(const std::string& file_path) { - - WiFiStats stats; - std::ifstream file(file_path); - - if (!file.is_open()) { - return stats; - } - - std::string line; - while (std::getline(file, line)) { - // Parse RSSI A and B - if (line.find("rssi_a =") != std::string::npos) { - std::regex rssi_ab_regex(R"(rssi_a\s*=\s*(\d+)\(%\),\s*rssi_b\s*=\s*(\d+)\(%\))"); - std::smatch match; - if (std::regex_search(line, match, rssi_ab_regex) && match.size() == 3) { - stats.rssi_a = std::stoi(match[1]); - stats.rssi_b = std::stoi(match[2]); - } - } - // Parse RSSI percentage - else if (line.find("rssi :") != std::string::npos) { - std::regex rssi_regex(R"(rssi\s*:\s*(\d+)\s*\(\%\))"); - std::smatch match; - if (std::regex_search(line, match, rssi_regex) && match.size() > 1) { - stats.rssi_percent = std::stoi(match[1]); - } - } - else if (line.find("is_linked =") != std::string::npos) { - std::regex linked_regex(R"(is_linked\s*=\s*(\d+))"); - std::smatch match; - if (std::regex_search(line, match, linked_regex) && match.size() > 1) { - stats.is_linked = (std::stoi(match[1]) == 1); - } - } - } - - file.close(); - return stats; -} - -void WiFiRSSIMonitor::add_interface_stats_to_batch(void* batch, const std::string& interface_name, const WiFiStats& stats) { - if (!stats.is_linked) { - return; // Don't publish stats for disconnected interfaces - } - - // Prepare common tags - osd_tag interface_tag; - strncpy(interface_tag.key, "interface", TAG_MAX_LEN - 1); - strncpy(interface_tag.val, interface_name.c_str(), TAG_MAX_LEN - 1); - interface_tag.key[TAG_MAX_LEN - 1] = '\0'; - interface_tag.val[TAG_MAX_LEN - 1] = '\0'; - - // Publish RSSI A - add_rssi_fact_to_batch(batch, "rssi_a", stats.rssi_a, &interface_tag); - - // Publish RSSI B - add_rssi_fact_to_batch(batch, "rssi_b", stats.rssi_b, &interface_tag); - - // Publish RSSI Overall Percentage - add_rssi_fact_to_batch(batch, "rssi_percent", stats.rssi_percent, &interface_tag); - - // Publish connection status - add_rssi_fact_to_batch(batch, "connected", 1, &interface_tag); -} - -void WiFiRSSIMonitor::add_rssi_fact_to_batch(void* batch, const std::string& rssi_type, int value, osd_tag* interface_tag) { - osd_tag tags[2]; - - // Copy interface tag - memcpy(&tags[0], interface_tag, sizeof(osd_tag)); - - // Add type tag - strncpy(tags[1].key, "type", TAG_MAX_LEN - 1); - strncpy(tags[1].val, rssi_type.c_str(), TAG_MAX_LEN - 1); - tags[1].key[TAG_MAX_LEN - 1] = '\0'; - tags[1].val[TAG_MAX_LEN - 1] = '\0'; - - // Add fact to batch - std::string fact_name = "os_mon.wifi.rssi"; - osd_add_int_fact(batch, fact_name.c_str(), tags, 2, value); -} - -void WiFiRSSIMonitor::publish_reset() { - if (!fs::exists(base_path_)) { - spdlog::warn("RTL88x2eu proc path not found for reset: {}", base_path_); - return; - } - - // Initialize batch - estimate 5 facts per interface - void* batch = osd_batch_init(20); - - // Find all WiFi interfaces and publish reset values - for (const auto& entry : fs::directory_iterator(base_path_)) { - if (!entry.is_directory()) continue; - - std::string interface_name = entry.path().filename(); - publish_interface_reset(batch, interface_name); - } - - // Publish all reset facts - osd_publish_batch(batch); - - spdlog::debug("Published WiFi RSSI reset values for all interfaces"); -} - -void WiFiRSSIMonitor::publish_interface_reset(void* batch, const std::string& interface_name) { - // Prepare common tags - osd_tag interface_tag; - strncpy(interface_tag.key, "interface", TAG_MAX_LEN - 1); - strncpy(interface_tag.val, interface_name.c_str(), TAG_MAX_LEN - 1); - interface_tag.key[TAG_MAX_LEN - 1] = '\0'; - interface_tag.val[TAG_MAX_LEN - 1] = '\0'; - - // Publish all RSSI values as -1 (reset/error value) - add_rssi_fact_to_batch(batch, "rssi_a", -1, &interface_tag); - add_rssi_fact_to_batch(batch, "rssi_b", -1, &interface_tag); - add_rssi_fact_to_batch(batch, "rssi_min", -1, &interface_tag); - add_rssi_fact_to_batch(batch, "rssi_percent", -1, &interface_tag); - add_rssi_fact_to_batch(batch, "connected", 0, &interface_tag); // 0 = disconnected -} - -// C-callable function implementations -extern "C" { - -void wifi_rssi_monitor_reset(void) { - static WiFiRSSIMonitor monitor; - monitor.publish_reset(); -} - -} // extern "C" \ No newline at end of file diff --git a/src/WiFiRSSIMonitor.hpp b/src/WiFiRSSIMonitor.hpp deleted file mode 100644 index 4873f285..00000000 --- a/src/WiFiRSSIMonitor.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef WIFI_RSSI_MONITOR_H -#define WIFI_RSSI_MONITOR_H - -#include - -extern "C" { -#include "osd.h" -} - -class WiFiRSSIMonitor { -public: - WiFiRSSIMonitor(); - void run(); - void publish_reset(); - -private: - struct WiFiStats { - int rssi_a; - int rssi_b; - int rssi_min; - int rssi_percent; - bool is_linked; - - WiFiStats(); - }; - - std::string base_path_; - - WiFiStats parse_interface_stats(const std::string& file_path); - void add_interface_stats_to_batch(void* batch, const std::string& interface_name, const WiFiStats& stats); - void add_rssi_fact_to_batch(void* batch, const std::string& rssi_type, int value, osd_tag* interface_tag); - void publish_interface_reset(void* batch, const std::string& interface_name); - -}; - -#endif \ No newline at end of file diff --git a/src/gsmenu/colmenu_pages.c b/src/gsmenu/colmenu_pages.c index 9bece4f4..033ec4ad 100644 --- a/src/gsmenu/colmenu_pages.c +++ b/src/gsmenu/colmenu_pages.c @@ -184,7 +184,7 @@ static void apply_rx_mode(bool apfpv) setenv("REMOTE_IP", apfpv ? "192.168.0.1" : "10.5.0.10", 1); setenv("AIR_FIRMWARE_TYPE", apfpv ? "apfpv" : "wfb", 1); /* Drop the previous mode's stale facts: wfbcli.* (the wfbcli thread only runs in - * WFB) and os_mon.wifi.* (WiFiRSSIMonitor only runs in apfpv). The active source + * WFB) and os_mon.wifi.* (WiFiMonitor only runs in apfpv). The active source * re-publishes its own. Only these two prefixes - a blanket flush would also clear * facts nobody re-publishes, e.g. video.width/height (published once per decoder * frame-info change), leaving the VideoWidget stuck on "?x?". */ diff --git a/src/gsmenu/helper.c b/src/gsmenu/helper.c index 9f314efa..921f9289 100644 --- a/src/gsmenu/helper.c +++ b/src/gsmenu/helper.c @@ -5,7 +5,7 @@ #include "../menu.h" /* enum RXMode, RXMODE */ #include "helper.h" #include "styles.h" -#include "../WiFiRSSIMonitor.h" +#include "../WiFiMonitor.h" extern lv_indev_t * indev_drv; @@ -72,7 +72,7 @@ void gsmenu_toggle_rxmode() { setenv("REMOTE_IP" , "10.5.0.10", 1); setenv("AIR_FIRMWARE_TYPE" , "wfb", 1); #ifndef USE_SIMULATOR - wifi_rssi_monitor_reset(); + wifi_monitor_reset(); #endif break; diff --git a/src/main.cpp b/src/main.cpp index c7029c47..5bf781c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,7 +61,7 @@ extern "C" { #include "os_mon.hpp" #include "pixelpilot_config.h" #include -#include "WiFiRSSIMonitor.hpp" +#include "WiFiMonitor.hpp" #include "menu.h" @@ -144,7 +144,7 @@ size_t gsactions_count; uint32_t video_plane_id_override = 0; uint32_t osd_plane_id_override = 0; -WiFiRSSIMonitor wifi_monitor; +WiFiMonitor wifi_monitor; extern enum RXMode RXMODE; bool enable_live_colortrans = false; diff --git a/src/osd.cpp b/src/osd.cpp index b87f6349..8c993082 100644 --- a/src/osd.cpp +++ b/src/osd.cpp @@ -557,7 +557,9 @@ class FactMatcher { FactTags fact_tags = fact.getTags(); for (const auto& [key, match_value] : tags) { - if (auto value = fact_tags.find(key); value != tags.end()) { + // Compare against fact_tags.end(): find() returns an iterator into + // fact_tags, and an unmatched key used to be dereferenced as if found. + if (auto value = fact_tags.find(key); value != fact_tags.end()) { if (value->second != match_value) return false; } else { return false; @@ -964,7 +966,10 @@ class TplTextWidget: public Widget { std::vector tokenize(const std::string& tpl) { std::vector tokens; - std::regex token_regex(R"(%%|%[bisu]|%(\.\d+)?f|[^%]+)"); // Match placeholders and literals + // Match placeholders and literals. `d` belongs in the class: it is handled + // below (and documented) as an alias of `i`, but without it here the regex + // skips the '%' and the 'd' comes out as a literal. + std::regex token_regex(R"(%%|%[bisud]|%(\.\d+)?f|[^%]+)"); std::sregex_iterator iter(tpl.begin(), tpl.end(), token_regex); std::sregex_iterator end; From 2f7922cb5031811c7a7f37b884c571b2f00a96a7 Mon Sep 17 00:00:00 2001 From: Henk Wiedig Date: Sun, 2 Aug 2026 20:39:59 +0200 Subject: [PATCH 2/3] optimize facts publish --- README.md | 7 ++++++ src/WiFiMonitor.cpp | 61 +++++++++++++++++++++++++++++++++++---------- src/WiFiMonitor.hpp | 6 +++++ 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 44b61253..e35ebeec 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,13 @@ MAC-derived interface name, which differs per ground station. Note that the inde when other adapters are plugged in or removed. Temperature is sampled every 10 seconds, because reading it makes the driver trigger an ADC conversion on the RF chip. +An adapter that is not associated (no drone yet, drone powered off) publishes `connected` = 0 and +`-1` for every rssi value, so a bar bound to it disappears instead of freezing at the last value it +saw. `-1` is outside the 0..100 percentage range that `IconSelectorWidget` configs cover, so it +hides by default; add a range for it (`{"range": [-1, 0], "icon_path": "no_signal.png"}`) to show an +explicit "no signal" icon instead. If an adapter is unplugged entirely, its facts are dropped back +to undefined, which hides the widget as well. + There are many facts based on Mavlink telemetry, see `mavlink.c`. All of them have tags "sysid" and "compid", but some have extra tags. Currently implemented fact categories are grouped by Mavlink message types: diff --git a/src/WiFiMonitor.cpp b/src/WiFiMonitor.cpp index 404281f8..18f90878 100644 --- a/src/WiFiMonitor.cpp +++ b/src/WiFiMonitor.cpp @@ -71,9 +71,39 @@ std::vector WiFiMonitor::find_interfaces() { return interfaces; } +/* Drop the whole os_mon.wifi.* namespace when the set of adapters changes. + * + * A card that disappears (unplugged, driver unloaded) simply stops being + * published for, and nothing else retracts its facts, so its widgets would keep + * rendering the last value it ever reported. Worse, the `adapter` tag is the + * position in the list, so adding or removing a card renumbers the ones after + * it and their widgets would show another card's readings until every one of + * them happens to publish again. + * + * The adapters that are still here re-publish in the same run() below, and the + * fact processor applies pending flushes before the facts of the same batch, so + * they don't get cleared by our own flush. */ +void WiFiMonitor::flush_if_interfaces_changed(const std::vector& interfaces) { + std::vector names; + names.reserve(interfaces.size()); + for (const auto& interface : interfaces) { + names.push_back(interface.filename()); + } + + if (names == known_interfaces_) { + return; + } + known_interfaces_ = std::move(names); + + static const char* const prefix = "os_mon.wifi."; + osd_flush_facts(&prefix, 1); +} + void WiFiMonitor::run() { std::vector interfaces = find_interfaces(); + flush_if_interfaces_changed(interfaces); + if (interfaces.empty()) { if (!warned_no_driver_) { spdlog::error("No Realtek WiFi driver found below {}, no wifi stats will be published", base_path_); @@ -204,21 +234,27 @@ void WiFiMonitor::make_base_tags(osd_tag* tags, const std::string& interface_nam } void WiFiMonitor::add_interface_stats_to_batch(void* batch, const osd_tag* base_tags, const WiFiStats& stats) { - if (!stats.is_linked) { - return; // Don't publish stats for disconnected interfaces - } + /* An unlinked adapter (no drone yet, drone powered off, adapter never brought + * up) has no RSSI to report. Going silent leaves the widgets showing the last + * RSSI of a link that is gone, so publish RSSI_NONE instead: it is outside + * every icon range, so IconSelectorWidget hides the bar, and a config that + * wants a visible "no signal" instead can just add a range covering it. + * Retracting per adapter, rather than with osd_flush_facts(), because that is + * scoped by fact-name prefix - it would take the other adapters and the + * temperature down with it. */ + const bool linked = stats.is_linked; // Publish RSSI A - add_rssi_fact_to_batch(batch, base_tags, "rssi_a", stats.rssi_a); + add_rssi_fact_to_batch(batch, base_tags, "rssi_a", linked ? stats.rssi_a : RSSI_NONE); // Publish RSSI B - add_rssi_fact_to_batch(batch, base_tags, "rssi_b", stats.rssi_b); + add_rssi_fact_to_batch(batch, base_tags, "rssi_b", linked ? stats.rssi_b : RSSI_NONE); // Publish RSSI Overall Percentage - add_rssi_fact_to_batch(batch, base_tags, "rssi_percent", stats.rssi_percent); + add_rssi_fact_to_batch(batch, base_tags, "rssi_percent", linked ? stats.rssi_percent : RSSI_NONE); // Publish connection status - add_rssi_fact_to_batch(batch, base_tags, "connected", 1); + add_rssi_fact_to_batch(batch, base_tags, "connected", linked ? 1 : 0); } void WiFiMonitor::add_rssi_fact_to_batch(void* batch, const osd_tag* base_tags, const std::string& rssi_type, int value) { @@ -273,18 +309,17 @@ void WiFiMonitor::publish_interface_reset(void* batch, const fs::path& interface osd_tag base_tags[BASE_TAGS]; make_base_tags(base_tags, interface_path.filename(), adapter); - // Publish all RSSI values as -1 (reset/error value) - add_rssi_fact_to_batch(batch, base_tags, "rssi_a", -1); - add_rssi_fact_to_batch(batch, base_tags, "rssi_b", -1); - add_rssi_fact_to_batch(batch, base_tags, "rssi_min", -1); - add_rssi_fact_to_batch(batch, base_tags, "rssi_percent", -1); + // Publish all RSSI values as "no link" + add_rssi_fact_to_batch(batch, base_tags, "rssi_a", RSSI_NONE); + add_rssi_fact_to_batch(batch, base_tags, "rssi_b", RSSI_NONE); + add_rssi_fact_to_batch(batch, base_tags, "rssi_percent", RSSI_NONE); add_rssi_fact_to_batch(batch, base_tags, "connected", 0); // 0 = disconnected // Same for the temperature of every RF path the card reports std::string thermal_file = interface_path / "thermal_state"; if (fs::exists(thermal_file)) { for (const auto& thermal : parse_thermal_state(thermal_file)) { - add_temperature_fact_to_batch(batch, base_tags, thermal.rf_path, -1); + add_temperature_fact_to_batch(batch, base_tags, thermal.rf_path, RSSI_NONE); } } } diff --git a/src/WiFiMonitor.hpp b/src/WiFiMonitor.hpp index 02e20bfd..7f9736cc 100644 --- a/src/WiFiMonitor.hpp +++ b/src/WiFiMonitor.hpp @@ -59,9 +59,14 @@ class WiFiMonitor { * temp_measurement_interval default. */ static constexpr std::chrono::seconds THERMAL_INTERVAL{10}; + /* Published for an adapter that is not linked. Deliberately outside the 0..100 + * percentage range, so an icon widget hides unless the config maps it. */ + static constexpr int RSSI_NONE = -1; + std::string base_path_; std::chrono::steady_clock::time_point last_thermal_read_; bool warned_no_driver_; + std::vector known_interfaces_; // adapters seen on the previous run() /* Number of tags every fact carries: `interface` and `adapter`, plus the one * describing the value itself (`type` / `rf_path`). Kept identical for all @@ -73,6 +78,7 @@ class WiFiMonitor { * by interface name so the adapter index of a card doesn't depend on readdir * order (it does change when adapters are added or removed, though). */ std::vector find_interfaces(); + void flush_if_interfaces_changed(const std::vector& interfaces); WiFiStats parse_interface_stats(const std::string& file_path); std::vector parse_thermal_state(const std::string& file_path); void make_base_tags(osd_tag* tags, const std::string& interface_name, int adapter); From 3915322eb8ebc1f967af4eea45301243fb7c2a80 Mon Sep 17 00:00:00 2001 From: Henk Wiedig Date: Mon, 3 Aug 2026 15:08:16 +0200 Subject: [PATCH 3/3] drop dead code --- src/WiFiMonitor.cpp | 51 -------------------------------------- src/WiFiMonitor.h | 12 --------- src/WiFiMonitor.hpp | 2 -- src/gsmenu/colmenu_pages.c | 6 ++--- src/gsmenu/helper.c | 23 ----------------- src/gsmenu/helper.h | 1 - 6 files changed, 2 insertions(+), 93 deletions(-) delete mode 100644 src/WiFiMonitor.h diff --git a/src/WiFiMonitor.cpp b/src/WiFiMonitor.cpp index 18f90878..0922c986 100644 --- a/src/WiFiMonitor.cpp +++ b/src/WiFiMonitor.cpp @@ -282,54 +282,3 @@ void WiFiMonitor::add_temperature_fact_to_batch(void* batch, const osd_tag* base // Add fact to batch, degrees C osd_add_int_fact(batch, "os_mon.wifi.temperature", tags, N_TAGS, value); } - -void WiFiMonitor::publish_reset() { - std::vector interfaces = find_interfaces(); - if (interfaces.empty()) { - spdlog::warn("No Realtek WiFi driver found below {} for reset", base_path_); - return; - } - - // Initialize batch - estimate 6 facts per interface - void* batch = osd_batch_init(24); - - // Publish reset values for all interfaces of all drivers - for (size_t adapter = 0; adapter < interfaces.size(); adapter++) { - publish_interface_reset(batch, interfaces[adapter], (int)adapter); - } - - // Publish all reset facts - osd_publish_batch(batch); - - spdlog::debug("Published WiFi reset values for all interfaces"); -} - -void WiFiMonitor::publish_interface_reset(void* batch, const fs::path& interface_path, int adapter) { - // Prepare common tags - osd_tag base_tags[BASE_TAGS]; - make_base_tags(base_tags, interface_path.filename(), adapter); - - // Publish all RSSI values as "no link" - add_rssi_fact_to_batch(batch, base_tags, "rssi_a", RSSI_NONE); - add_rssi_fact_to_batch(batch, base_tags, "rssi_b", RSSI_NONE); - add_rssi_fact_to_batch(batch, base_tags, "rssi_percent", RSSI_NONE); - add_rssi_fact_to_batch(batch, base_tags, "connected", 0); // 0 = disconnected - - // Same for the temperature of every RF path the card reports - std::string thermal_file = interface_path / "thermal_state"; - if (fs::exists(thermal_file)) { - for (const auto& thermal : parse_thermal_state(thermal_file)) { - add_temperature_fact_to_batch(batch, base_tags, thermal.rf_path, RSSI_NONE); - } - } -} - -// C-callable function implementations -extern "C" { - -void wifi_monitor_reset(void) { - static WiFiMonitor monitor; - monitor.publish_reset(); -} - -} // extern "C" diff --git a/src/WiFiMonitor.h b/src/WiFiMonitor.h deleted file mode 100644 index 3dbb8f8f..00000000 --- a/src/WiFiMonitor.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -// C-callable functions -#ifdef __cplusplus -extern "C" { -#endif - -void wifi_monitor_reset(void); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/src/WiFiMonitor.hpp b/src/WiFiMonitor.hpp index 7f9736cc..e55c62e5 100644 --- a/src/WiFiMonitor.hpp +++ b/src/WiFiMonitor.hpp @@ -35,7 +35,6 @@ class WiFiMonitor { public: WiFiMonitor(); void run(); - void publish_reset(); private: struct WiFiStats { @@ -85,7 +84,6 @@ class WiFiMonitor { void add_interface_stats_to_batch(void* batch, const osd_tag* base_tags, const WiFiStats& stats); void add_rssi_fact_to_batch(void* batch, const osd_tag* base_tags, const std::string& rssi_type, int value); void add_temperature_fact_to_batch(void* batch, const osd_tag* base_tags, int rf_path, int value); - void publish_interface_reset(void* batch, const std::filesystem::path& interface_path, int adapter); }; diff --git a/src/gsmenu/colmenu_pages.c b/src/gsmenu/colmenu_pages.c index 033ec4ad..232adc46 100644 --- a/src/gsmenu/colmenu_pages.c +++ b/src/gsmenu/colmenu_pages.c @@ -175,8 +175,7 @@ static void notify_restart(const char * v) { (void)v; show_restart_notice(); } /* Apply the receiver mode: set RXMODE and the env vars the rest of the app reads * (REMOTE_IP / AIR_FIRMWARE_TYPE). Called both at startup and on a mode change, so - * the env is always in sync with the actual mode (the old gsmenu_toggle_rxmode() - * set these from menu creation onward). */ + * the env is always in sync with the actual mode. */ static void apply_rx_mode(bool apfpv) { bool changed = ((RXMODE == APFPV) != apfpv); @@ -211,8 +210,7 @@ static bool mode_is_apfpv(void) } /* Switching RX mode: apply the env the rest of the app expects and rebuild the - * menu so the mode-specific pages appear. (Not the old gsmenu_toggle_rxmode(), - * which pokes lv_menu objects that don't exist here.) */ + * menu so the mode-specific pages appear. */ static void on_rx_mode_change(const char * value) { bool apfpv = value && strcmp(value, "apfpv") == 0; diff --git a/src/gsmenu/helper.c b/src/gsmenu/helper.c index 921f9289..9be04b1a 100644 --- a/src/gsmenu/helper.c +++ b/src/gsmenu/helper.c @@ -2,10 +2,8 @@ #include #include #include "../../lvgl/lvgl.h" -#include "../menu.h" /* enum RXMode, RXMODE */ #include "helper.h" #include "styles.h" -#include "../WiFiMonitor.h" extern lv_indev_t * indev_drv; @@ -60,27 +58,6 @@ lv_obj_t * find_first_focusable_obj(lv_obj_t * parent) { return NULL; } -/* Apply the environment for the current RX mode (WFB vs APFPV). */ -void gsmenu_toggle_rxmode() { - switch (RXMODE) - { - case APFPV: - setenv("REMOTE_IP" , "192.168.0.1", 1); - setenv("AIR_FIRMWARE_TYPE" , "apfpv", 1); - break; - case WFB: - setenv("REMOTE_IP" , "10.5.0.10", 1); - setenv("AIR_FIRMWARE_TYPE" , "wfb", 1); -#ifndef USE_SIMULATOR - wifi_monitor_reset(); -#endif - break; - - default: - break; - } -} - typedef struct { lv_obj_t *mbox; lv_group_t *prev_group; diff --git a/src/gsmenu/helper.h b/src/gsmenu/helper.h index 6ae23920..2c208dfb 100644 --- a/src/gsmenu/helper.h +++ b/src/gsmenu/helper.h @@ -3,7 +3,6 @@ lv_obj_t * find_first_focusable_obj(lv_obj_t * parent); const char* find_resource_file(const char* relative_path); -void gsmenu_toggle_rxmode(void); void show_restart_notice(void); /* Apply the dark gsmenu theme to a msgbox (body, header, footer, content).