diff --git a/CMakeLists.txt b/CMakeLists.txt index e1c9d0d..98c1441 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,14 +51,20 @@ set(SIMULATOR_SOURCES src/gsmenu/gs_dvrplayer.c src/gsmenu/gs_wfbng.h src/gsmenu/gs_wfbng.c + src/gsmenu/gs_apfpv.h + src/gsmenu/gs_apfpv.c src/gsmenu/gs_system.h src/gsmenu/gs_system.c src/gsmenu/gs_actions.h - src/gsmenu/gs_actions.c + src/gsmenu/gs_actions.c + src/gsmenu/gs_connection_checker.h + src/gsmenu/gs_connection_checker.c src/gsmenu/air_wfbng.h src/gsmenu/air_wfbng.c src/gsmenu/air_alink.h src/gsmenu/air_alink.c + src/gsmenu/air_aalink.h + src/gsmenu/air_aalink.c src/gsmenu/ui.c src/gsmenu/styles.h src/gsmenu/styles.c @@ -100,14 +106,20 @@ set(SOURCE_FILES src/gsmenu/gs_dvrplayer.c src/gsmenu/gs_wfbng.h src/gsmenu/gs_wfbng.c + src/gsmenu/gs_apfpv.h + src/gsmenu/gs_apfpv.c src/gsmenu/gs_system.h src/gsmenu/gs_system.c src/gsmenu/air_wfbng.h src/gsmenu/air_wfbng.c src/gsmenu/air_alink.h src/gsmenu/air_alink.c + src/gsmenu/air_aalink.h + src/gsmenu/air_aalink.c src/gsmenu/gs_actions.h - src/gsmenu/gs_actions.c + src/gsmenu/gs_actions.c + src/gsmenu/gs_connection_checker.h + src/gsmenu/gs_connection_checker.c src/gsmenu/styles.h src/gsmenu/styles.c src/gsmenu/ui.c @@ -130,6 +142,8 @@ set(SOURCE_FILES src/main.h src/wfbcli.hpp src/wfbcli.cpp + src/WiFiRSSIMonitor.hpp + src/WiFiRSSIMonitor.cpp src/scheduling_helper.hpp src/gstrtpreceiver.cpp src/gstrtpreceiver.h) diff --git a/README.md b/README.md index 04bae10..21ad10f 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,7 @@ 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 | There are many facts based on Mavlink telemetry, see `mavlink.c`. All of them have tags "sysid" and "compid", but some have extra tags. @@ -356,7 +357,7 @@ cmake -B build sudo cmake --build build --target install curl -L -o /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.45.4/yq_linux_arm64 chmod +x /usr/local/bin/yq -sudo apt install drm-info jq +sudo apt install drm-info jq netcat ``` ### Navigation diff --git a/gsmenu.sh b/gsmenu.sh index b3e0ace..3edc8d0 100755 --- a/gsmenu.sh +++ b/gsmenu.sh @@ -2,13 +2,15 @@ set -o pipefail # Configuration -REMOTE_IP="10.5.0.10" +REMOTE_IP="${REMOTE_IP:-10.5.0.10}" +AIR_FIRMWARE_TYPE="${AIR_FIRMWARE_TYPE:-wfb}" SSH_PASS="12345" CACHE_DIR="/tmp/gsmenu_cache" CACHE_TTL=10 # seconds MAJESTIC_YAML="/etc/majestic.yaml" WFB_YAML="/etc/wfb.yaml" ALINK_CONF="/etc/alink.conf" +AALINK_CONF="/etc/aalink.conf" TXPROFILES_CONF="/etc/txprofiles.conf" PRESET_DIR="/etc/presets" @@ -27,7 +29,7 @@ refresh_cache() { # Check if we need to refresh if [[ ! -f "$CACHE_DIR/last_refresh" ]] || [[ $(cat "$CACHE_DIR/last_refresh") -lt $last_refresh ]]; then # Copy the YAML configuration files - $SCP root@$REMOTE_IP:$MAJESTIC_YAML root@$REMOTE_IP:$WFB_YAML root@$REMOTE_IP:$ALINK_CONF root@$REMOTE_IP:$TXPROFILES_CONF $CACHE_DIR 2>/dev/null + $SCP root@$REMOTE_IP:$MAJESTIC_YAML root@$REMOTE_IP:$WFB_YAML root@$REMOTE_IP:$ALINK_CONF root@$REMOTE_IP:$TXPROFILES_CONF root@$REMOTE_IP:$AALINK_CONF $CACHE_DIR 2>/dev/null # Update refresh timestamp echo "$current_time" > "$CACHE_DIR/last_refresh" @@ -52,6 +54,12 @@ get_alink_value() { grep $key= "$CACHE_DIR/alink.conf" | cut -d "=" -f 2 2>/dev/null } +# Function to get value from alink.conf +get_aalink_value() { + local key="$1" + grep ^$key= "$CACHE_DIR/aalink.conf" | cut -d "=" -f 2 2>/dev/null +} + # Refresh cache for get case "$@" in "get air"*) @@ -157,9 +165,33 @@ case "$@" in "values air alink multiply_font_size_by") echo -n 0 1.5 ;; + "values air aalink channel") + echo -n -e "36\n40\n44\n48\n52\n56\n60\n64\n100\n104\n108\n112\n116\n120\n124\n128\n132\n136\n140\n144\n149\n153\n157\n161\n165\n36_40\n44_48\n52_56\n60_64\n100_104\n108_112\n116_120\n124_128\n132_136\n140_144\n149_153\n157_161" + ;; + "values air aalink SCALE_TX_POWER") + echo -n 0.2 1.2 + ;; + "values air aalink THRESH_SHIFT") + echo -n -50 50 + ;; + "values air aalink OSD_SCALE") + echo -n 0.2 2 + ;; + "values air aalink THROUGHPUT_PCT") + echo -n 0 100 + ;; + "values air aalink HIGH_TEMP") + echo -n 70 100 + ;; + "values air aalink MCS_SOURCE") + echo -n -e "lowest\ndownlink" + ;; "values air camera size") echo -n -e "1280x720\n1456x816\n1920x1080\n1440x1080\n1920x1440\n2104x1184\n2208x1248\n2240x1264\n2312x1304\n2436x1828\n2512x1416\n2560x1440\n2560x1920\n2720x1528\n2944x1656\n3200x1800\n3840x2160" ;; + "values air camera video_mode") + echo -ne "16:9 720p 30\n\n16:9 720p 30 50HzAC\n16:9 1080p 30\n16:9 1080p 30 50HzAC\n16:9 1440p 30\n16:9 1440p 30 50HzAC\n16:9 4k 2160p 30\n16:9 4k 2160p 30 50HzAC\n16:9 540p 60\n16:9 540p 60 50HzAC\n16:9 720p 60\n16:9 720p 60 50HzAC\n16:9 1080p 60\n16:9 1080p 60 50HzAC\n16:9 1440p 60\n16:9 1440p 60 50HzAC\n16:9 1688p 60\n16:9 1688p 60 50HzAC\n16:9 540p 90\n16:9 540p 90 50HzAC\n16:9 720p 90\n16:9 720p 90 50HzAC\n16:9 1080p 90\n16:9 1080p 90 50HzAC\n16:9 540p 120\n16:9 720p 120\n16:9 816p 120\n4:3 720p 30\n4:3 720p 30 50HzAC\n4:3 960p 30\n4:3 960p 30 50HzAC\n4:3 1080p 30\n4:3 1080p 30 50HzAC\n4:3 1440p 30\n4:3 1440p 30 50HzAC\n4:3 2160p 30\n4:3 2160p 30 50HzAC\n4:3 720p 60\n4:3 720p 60 50HzAC\n4:3 960p 60\n4:3 960p 60 50HzAC\n4:3 1080p 60\n4:3 1080p 60 50HzAC\n4:3 1440p 60\n4:3 1440p 60 50HzAC\n4:3 1688p 60\n4:3 1688p 60 50HzAC\n4:3 720p 90\n4:3 720p 90 50HzAC\n4:3 960p 90\n4:3 960p 90 50HzAC\n4:3 1080p 90\n4:3 1080p 90 50HzAC\n4:3 540p 120\n4:3 720p 120\n4:3 816p 120" + ;; "values air camera fps") echo -n -e "60\n90\n120" ;; @@ -232,6 +264,9 @@ case "$@" in "get air camera size") get_majestic_value '.video0.size' ;; + "get air camera video_mode") + echo get_current_video_mode | nc -w 11 $REMOTE_IP 12355 + ;; "get air camera fps") get_majestic_value '.video0.fps' ;; @@ -354,6 +389,9 @@ case "$@" in "set air camera size"*) $SSH "cli -s .video0.size $5 && killall -1 majestic" ;; + "set air camera video_mode"*) + echo set_simple_video_mode "$5" | nc -w 11 $REMOTE_IP 12355 + ;; "set air camera fps"*) $SSH "cli -s .video0.fps $5 && killall -1 majestic" ;; @@ -405,7 +443,19 @@ case "$@" in ;; "get air telemetry serial") - $SSH wifibroadcast cli -g .telemetry.serial + if [ $AIR_FIRMWARE_TYPE = "wfb" ] + then + $SSH wifibroadcast cli -g .telemetry.serial + elif [ $AIR_FIRMWARE_TYPE = "apfpv" ] + then + tty=$($SSH "fw_printenv -n msposd_tty") + if [ ! -z $tty ] + then + basename "$tty" + else + echo ttyS2 + fi + fi ;; "get air telemetry router") $SSH wifibroadcast cli -g .telemetry.router @@ -424,8 +474,14 @@ case "$@" in else $SSH "sed -i 's/^#console::respawn:\/sbin\/getty -L console 0 vt100/console::respawn:\/sbin\/getty -L console 0 vt100/' /etc/inittab ; kill -HUP 1" fi - $SSH wifibroadcast cli -s .telemetry.serial $5 - $SSH "(wifibroadcast stop ;wifibroadcast stop; sleep 1; wifibroadcast start) >/dev/null 2>&1 &" + if [ $AIR_FIRMWARE_TYPE = "wfb" ] + then + $SSH wifibroadcast cli -s .telemetry.serial $5 + $SSH "(wifibroadcast stop ;wifibroadcast stop; sleep 1; wifibroadcast start) >/dev/null 2>&1 &" + elif [ $AIR_FIRMWARE_TYPE = "apfpv" ] + then + $SSH "fw_setenv msposd_tty /dev/$5; /etc/init.d/S99msposd stop ; /etc/init.d/S99msposd stop ; sleep 1; /etc/init.d/S99msposd start" + fi ;; "set air telemetry router"*) $SSH wifibroadcast cli -s .telemetry.router $5 @@ -537,10 +593,101 @@ case "$@" in fi ;; + "get gs apfpv ssid") + nmcli c show apfpv0 | grep "802-11-wireless.ssid" | cut -d : -f2 | awk ' {print $1}' + ;; + "get gs apfpv password") + nmcli -t connection show apfpv0 --show-secrets | grep 802-11-wireless-security.psk: | cut -d : -f2 + ;; + "get gs apfpv wlx"*) + grep -q autoconnect=false $(grep -l $4 /etc/NetworkManager/system-connections/apfpv*.nmconnection) && echo 0 || echo 1 + ;; + "get gs apfpv status wlx"*) + nmcli -t device status | grep $5 | grep -q :connected: && echo Connected || echo Disconnected + ;; + "set gs apfpv ssid"*) + if [ "$GSMENU_VTX_DETECTED" -eq "1" ]; then + $SSH 'fw_setenv wlanssid "'$5'"' + $SSH '(hostapd_cli -i wlan0 set ssid "'$5'"; hostapd_cli -i wlan0 reload) >/dev/null 2>&1 &' + fi + WIFI_IFACES=$(ip -o link show | awk -F': ' '{print $2}' | grep '^wlx' | grep -v "^$EXCLUDE_IFACE$") + INDEX=0 + for IFACE in $WIFI_IFACES; do + CONN_NAME="apfpv$INDEX" + if nmcli connection show "$CONN_NAME" &>/dev/null; then + nmcli connection modify "$CONN_NAME" ssid "$5" + if [ $($0 get gs apfpv $IFACE) = 1 ] + then + nmcli -w 0 connection up "$CONN_NAME" + fi + fi + INDEX=$((INDEX + 1)) + done + ;; + "set gs apfpv password"*) + if [ "$GSMENU_VTX_DETECTED" -eq "1" ]; then + $SSH 'fw_setenv wlanpass "'$5'"' + $SSH '(hostapd_cli -i wlan0 set wpa_passphrase "'$5'"; hostapd_cli -i wlan0 reload) >/dev/null 2>&1 &' + fi + WIFI_IFACES=$(ip -o link show | awk -F': ' '{print $2}' | grep '^wlx' | grep -v "^$EXCLUDE_IFACE$") + INDEX=0 + for IFACE in $WIFI_IFACES; do + CONN_NAME="apfpv$INDEX" + if nmcli connection show "$CONN_NAME" &>/dev/null; then + nmcli connection modify "$CONN_NAME" wifi-sec.psk "$5" + if [ $($0 get gs apfpv $IFACE) = 1 ] + then + nmcli -w 0 connection up "$CONN_NAME" + fi + fi + INDEX=$((INDEX + 1)) + done + ;; + + "set gs apfpv wlx"*) + conn=$(basename -s .nmconnection $(grep -l $4 /etc/NetworkManager/system-connections/apfpv*.nmconnection)) + if [ $5 = "on" ] + then + nmcli connection modify "$conn" connection.autoconnect yes + nmcli connection up "$conn" + else + nmcli connection modify "$conn" connection.autoconnect no + nmcli connection down "$conn" + DRV_PATH=$(readlink -f /sys/class/net/$4/device/driver 2>/dev/null || true) + DEV_PATH=$(readlink -f /sys/class/net/$4/device 2>/dev/null || true) + DRV_NAME=$(basename "$DRV_PATH") + DEV_NAME=$(basename "$DEV_PATH") + echo -n "$DEV_NAME" | sudo tee /sys/bus/usb/drivers/$DRV_NAME/unbind >/dev/null + sleep 1 + echo -n "$DEV_NAME" | sudo tee /sys/bus/usb/drivers/$DRV_NAME/bind >/dev/null + sleep 1 + fi + ;; + "set gs apfpv reset") + CONNECTIONS=$(nmcli -t c show | grep ^apfpv | cut -d : -f1) + for CONN_NAME in $CONNECTIONS; do + if nmcli connection show "$CONN_NAME" &>/dev/null; then + nmcli connection down "$CONN_NAME" + nmcli connection delete "$CONN_NAME" + fi + done + ;; "get air alink"*) get_alink_value $4 ;; + "get air aalink channel") + $SSH "fw_printenv -n wlanchan || echo 157" + ;; + + "get air aalink"*) + get_aalink_value $4 + ;; + + "set air aalink channel"*) + echo "set_ap_channel $5" | nc -w 11 $REMOTE_IP 12355 + ;; + "set air alink"*) if [ "$5" = "off" ] then @@ -557,6 +704,10 @@ case "$@" in fi ;; + "set air aalink"*) + $SSH 'sed -i "s/'$4'=.*/'$4'='$5'/" /etc/aalink.conf; kill -SIGHUP $(pidof aalink)' + ;; + "values gs wfbng gs_channel") iw list | grep MHz | grep -v disabled | grep -v "radar detection" | grep \* | tr -d '[]' | awk '{print $4 " (" $2 " " $3 ")"}' | grep '^[1-9]' | sort -n | uniq | sed -z '$ s/\n$//' ;; @@ -566,6 +717,9 @@ case "$@" in "values gs wfbng txpower") echo -n -e "1\n100" ;; + "values gs system rx_mode") + echo -n -e "wfb\napfpv" + ;; "values gs system resolution") drm_info -j /dev/dri/card0 2>/dev/null | jq -r '."/dev/dri/card0".connectors[1].modes[] | select(.name | contains("i") | not) | .name + "@" + (.vrefresh|tostring)' | sort | uniq | sed -z '$ s/\n$//' ;; @@ -573,6 +727,9 @@ case "$@" in echo -n -e "60\n90\n120" ;; + "get gs system rx_mode") + systemctl is-enabled --quiet wifibroadcast && echo wfb || echo apfpv + ;; "get gs system gs_rendering") [ "$(grep ^render /config/setup.txt | cut -d ' ' -f 3)" = "ground" ] && echo 1 || echo 0 ;; @@ -582,6 +739,67 @@ case "$@" in "get gs system rec_fps") grep ^rec_fps /config/setup.txt | cut -d ' ' -f 3 ;; + "set gs system rx_mode"*) + EXCLUDE_IFACE="wlan0" + SSID="${6:-OpenIPC}" + PASSWORD="${7:-12345678}" + if [ "$5" = "apfpv" ] + then + systemctl stop alink_gs.service + systemctl stop wifibroadcast.service + systemctl stop wifibroadcast@gs.service + systemctl disable wifibroadcast.service + systemctl disable wifibroadcast@gs.service + systemctl disable alink_gs.service + rmmod 8812eu + rmmod 88XXau_wfb + modprobe 8812eu + modprobe 88XXau_wfb + # list every wifi interface wlx or wlan expect wlan0 + WIFI_IFACES=$(ip -o link show | awk -F': ' '{print $2}' | grep '^wlx' | grep -v "^$EXCLUDE_IFACE$") + INDEX=0 + for IFACE in $WIFI_IFACES; do + nmcli device set $IFACE managed yes + CONN_NAME="apfpv$INDEX" + if nmcli connection show "$CONN_NAME" &>/dev/null; then + nmcli connection modify "$CONN_NAME" connection.autoconnect $([ "$INDEX" -eq 0 ] && echo "yes" || echo "no") + else + nmcli device wifi rescan ifname "$IFACE" + sleep 2 + nmcli connection add type wifi ifname "$IFACE" con-name "$CONN_NAME" ssid "$SSID" \ + wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$PASSWORD" \ + ipv4.method auto connection.autoconnect $([ "$INDEX" -eq 0 ] && echo "yes" || echo "no") + fi + nmcli connection modify "$CONN_NAME" ipv4.route-metric $((100 * (INDEX + 1))) + INDEX=$((INDEX + 1)) + done + ln -s /usr/local/bin/gsmenu.sh /etc/NetworkManager/dispatcher.d/ + nmcli -w 0 connection up apfpv0 + elif [ "$5" = "wfb" ] + then + rm /etc/NetworkManager/dispatcher.d/gsmenu.sh + WIFI_IFACES=$(ip -o link show | awk -F': ' '{print $2}' | grep -E '^wlx' | grep -v "^$EXCLUDE_IFACE$") + INDEX=0 + for IFACE in $WIFI_IFACES; do + CONN_NAME="apfpv$INDEX" + if nmcli connection show "$CONN_NAME" &>/dev/null; then + nmcli connection modify "$CONN_NAME" connection.autoconnect no + nmcli connection down "$IFACE" + fi + INDEX=$((INDEX + 1)) + done + rmmod 8812eu + rmmod 88XXau_wfb + modprobe 8812eu + modprobe 88XXau_wfb + systemctl start wifibroadcast.service + systemctl start wifibroadcast@gs.service + systemctl start alink_gs.service + systemctl enable wifibroadcast.service + systemctl enable wifibroadcast@gs.service + systemctl enable alink_gs.service + fi + ;; "set gs system gs_rendering"*) if [ "$5" = "off" ] then @@ -789,6 +1007,19 @@ case "$@" in reboot ;; + "wlx"*"dhcp4-change") + eval $(udevadm info -x --query=property --path=/sys/class/net/$DEVICE_IFACE) + case "$ID_NET_DRIVER" in + "rtl88xxau_wfb") + iw dev "$DEVICE_IFACE" set txpower fixed -4000 + ;; + + "rtl88x2eu") + iw dev "$DEVICE_IFACE" set txpower fixed 2500 + ;; + esac + + ;; *) echo "Unknown $@" exit 1 diff --git a/src/WiFiRSSIMonitor.cpp b/src/WiFiRSSIMonitor.cpp new file mode 100644 index 0000000..eb5d380 --- /dev/null +++ b/src/WiFiRSSIMonitor.cpp @@ -0,0 +1,180 @@ +#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.h b/src/WiFiRSSIMonitor.h new file mode 100644 index 0000000..1f925ce --- /dev/null +++ b/src/WiFiRSSIMonitor.h @@ -0,0 +1,12 @@ +#pragma once + +// C-callable functions +#ifdef __cplusplus +extern "C" { +#endif + +void wifi_rssi_monitor_reset(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/WiFiRSSIMonitor.hpp b/src/WiFiRSSIMonitor.hpp new file mode 100644 index 0000000..4873f28 --- /dev/null +++ b/src/WiFiRSSIMonitor.hpp @@ -0,0 +1,36 @@ +#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/air_aalink.c b/src/gsmenu/air_aalink.c new file mode 100644 index 0000000..2df9f64 --- /dev/null +++ b/src/gsmenu/air_aalink.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include "executor.h" +#include "styles.h" +#include "lvgl/lvgl.h" +#include "helper.h" +#include "../input.h" + +extern lv_group_t * default_group; +extern lv_indev_t * indev_drv; +extern gsmenu_control_mode_t control_mode; + +lv_obj_t * ap_fpv_channel; +lv_obj_t * txPower; +lv_obj_t * mcsShift; +lv_obj_t * temp; +lv_obj_t * throughput; +lv_obj_t * osdscale; +lv_obj_t * mcssource; + +void create_air_aalink_menu(lv_obj_t * parent) { + + menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); + strcpy(menu_page_data->type, "air"); + strcpy(menu_page_data->page, "aalink"); + menu_page_data->page_load_callback = generic_page_load_callback; + menu_page_data->indev_group = lv_group_create(); + menu_page_data->entry_count = 0; + menu_page_data->page_entries = NULL; + lv_group_set_default(menu_page_data->indev_group); + lv_obj_set_user_data(parent,menu_page_data); + + lv_obj_t * cont; + lv_obj_t * section; + + create_text(parent, NULL, "WLAN Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + section = lv_menu_section_create(parent); + lv_obj_add_style(section, &style_openipc_section, 0); + cont = lv_menu_cont_create(section); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + + ap_fpv_channel = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Channel","","channel",menu_page_data,false); + + create_text(parent, NULL, "AALink Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + section = lv_menu_section_create(parent); + lv_obj_add_style(section, &style_openipc_section, 0); + cont = lv_menu_cont_create(section); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + + txPower = create_slider(cont,LV_SYMBOL_SETTINGS,"VTX Power Output","SCALE_TX_POWER",menu_page_data,false,1); + mcsShift = create_slider(cont,LV_SYMBOL_SETTINGS,"Link resilience (dB)","THRESH_SHIFT",menu_page_data,false,0); + osdscale = create_slider(cont,LV_SYMBOL_SETTINGS,"OSD Size","OSD_SCALE",menu_page_data,false,1); + throughput = create_slider(cont,LV_SYMBOL_SETTINGS,"Maximum Throughput (%)","THROUGHPUT_PCT",menu_page_data,false,0); + temp = create_slider(cont,LV_SYMBOL_SETTINGS,"Temp Throttle Threshold (°C)","HIGH_TEMP",menu_page_data,false,0); + mcssource = create_dropdown(cont,LV_SYMBOL_SETTINGS, "LQ Consideration","","MCS_SOURCE",menu_page_data,false); + + + add_entry_to_menu_page(menu_page_data,"Loading Channel ...", ap_fpv_channel, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading VTX Power Output ...", txPower, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading Link resilience (dB) ...", mcsShift, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading OSD Size ...", osdscale, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading Maximum Throughput ...", throughput, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading Temp Throttle Threshold (°C)", temp, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading LQ Consideration ...", mcssource, reload_dropdown_value); + + lv_group_set_default(default_group); +} \ No newline at end of file diff --git a/src/gsmenu/air_aalink.h b/src/gsmenu/air_aalink.h new file mode 100644 index 0000000..cfca1cf --- /dev/null +++ b/src/gsmenu/air_aalink.h @@ -0,0 +1,3 @@ +#pragma once + +void create_air_aalink_menu(lv_obj_t * parent); \ No newline at end of file diff --git a/src/gsmenu/air_alink.c b/src/gsmenu/air_alink.c index 54af8d6..4ac941e 100644 --- a/src/gsmenu/air_alink.c +++ b/src/gsmenu/air_alink.c @@ -15,7 +15,6 @@ extern lv_obj_t * txprofiles_screen; extern lv_group_t * tx_profile_group; lv_obj_t * txprofiles; -#define ENTRIES 16 // # set desired power output (0 pitmode, 4 highest power)(scales with MCS) lv_obj_t * power_level_0_to_4; // ### if gs heartbeat lost for x ms, set link low (fallback) default 1000 @@ -76,12 +75,13 @@ static void txprofiles_callback(lv_event_t * e) void create_air_alink_menu(lv_obj_t * parent) { - menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t) + sizeof(PageEntry) * ENTRIES); + menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); strcpy(menu_page_data->type, "air"); strcpy(menu_page_data->page, "alink"); menu_page_data->page_load_callback = generic_page_load_callback; menu_page_data->indev_group = lv_group_create(); - menu_page_data->entry_count = ENTRIES; + menu_page_data->entry_count = 0; + menu_page_data->page_entries = NULL; lv_group_set_default(menu_page_data->indev_group); lv_obj_set_user_data(parent,menu_page_data); @@ -113,25 +113,22 @@ void create_air_alink_menu(lv_obj_t * parent) { osd_level = create_dropdown(cont,LV_SYMBOL_SETTINGS, "OSD Details 4 = all on one line. 6 = all on multiple lines","","osd_level",menu_page_data,false); multiply_font_size_by = create_slider(cont,LV_SYMBOL_SETTINGS, "Font Size","multiply_font_size_by",menu_page_data,false,1); - PageEntry entries[] = { - { "Loading power_level_0_to_4 ...", power_level_0_to_4, reload_dropdown_value}, - { "Loading fallback_ms ...", fallback_ms, reload_slider_value}, - { "Loading hold_fallback_mode_s ...", hold_fallback_mode_s, reload_slider_value}, - { "Loading min_between_changes_ms ...", min_between_changes_ms, reload_slider_value}, - { "Loading hold_modes_down_s ...", hold_modes_down_s, reload_slider_value}, - { "Loading hysteresis_percent ...", hysteresis_percent, reload_slider_value}, - { "Loading hysteresis_percent_down ...", hysteresis_percent_down, reload_slider_value}, - { "Loading exp_smoothing_factor ...", exp_smoothing_factor, reload_slider_value}, - { "Loading exp_smoothing_factor_down ...", exp_smoothing_factor_down, reload_slider_value}, - { "Loading allow_request_keyframe ...", allow_request_keyframe, reload_switch_value}, - { "Loading allow_rq_kf_by_tx_d ...", allow_rq_kf_by_tx_d, reload_switch_value}, - { "Loading check_xtx_period_ms ...", check_xtx_period_ms, reload_slider_value}, - { "Loading request_keyframe_interval_ms ...", request_keyframe_interval_ms, reload_slider_value}, - { "Loading idr_every_change ...", idr_every_change, reload_switch_value}, - { "Loading osd_level ...", osd_level, reload_dropdown_value}, - { "Loading multiply_font_size_by ...", multiply_font_size_by, reload_slider_value}, - }; - memcpy(menu_page_data->page_entries, entries, sizeof(entries)); + add_entry_to_menu_page(menu_page_data,"Loading power_level_0_to_4 ...", power_level_0_to_4, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading fallback_ms ...", fallback_ms, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading hold_fallback_mode_s ...", hold_fallback_mode_s, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading min_between_changes_ms ...", min_between_changes_ms, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading hold_modes_down_s ...", hold_modes_down_s, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading hysteresis_percent ...", hysteresis_percent, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading hysteresis_percent_down ...", hysteresis_percent_down, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading exp_smoothing_factor ...", exp_smoothing_factor, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading exp_smoothing_factor_down ...", exp_smoothing_factor_down, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading allow_request_keyframe ...", allow_request_keyframe, reload_switch_value); + add_entry_to_menu_page(menu_page_data,"Loading allow_rq_kf_by_tx_d ...", allow_rq_kf_by_tx_d, reload_switch_value); + add_entry_to_menu_page(menu_page_data,"Loading check_xtx_period_ms ...", check_xtx_period_ms, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading request_keyframe_interval_ms ...", request_keyframe_interval_ms, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading idr_every_change ...", idr_every_change, reload_switch_value); + add_entry_to_menu_page(menu_page_data,"Loading osd_level ...", osd_level, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading multiply_font_size_by ...", multiply_font_size_by, reload_slider_value); lv_group_set_default(default_group); } \ No newline at end of file diff --git a/src/gsmenu/air_camera.c b/src/gsmenu/air_camera.c index 17f890e..56eba7d 100644 --- a/src/gsmenu/air_camera.c +++ b/src/gsmenu/air_camera.c @@ -8,13 +8,13 @@ extern lv_group_t * default_group; -#define ENTRIES 20 lv_obj_t * mirror; lv_obj_t * flip; lv_obj_t * contrast; lv_obj_t * hue; lv_obj_t * saturation; lv_obj_t * luminace; +lv_obj_t * video_mode; lv_obj_t * size; lv_obj_t * fps; lv_obj_t * bitrate; @@ -46,12 +46,13 @@ void air_rec_fps_cb(lv_event_t *e) { void create_air_camera_menu(lv_obj_t * parent) { - menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t) + sizeof(PageEntry) * ENTRIES); + menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); strcpy(menu_page_data->type, "air"); strcpy(menu_page_data->page, "camera"); menu_page_data->page_load_callback = generic_page_load_callback; menu_page_data->indev_group = lv_group_create(); - menu_page_data->entry_count = ENTRIES; + menu_page_data->entry_count = 0; + menu_page_data->page_entries = NULL; lv_group_set_default(menu_page_data->indev_group); lv_obj_set_user_data(parent,menu_page_data); @@ -64,6 +65,7 @@ void create_air_camera_menu(lv_obj_t * parent) { cont = lv_menu_cont_create(section); lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); size = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Size","","size",menu_page_data,false); + video_mode = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Video Mode","","video_mode",menu_page_data,false); fps = create_dropdown(cont,LV_SYMBOL_SETTINGS, "FPS","","fps",menu_page_data,false); // change rec fps when changeing camera fps @@ -114,29 +116,27 @@ void create_air_camera_menu(lv_obj_t * parent) { noiselevel = create_slider(cont,LV_SYMBOL_SETTINGS,"Noiselevel","noiselevel",menu_page_data,false,0); - PageEntry entries[] = { - { "Loading mirror ...", mirror, reload_switch_value }, - { "Loading flip ...", flip, reload_switch_value }, - { "Loading contrast ...", contrast, reload_slider_value }, - { "Loading hue ...", hue, reload_slider_value }, - { "Loading saturation ...", saturation, reload_slider_value }, - { "Loading luminace ...", luminace, reload_slider_value }, - { "Loading size ...", size, reload_dropdown_value }, - { "Loading fps ...", fps, reload_dropdown_value }, - { "Loading bitrate ...", bitrate, reload_dropdown_value }, - { "Loading video_codec ...", video_codec, reload_dropdown_value }, - { "Loading gopsize ...", gopsize, reload_slider_value }, - { "Loading rc_mode ...", rc_mode, reload_dropdown_value }, - { "Loading rec_enable ...", rec_enable, reload_switch_value }, - { "Loading rec_split ...", rec_split, reload_slider_value }, - { "Loading rec_maxusage ...", rec_maxusage, reload_slider_value }, - { "Loading exposure ...", exposure, reload_slider_value }, - { "Loading antiflicker ...", antiflicker, reload_dropdown_value }, - { "Loading sensor_file ...", sensor_file, reload_dropdown_value }, - { "Loading fpv_enable ...", fpv_enable, reload_switch_value }, - { "Loading noiselevel ...", noiselevel, reload_slider_value } - }; - memcpy(menu_page_data->page_entries, entries, sizeof(entries)); + add_entry_to_menu_page(menu_page_data,"Loading mirror ...", mirror, reload_switch_value ); + add_entry_to_menu_page(menu_page_data,"Loading flip ...", flip, reload_switch_value ); + add_entry_to_menu_page(menu_page_data,"Loading contrast ...", contrast, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading hue ...", hue, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading saturation ...", saturation, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading luminace ...", luminace, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading size ...", size, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading video_mode ...", video_mode, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading fps ...", fps, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading bitrate ...", bitrate, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading video_codec ...", video_codec, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading gopsize ...", gopsize, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading rc_mode ...", rc_mode, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading rec_enable ...", rec_enable, reload_switch_value ); + add_entry_to_menu_page(menu_page_data,"Loading rec_split ...", rec_split, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading rec_maxusage ...", rec_maxusage, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading exposure ...", exposure, reload_slider_value ); + add_entry_to_menu_page(menu_page_data,"Loading antiflicker ...", antiflicker, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading sensor_file ...", sensor_file, reload_dropdown_value ); + add_entry_to_menu_page(menu_page_data,"Loading fpv_enable ...", fpv_enable, reload_switch_value ); + add_entry_to_menu_page(menu_page_data,"Loading noiselevel ...", noiselevel, reload_slider_value); lv_group_set_default(default_group); } \ No newline at end of file diff --git a/src/gsmenu/air_presets.c b/src/gsmenu/air_presets.c index b4a41dc..fdc5710 100644 --- a/src/gsmenu/air_presets.c +++ b/src/gsmenu/air_presets.c @@ -9,7 +9,6 @@ extern lv_group_t * default_group; -#define ENTRIES 1 lv_obj_t * preset; lv_obj_t * apply; lv_obj_t * update; @@ -56,12 +55,13 @@ void air_presets_page_load_callback(lv_obj_t *page) { void create_air_presets_menu(lv_obj_t * parent) { - menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t) + sizeof(PageEntry) * ENTRIES); + menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); strcpy(menu_page_data->type, "air"); strcpy(menu_page_data->page, "presets"); menu_page_data->page_load_callback = air_presets_page_load_callback; menu_page_data->indev_group = lv_group_create(); - menu_page_data->entry_count = ENTRIES; + menu_page_data->entry_count = 0; + menu_page_data->page_entries = NULL; lv_group_set_default(menu_page_data->indev_group); lv_obj_set_user_data(parent,menu_page_data); diff --git a/src/gsmenu/air_telemetry.c b/src/gsmenu/air_telemetry.c index 0ae2d39..d4946ef 100644 --- a/src/gsmenu/air_telemetry.c +++ b/src/gsmenu/air_telemetry.c @@ -15,6 +15,9 @@ lv_obj_t * router; lv_obj_t * osd_fps; lv_obj_t * air_gs_rendering; +lv_obj_t * air_telemetry_msposd_text; +lv_obj_t * air_telemetry_msposd_section; + void create_air_telemetry_menu(lv_obj_t * parent) { menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t) + sizeof(PageEntry) * ENTRIES); @@ -22,7 +25,8 @@ void create_air_telemetry_menu(lv_obj_t * parent) { strcpy(menu_page_data->page, "telemetry"); menu_page_data->page_load_callback = generic_page_load_callback; menu_page_data->indev_group = lv_group_create(); - menu_page_data->entry_count = ENTRIES; + menu_page_data->entry_count = 0; + menu_page_data->page_entries = NULL; lv_group_set_default(menu_page_data->indev_group); lv_obj_set_user_data(parent,menu_page_data); @@ -36,21 +40,18 @@ void create_air_telemetry_menu(lv_obj_t * parent) { serial = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Serial Port", "","serial",menu_page_data,false); router = create_dropdown(cont,LV_SYMBOL_SETTINGS,"Router","","router",menu_page_data,false); - create_text(parent, NULL, "MSPOSD Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); - section = lv_menu_section_create(parent); - lv_obj_add_style(section, &style_openipc_section, 0); - cont = lv_menu_cont_create(section); + air_telemetry_msposd_text = create_text(parent, NULL, "MSPOSD Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + air_telemetry_msposd_section = lv_menu_section_create(parent); + lv_obj_add_style(air_telemetry_msposd_section, &style_openipc_section, 0); + cont = lv_menu_cont_create(air_telemetry_msposd_section); lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); osd_fps = create_slider(cont,LV_SYMBOL_SETTINGS,"OSD FPS","osd_fps",menu_page_data,false,0); air_gs_rendering = create_switch(cont,LV_SYMBOL_SETTINGS,"GS Rendering","gs_rendering", menu_page_data,false); - PageEntry entries[] = { - { "Loading serial ...", serial, reload_dropdown_value }, - { "Loading router ...", router, reload_dropdown_value }, - { "Loading osd_fps ...", osd_fps, reload_slider_value }, - { "Loading air_gs_rendering ...", air_gs_rendering, reload_switch_value }, - }; - memcpy(menu_page_data->page_entries, entries, sizeof(entries)); + add_entry_to_menu_page(menu_page_data,"Loading serial ...", serial, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading router ...", router, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading osd_fps ...", osd_fps, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading air_gs_rendering ...", air_gs_rendering, reload_switch_value); lv_group_set_default(default_group); } \ No newline at end of file diff --git a/src/gsmenu/air_wfbng.c b/src/gsmenu/air_wfbng.c index e85b5d7..c7567f7 100644 --- a/src/gsmenu/air_wfbng.c +++ b/src/gsmenu/air_wfbng.c @@ -10,7 +10,6 @@ extern lv_group_t * default_group; -#define ENTRIES 10 lv_obj_t * driver_txpower_override; lv_obj_t * air_channel; lv_obj_t * air_bandwidth; @@ -24,12 +23,13 @@ lv_obj_t * air_adaptivelink; void create_air_wfbng_menu(lv_obj_t * parent) { - menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t) + sizeof(PageEntry) * ENTRIES); + menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); strcpy(menu_page_data->type, "air"); strcpy(menu_page_data->page, "wfbng"); menu_page_data->page_load_callback = generic_page_load_callback; menu_page_data->indev_group = lv_group_create(); - menu_page_data->entry_count = ENTRIES; + menu_page_data->entry_count = 0; + menu_page_data->page_entries = NULL; lv_group_set_default(menu_page_data->indev_group); lv_obj_set_user_data(parent,menu_page_data); @@ -57,19 +57,16 @@ void create_air_wfbng_menu(lv_obj_t * parent) { air_adaptivelink = create_switch(cont,LV_SYMBOL_SETTINGS,"Enabled","adaptivelink", menu_page_data,false); - PageEntry entries[] = { - { "Loading driver_txpower_override ...", driver_txpower_override, reload_dropdown_value }, - { "Loading air_channel ...", air_channel, reload_dropdown_value }, - { "Loading air_bandwidth ...", air_bandwidth, reload_dropdown_value }, - { "Loading mcs_index ...", mcs_index, reload_slider_value }, - { "Loading stbc ...", stbc, reload_switch_value }, - { "Loading ldpc ...", ldpc, reload_switch_value }, - { "Loading fec_k ...", fec_k, reload_slider_value }, - { "Loading fec_n ...", fec_n, reload_slider_value }, - { "Loading mlink ...", mlink, reload_dropdown_value }, - { "Loading air_adaptivelink ...", air_adaptivelink, reload_switch_value }, - }; - memcpy(menu_page_data->page_entries, entries, sizeof(entries)); - + add_entry_to_menu_page(menu_page_data,"Loading driver_txpower_override ...", driver_txpower_override, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading air_channel ...", air_channel, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading air_bandwidth ...", air_bandwidth, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading mcs_index ...", mcs_index, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading stbc ...", stbc, reload_switch_value); + add_entry_to_menu_page(menu_page_data,"Loading ldpc ...", ldpc, reload_switch_value); + add_entry_to_menu_page(menu_page_data,"Loading fec_k ...", fec_k, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading fec_n ...", fec_n, reload_slider_value); + add_entry_to_menu_page(menu_page_data,"Loading mlink ...", mlink, reload_dropdown_value); + add_entry_to_menu_page(menu_page_data,"Loading air_adaptivelink ...", air_adaptivelink, reload_switch_value); + lv_group_set_default(default_group); } diff --git a/src/gsmenu/executor.c b/src/gsmenu/executor.c index 59cbff2..17f8de7 100644 --- a/src/gsmenu/executor.c +++ b/src/gsmenu/executor.c @@ -275,6 +275,45 @@ void generic_switch_event_cb(lv_event_t * e) run_command_and_block(e,final_command,NULL); } +void generic_checkbox_event_cb(lv_event_t * e) +{ + lv_key_t key = lv_indev_get_key(indev_drv); + if (key == LV_KEY_HOME) { + printf("skipping change as user wants to go back"); + return; + } + lv_obj_t * target = lv_event_get_target(e); + thread_data_t * user_data = (thread_data_t *) lv_event_get_user_data(e); + char final_command[200] = "gsmenu.sh set "; + strcat(final_command,user_data->menu_page_data->type); + strcat(final_command," "); + strcat(final_command,user_data->menu_page_data->page); + strcat(final_command," "); + strcat(final_command,user_data->parameter); + strcat(final_command," "); + + if(lv_obj_has_state(target, LV_STATE_CHECKED)) { + strcat(final_command,"on"); + } else { + strcat(final_command,"off"); + } + + for(int i=0;iarguments[i]) { + if (lv_obj_check_type(user_data->arguments[i],&lv_checkbox_class)) { + strcat(final_command," \""); + strcat(final_command,lv_checkbox_get_text(user_data->arguments[i])); + strcat(final_command,"\""); + } + } + } + + if (user_data->blocking) + run_command(final_command); + else + run_command_and_block(e,final_command,user_data->callback_fn); +} + void generic_dropdown_event_cb(lv_event_t * e) { lv_obj_t * target = lv_event_get_target(e); @@ -293,6 +332,16 @@ void generic_dropdown_event_cb(lv_event_t * e) strcat(final_command,user_data->argument_string); strcat(final_command,"\""); + for(int i=0;iarguments[i]) { + if (lv_obj_check_type(user_data->arguments[i],&lv_textarea_class)) { + strcat(final_command," \""); + strcat(final_command,lv_textarea_get_text(user_data->arguments[i])); + strcat(final_command,"\""); + } + } + } + if (user_data->blocking) run_command(final_command); else diff --git a/src/gsmenu/executor.h b/src/gsmenu/executor.h index 92da91c..9b20672 100644 --- a/src/gsmenu/executor.h +++ b/src/gsmenu/executor.h @@ -25,5 +25,6 @@ typedef struct { char* run_command(const char* command); void run_command_and_block(lv_event_t* e,const char * command, callback_fn callback); void generic_switch_event_cb(lv_event_t * e); +void generic_checkbox_event_cb(lv_event_t * e); void generic_dropdown_event_cb(lv_event_t * e); void generic_slider_event_cb(lv_event_t * e); \ No newline at end of file diff --git a/src/gsmenu/gs_apfpv.c b/src/gsmenu/gs_apfpv.c new file mode 100644 index 0000000..4e4ab2e --- /dev/null +++ b/src/gsmenu/gs_apfpv.c @@ -0,0 +1,339 @@ +#include +#include +#include +#include +#include "ui.h" +#include "../input.h" +#include "helper.h" +#include "executor.h" +#include "styles.h" +#include "executor.h" +#include "gs_wifi.h" + +extern lv_obj_t * menu; +extern gsmenu_control_mode_t control_mode; +extern lv_group_t * default_group; + +extern lv_obj_t * rx_mode; + +lv_obj_t *this_page; +lv_obj_t * ap_fpv_ssid; +lv_obj_t * ap_fpv_password; +lv_obj_t * reset_apfpv; +lv_obj_t * autoconnect_cont; + +static int adapter_count = 0; + +#define MAX_DEVICES 32 +#define MAX_NAME_LENGTH 64 +#define MAX_PATH_LENGTH 256 + +typedef struct { + char device_name[MAX_NAME_LENGTH]; + char usb_port[MAX_NAME_LENGTH]; +} NetworkDevice; + +int get_usb_port_udev(const char* device_name, char* usb_port, size_t port_size) { + char cmd[256]; + snprintf(cmd, sizeof(cmd), + "udevadm info -q path /sys/class/net/%s 2>/dev/null | grep -o 'usb[0-9]*/.*' | head -1", + device_name); + + char* result = run_command(cmd); + if (result == NULL || strlen(result) == 0) { + free(result); + return -1; + } + + // Remove newline and extract relevant portion + result[strcspn(result, "\n")] = '\0'; + + // Find the USB port part (typically after usbX/) + char* usb_part = strstr(result, "usb"); + if (usb_part != NULL) { + char* port_part = strchr(usb_part, '/'); + if (port_part != NULL) { + port_part++; // Move past the slash + + // Copy up to next slash or end of string + char* next_slash = strchr(port_part, '/'); + if (next_slash != NULL) { + size_t len = next_slash - port_part; + if (len < port_size) { + strncpy(usb_port, port_part, len); + usb_port[len] = '\0'; + free(result); + return 0; + } + } else { + // No more slashes, use the remaining string + if (strlen(port_part) < port_size) { + strcpy(usb_port, port_part); + free(result); + return 0; + } + } + } + } + + free(result); + return -1; +} + +// Main function to get the list of network devices with USB ports +NetworkDevice* get_wireless_devices(int* count) { + NetworkDevice* devices = malloc(MAX_DEVICES * sizeof(NetworkDevice)); + if (devices == NULL) { + *count = 0; + return NULL; + } + + // Get wireless device names using ip command + char* ip_output = run_command("ip -o link show | awk -F': ' '{print $2}' | grep '^wlx'"); + if (ip_output == NULL) { + *count = 0; + free(devices); + return NULL; + } + + *count = 0; + char* line = ip_output; + char* next_line; + + while (*count < MAX_DEVICES && (next_line = strchr(line, '\n')) != NULL) { + // Extract device name + size_t name_len = next_line - line; + if (name_len > 0 && name_len < MAX_NAME_LENGTH) { + strncpy(devices[*count].device_name, line, name_len); + devices[*count].device_name[name_len] = '\0'; + + // Remove trailing whitespace + devices[*count].device_name[strcspn(devices[*count].device_name, " \t\n")] = '\0'; + + // Get USB port information + devices[*count].usb_port[0] = '\0'; + + // Try udev method first + get_usb_port_udev(devices[*count].device_name, + devices[*count].usb_port, + MAX_NAME_LENGTH); + + (*count)++; + } + + line = next_line + 1; + } + + free(ip_output); + return devices; +} + +// Example usage and cleanup function +void free_devices(NetworkDevice* devices) { + free(devices); +} + +void focus_label(lv_event_t* e) +{ + lv_obj_t* obj = lv_event_get_current_target(e); + lv_obj_t* label = lv_event_get_user_data(e); + lv_obj_scroll_to_view_recursive(label, LV_ANIM_ON); +} + +static void btn_event_cb(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * btn = lv_event_get_target(e); + lv_obj_t * kb = lv_event_get_user_data(e); + lv_obj_t * target_ta = lv_obj_get_user_data(btn); // Retrieve associated textarea + + if(code == LV_EVENT_CLICKED) { + if (target_ta) { + lv_keyboard_set_textarea(kb, target_ta); + lv_obj_remove_flag(kb, LV_OBJ_FLAG_HIDDEN); + lv_obj_scroll_to_view_recursive(target_ta, LV_ANIM_OFF); + lv_indev_wait_release(lv_event_get_param(e)); + lv_group_focus_obj(kb); + } + } +} + +static void change_textarea_value(lv_event_t *e ,lv_obj_t * ta) { + + thread_data_t * user_data = (thread_data_t*) lv_obj_get_user_data(ta); + char final_command[200] = "gsmenu.sh set "; + strcat(final_command,user_data->menu_page_data->type); + strcat(final_command," "); + strcat(final_command,user_data->menu_page_data->page); + strcat(final_command," "); + strcat(final_command,user_data->parameter); + strcat(final_command," "); + user_data->argument_string = strdup(lv_textarea_get_text(ta)); + strcat(final_command,"\""); + strcat(final_command,user_data->argument_string); + strcat(final_command,"\""); + + run_command_and_block(e,final_command,NULL); +} + +static void kb_event_cb(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * ta = lv_event_get_target(e); + lv_obj_t * kb = lv_event_get_user_data(e); + + if (code == LV_EVENT_FOCUSED) { + control_mode = GSMENU_CONTROL_MODE_KEYBOARD; + + } + else if (code == LV_EVENT_DEFOCUSED) + { + control_mode = GSMENU_CONTROL_MODE_NAV; + } + else if(code == LV_EVENT_READY || code == LV_EVENT_CANCEL) { + lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); + lv_indev_reset(NULL, ta); /*To forget the last clicked object to make it focusable again*/ + lv_group_focus_obj(lv_obj_get_child_by_type(lv_obj_get_parent(lv_keyboard_get_textarea(kb)),0,&lv_button_class)); + lv_obj_update_layout(lv_obj_get_parent(kb)); + } + + if(code == LV_EVENT_READY) { + change_textarea_value(e,lv_keyboard_get_textarea(kb)); + } +} + +void checkbox_event_cb(void) +{ + generic_page_load_callback(this_page); +} + +void apfpv_page_load_callback(lv_obj_t *page) { + + this_page = page; + menu_page_data_t *menu_page_data = lv_obj_get_user_data(page); + PageEntry *entries = menu_page_data->page_entries; + + if (adapter_count == 0 ) { + NetworkDevice* devices = get_wireless_devices(&adapter_count); + + if (devices == NULL) { + printf("Failed to get device list\n"); + } + + printf("Found %d wireless devices:\n", adapter_count); + for (int i = 0; i < adapter_count; i++) { + printf("Device: %s -> USB Port: %s\n", + devices[i].device_name, + devices[i].usb_port[0] ? devices[i].usb_port : "Unknown"); + lv_group_set_default(menu_page_data->indev_group); + + // Create dynamic strings + char checkbox_label[128]; + char loading_text[128]; + char param_text[128]; + + snprintf(checkbox_label, sizeof(checkbox_label), "Device: %s USB: %s", + devices[i].device_name, + devices[i].usb_port[0] ? devices[i].usb_port : "Unknown"); + + snprintf(loading_text, sizeof(loading_text), "Loading device %s...", + devices[i].device_name); + + snprintf(param_text, sizeof(param_text), "status %s", + devices[i].device_name); + + lv_obj_t * adapter = create_checkbox(autoconnect_cont, LV_SYMBOL_WIFI, checkbox_label, devices[i].device_name, menu_page_data, false); + thread_data_t* data = lv_obj_get_user_data(lv_obj_get_child_by_type(adapter,0,&lv_checkbox_class)); + data->callback_fn = checkbox_event_cb; + lv_obj_t * adapter_status = create_text(autoconnect_cont, LV_SYMBOL_SETTINGS, "Status", param_text, menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + + lv_obj_add_event_cb(lv_obj_get_child_by_type(adapter,0,&lv_checkbox_class), focus_label, LV_EVENT_FOCUSED, adapter_status); + + add_entry_to_menu_page(menu_page_data, loading_text, adapter, reload_checkbox_value); + add_entry_to_menu_page(menu_page_data, loading_text, adapter_status, reload_label_value); + + lv_group_set_default(default_group); + } + + free_devices(devices); + + } + + generic_page_load_callback(page); +} + +void gs_actions_reset_apfpv(lv_event_t * event) +{ + lv_textarea_set_text(lv_obj_get_child_by_type(ap_fpv_ssid,0,&lv_textarea_class),"OpenIPC"); + lv_textarea_set_text(lv_obj_get_child_by_type(ap_fpv_password,0,&lv_textarea_class),"12345678"); + run_command_and_block(event,"gsmenu.sh set gs apfpv reset",NULL); + lv_obj_send_event(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class),LV_EVENT_VALUE_CHANGED,NULL); + + int child_count = lv_obj_get_child_count(autoconnect_cont); + for (int i = child_count - 1; i >= 0; i--) { + lv_obj_t *child = lv_obj_get_child(autoconnect_cont, i); + int grand_child_count = lv_obj_get_child_count(child); + + for (int j = grand_child_count - 1; j >= 0; j--) { + lv_obj_t *grand_child = lv_obj_get_child(child, j); + thread_data_t* user_data = lv_obj_get_user_data(grand_child); + + if (user_data != NULL) { + delete_menu_page_entry_by_obj(user_data->menu_page_data,child); + } + } + lv_obj_del(child); + } + adapter_count = 0; +} + +void create_apfpv_menu(lv_obj_t * parent) { + + menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); + strcpy(menu_page_data->type, "gs"); + strcpy(menu_page_data->page, "apfpv"); + menu_page_data->page_load_callback = apfpv_page_load_callback; + menu_page_data->indev_group = lv_group_create(); + menu_page_data->entry_count = 0; + menu_page_data->page_entries = NULL; + lv_group_set_default(menu_page_data->indev_group); + lv_obj_set_user_data(parent,menu_page_data); + + create_text(parent, NULL, "Connection Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + lv_obj_t * section = lv_menu_section_create(parent); + lv_obj_add_style(section, &style_openipc_section, 0); + + lv_obj_t * cont = lv_menu_cont_create(section); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + + ap_fpv_ssid = create_textarea(cont, "", "SSID", "ssid", menu_page_data, false); + ap_fpv_password = create_textarea(cont, "", "Password", "password", menu_page_data, true); + + reset_apfpv = create_button(section, "Reset APFPV"); + lv_obj_add_event_cb(lv_obj_get_child_by_type(reset_apfpv,0,&lv_button_class),gs_actions_reset_apfpv,LV_EVENT_CLICKED,NULL); + + lv_obj_t * kb = lv_keyboard_create(lv_obj_get_parent(section)); + lv_obj_add_flag(kb, LV_OBJ_FLAG_SCROLL_ON_FOCUS); + lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_style(kb, &style_openipc_outline, LV_PART_MAIN | LV_STATE_FOCUS_KEY); + lv_obj_add_style(kb, &style_openipc, LV_PART_ITEMS| LV_STATE_FOCUS_KEY); + lv_obj_add_style(kb, &style_openipc_dark_background, LV_PART_ITEMS| LV_STATE_DEFAULT); + lv_obj_add_style(kb, &style_openipc_textcolor, LV_PART_ITEMS| LV_STATE_FOCUS_KEY); + lv_obj_add_style(kb, &style_openipc_lightdark_background, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_add_event_cb(lv_obj_get_child_by_type(ap_fpv_ssid,0,&lv_button_class), btn_event_cb, LV_EVENT_ALL, kb); + lv_obj_add_event_cb(lv_obj_get_child_by_type(ap_fpv_password,0,&lv_button_class), btn_event_cb, LV_EVENT_ALL, kb); + lv_obj_add_event_cb(kb, kb_event_cb, LV_EVENT_ALL,kb); + lv_keyboard_set_textarea(kb, NULL); + + create_text(parent, NULL, "Autoconnect", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + section = lv_menu_section_create(parent); + lv_obj_add_style(section, &style_openipc_section, 0); + autoconnect_cont = lv_menu_cont_create(section); + lv_obj_set_flex_flow(autoconnect_cont, LV_FLEX_FLOW_COLUMN); + + add_entry_to_menu_page(menu_page_data,"Loading SSID ...", ap_fpv_ssid, reload_textarea_value ); + add_entry_to_menu_page(menu_page_data,"Loading Password ...", ap_fpv_password, reload_textarea_value ); + + lv_group_set_default(default_group); +} diff --git a/src/gsmenu/gs_apfpv.h b/src/gsmenu/gs_apfpv.h new file mode 100644 index 0000000..d5d20bf --- /dev/null +++ b/src/gsmenu/gs_apfpv.h @@ -0,0 +1,3 @@ +#pragma once + +void create_apfpv_menu(lv_obj_t * parent); \ No newline at end of file diff --git a/src/gsmenu/gs_connection_checker.c b/src/gsmenu/gs_connection_checker.c new file mode 100644 index 0000000..d055647 --- /dev/null +++ b/src/gsmenu/gs_connection_checker.c @@ -0,0 +1,83 @@ +#include "gs_connection_checker.h" +#include +#include +#include +#include +#include +#include "../osd.h" + +extern uint64_t gtotal_tunnel_data; // global variable for easyer access in gsmenu + // we missue the variable here for easyer integration + // this is also used from wfb cli thread for incomming traffic + +#define PROC_NET_DEV "/proc/net/dev" +#define MAX_LINE_LENGTH 256 +#define INTERFACE_PREFIX "wlx" + +unsigned long long get_rx_bytes(const char *interface_name) { + FILE *file = fopen(PROC_NET_DEV, "r"); + if (!file) { + perror("Error opening /proc/net/dev"); + return 0; + } + + char line[MAX_LINE_LENGTH]; + unsigned long long rx_bytes = 0; + int found = 0; + + // Skip the first two header lines + fgets(line, sizeof(line), file); + fgets(line, sizeof(line), file); + + while (fgets(line, sizeof(line), file)) { + char iface[64]; + unsigned long long rb, rp, re, rd, rr, rf, rm, rmc; + + // Parse the line: interface_name: rx_bytes rx_packets rx_errs rx_drop rx_fifo rx_frame rx_compressed rx_multicast ... + if (sscanf(line, " %63[^:]: %llu %llu %llu %llu %llu %llu %llu %llu", + iface, &rb, &rp, &re, &rd, &rf, &rr, &rm, &rmc) >= 9) { + + // Remove trailing colon if present + if (iface[strlen(iface)-1] == ':') { + iface[strlen(iface)-1] = '\0'; + } + + if (strncmp(iface, interface_name, strlen(interface_name)) == 0) { + rx_bytes += rb; + found = 1; + // printf("Found interface %s: %llu bytes\n", iface, rb); + } + } + } + + fclose(file); + + if (!found) { + printf("No interfaces found with prefix '%s'\n", interface_name); + } + + return rx_bytes; +} + +// Update network status +void update_network_status() { + + gtotal_tunnel_data = get_rx_bytes("wlx"); + +#ifndef USE_SIMULATOR + // ugly hack to hide osd bars + // ToDo: come back and use VRX side WLAN stats to populate rssi or lq + osd_tag tags[2]; + strcpy(tags[0].key, "id"); + strcpy(tags[0].val, "video rx"); + osd_publish_int_fact("wfbcli.rx.packets.all.total", tags, 1,2); // out of bound for osd config values + + strcpy(tags[1].key, "ant_id"); + for(int i=0;i< 10;i++) { + sprintf(tags[1].val, "%d", i * 256); + osd_publish_int_fact("wfbcli.rx.ant_stats.rssi_avg", tags, 2,2); // out of bound for osd config values + sprintf(tags[1].val, "%d", i * 256 + 1); + osd_publish_int_fact("wfbcli.rx.ant_stats.rssi_avg", tags, 2,2); // out of bound for osd config values + } +#endif +} diff --git a/src/gsmenu/gs_connection_checker.h b/src/gsmenu/gs_connection_checker.h new file mode 100644 index 0000000..abea8eb --- /dev/null +++ b/src/gsmenu/gs_connection_checker.h @@ -0,0 +1,3 @@ +#pragma once + +void update_network_status(); diff --git a/src/gsmenu/gs_system.c b/src/gsmenu/gs_system.c index d112dcd..1b389cc 100644 --- a/src/gsmenu/gs_system.c +++ b/src/gsmenu/gs_system.c @@ -1,6 +1,7 @@ #include #include #include +#include "../main.h" #include "gs_system.h" #include "lvgl/lvgl.h" #include "helper.h" @@ -8,13 +9,18 @@ #include "styles.h" extern lv_group_t * default_group; +enum RXMode RXMODE = WFB; +lv_obj_t * rx_mode; lv_obj_t * gs_rendering; lv_obj_t * resolution; lv_obj_t * rec_enabled; lv_obj_t * rec_fps; lv_obj_t * vsync_disabled; +extern lv_obj_t * ap_fpv_ssid; +extern lv_obj_t * ap_fpv_password; + typedef struct Dvr* Dvr; // Forward declaration void dvr_start_recording(Dvr* dvr); void dvr_stop_recording(Dvr* dvr); @@ -25,7 +31,10 @@ extern bool disable_vsync; void gs_system_page_load_callback(lv_obj_t * page) { + reload_switch_value(page,gs_rendering); + reload_dropdown_value(page,rx_mode); + RXMODE = lv_dropdown_get_selected(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class)); reload_dropdown_value(page,resolution); reload_dropdown_value(page,rec_fps); @@ -89,6 +98,15 @@ void rec_fps_cb(lv_event_t *e) { } } +void rx_mode_cb(lv_event_t *e) { + lv_event_code_t event = lv_event_get_code(e); + if (event == LV_EVENT_VALUE_CHANGED) { + lv_obj_t *ta = lv_event_get_target(e); + RXMODE = lv_dropdown_get_selected(ta); + gsmenu_toggle_rxmode(); + } +} + void create_gs_system_menu(lv_obj_t * parent) { menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); @@ -110,6 +128,14 @@ void create_gs_system_menu(lv_obj_t * parent) { cont = lv_menu_cont_create(section); lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + rx_mode = create_dropdown(cont,LV_SYMBOL_SETTINGS, "RX Mode","","rx_mode",menu_page_data,false); + lv_obj_add_event_cb(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class), rx_mode_cb, LV_EVENT_VALUE_CHANGED,NULL); + thread_data_t* data = lv_obj_get_user_data(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class)); + data->arguments[0] = lv_obj_get_child_by_type(ap_fpv_ssid,0,&lv_textarea_class); + data->arguments[1] = lv_obj_get_child_by_type(ap_fpv_password,0,&lv_textarea_class); + reload_dropdown_value(parent,rx_mode); + RXMODE = lv_dropdown_get_selected(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class)); + gs_rendering = create_switch(cont,LV_SYMBOL_SETTINGS,"GS Rendering","gs_rendering", menu_page_data,false); resolution = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Resolution","","resolution",menu_page_data,false); vsync_disabled = create_switch(cont,LV_SYMBOL_SETTINGS,"Disable VSYNC","disable_vsync", NULL,false); diff --git a/src/gsmenu/gs_system.h b/src/gsmenu/gs_system.h index 5565ffa..9dd4038 100644 --- a/src/gsmenu/gs_system.h +++ b/src/gsmenu/gs_system.h @@ -1,5 +1,10 @@ #pragma once #include "lvgl/lvgl.h" +enum RXMode { + WFB, + APFPV +}; + void toggle_rec_enabled(void); void create_gs_system_menu(lv_obj_t * parent); \ No newline at end of file diff --git a/src/gsmenu/helper.c b/src/gsmenu/helper.c index 05480e8..c1a4f13 100644 --- a/src/gsmenu/helper.c +++ b/src/gsmenu/helper.c @@ -2,11 +2,15 @@ #include #include #include "../../lvgl/lvgl.h" +#include "gs_system.h" #include "../input.h" #include "helper.h" #include "styles.h" #include "ui.h" #include "executor.h" +#include "../WiFiRSSIMonitor.h" + +extern enum RXMode RXMODE; extern gsmenu_control_mode_t control_mode; extern lv_obj_t * menu; @@ -15,16 +19,29 @@ extern lv_obj_t * sub_gs_main_page; extern lv_obj_t * air_presets_cont; extern lv_obj_t * air_wfbng_cont; +extern lv_obj_t * air_alink_cont; +extern lv_obj_t * air_aalink_cont; extern lv_obj_t * air_camera_cont; extern lv_obj_t * air_telemetry_cont; extern lv_obj_t * air_actions_cont; extern lv_obj_t * gs_dvr_cont; extern lv_obj_t * gs_wfbng_cont; +extern lv_obj_t * gs_apfpv_cont; extern lv_obj_t * gs_system_cont; extern lv_obj_t * gs_wlan_cont; extern lv_obj_t * gs_actions_cont; extern lv_group_t *main_group; extern lv_group_t * error_group; +extern lv_obj_t * size; // air camera size setting wfb-ng only +extern lv_obj_t * fps; // air camera fps setting wfb-ng only +extern lv_obj_t * bitrate; // air camera bitrate setting wfb-ng only +extern lv_obj_t * video_mode; // air camera video_mode setting apfpv only +extern lv_obj_t * router; +extern lv_obj_t * osd_fps; +extern lv_obj_t * air_gs_rendering; +extern lv_obj_t * air_telemetry_msposd_text; +extern lv_obj_t * air_telemetry_msposd_section; + extern lv_obj_t * msgbox; @@ -147,11 +164,14 @@ void generic_back_event_handler(lv_event_t * e) { lv_menu_set_page(menu,NULL); lv_obj_remove_state(air_presets_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_wfbng_cont, LV_STATE_CHECKED); + lv_obj_remove_state(air_alink_cont, LV_STATE_CHECKED); + lv_obj_remove_state(air_aalink_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_camera_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_telemetry_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_actions_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_dvr_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_wfbng_cont, LV_STATE_CHECKED); + lv_obj_remove_state(gs_apfpv_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_system_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_wlan_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_actions_cont, LV_STATE_CHECKED); @@ -177,6 +197,7 @@ lv_obj_t * create_text(lv_obj_t * parent, const char * icon, const char * txt, c lv_label_set_text(label, txt); // lv_label_set_long_mode(label, LV_LABEL_LONG_MODE_SCROLL_CIRCULAR); lv_obj_set_flex_grow(label, 1); + lv_obj_set_user_data(obj,(void *)txt); // ugly but where else to store } if(builder_variant == LV_MENU_ITEM_BUILDER_VARIANT_2 && icon && txt) { @@ -530,6 +551,43 @@ lv_obj_t * create_dropdown(lv_obj_t * parent, const char * icon, const char * la return obj; } + +lv_obj_t * create_checkbox(lv_obj_t * parent, const char * icon, const char * label_txt, const char * parameter, menu_page_data_t* menu_page_data,bool blocking) +{ + + lv_obj_t * obj = lv_menu_cont_create(parent); + + lv_obj_t * img = NULL; + + if(icon) { + img = lv_image_create(obj); + lv_image_set_src(img, icon); + } + + lv_obj_t * cb; + cb = lv_checkbox_create(obj); + lv_checkbox_set_text(cb, label_txt); + + lv_obj_add_style(cb, &style_openipc_outline, LV_PART_MAIN | LV_STATE_FOCUS_KEY); + lv_obj_set_style_border_color(cb, lv_color_hex(0xff4c60d8), LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(cb, lv_color_hex(0xff4c60d8), LV_PART_INDICATOR | LV_STATE_CHECKED); + + thread_data_t* data = malloc(sizeof(thread_data_t)); + if (data) { + memset(data, 0, sizeof(thread_data_t)); + } + data->menu_page_data = menu_page_data; + data->blocking = blocking; + strcpy(data->parameter, parameter); + + lv_obj_set_user_data(cb,data); + + lv_obj_add_event_cb(cb, generic_checkbox_event_cb, LV_EVENT_VALUE_CHANGED,data); + lv_obj_add_event_cb(cb, generic_back_event_handler, LV_EVENT_KEY,NULL); + + return obj; +} + lv_obj_t * create_backbutton(lv_obj_t * parent, const char * icon, const char * label_txt) { @@ -591,6 +649,7 @@ lv_obj_t * create_textarea(lv_obj_t * parent, char * text, const char * label_tx // Recursive function to find the first focusable object lv_obj_t * find_first_focusable_obj(lv_obj_t * parent) { // Iterate through all children of the parent + if (lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) return NULL; for (int i = 0; i < lv_obj_get_child_cnt(parent); i++) { lv_obj_t * child = lv_obj_get_child(parent, i); @@ -657,10 +716,13 @@ void reload_label_value(lv_obj_t * page,lv_obj_t * parameter) { // Get the parameter value const char *param_value = get_paramater(page, param_user_data->parameter); + + // Original label + const char *org_txt = (const char*)lv_obj_get_user_data(parameter); // Create the combined string char buffer[256]; - snprintf(buffer, sizeof(buffer), "%s: %s", param_user_data->parameter, param_value); + snprintf(buffer, sizeof(buffer), "%s: %s", org_txt, param_value); // Set the label text lv_label_set_text(obj, buffer); @@ -668,50 +730,69 @@ void reload_label_value(lv_obj_t * page,lv_obj_t * parameter) { void reload_switch_value(lv_obj_t * page,lv_obj_t * parameter) { lv_obj_t * obj = lv_obj_get_child_by_type(parameter,0,&lv_switch_class); - thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); - bool value = atoi(get_paramater(page,param_user_data->parameter)); - lv_lock(); - lv_obj_set_state(obj,LV_STATE_CHECKED,value); - lv_unlock(); + if ( !lv_obj_has_state(obj, LV_STATE_DISABLED) && ! lv_obj_has_flag(parameter,LV_OBJ_FLAG_HIDDEN)) { + thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); + bool value = atoi(get_paramater(page,param_user_data->parameter)); + lv_lock(); + lv_obj_set_state(obj,LV_STATE_CHECKED,value); + lv_unlock(); + } } void reload_dropdown_value(lv_obj_t * page,lv_obj_t * parameter) { lv_obj_t * obj = lv_obj_get_child_by_type(parameter,0,&lv_dropdown_class); - thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); - char * value = get_paramater(page, param_user_data->parameter); - lv_lock(); - lv_dropdown_set_selected(obj,lv_dropdown_get_option_index(obj,value)); - lv_unlock(); + if ( !lv_obj_has_state(obj, LV_STATE_DISABLED) && ! lv_obj_has_flag(parameter,LV_OBJ_FLAG_HIDDEN)) { + thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); + char * value = get_paramater(page, param_user_data->parameter); + lv_lock(); + lv_dropdown_set_selected(obj,lv_dropdown_get_option_index(obj,value)); + lv_unlock(); + } +} + +void reload_checkbox_value(lv_obj_t * page,lv_obj_t * parameter) { + lv_obj_t * obj = lv_obj_get_child_by_type(parameter,0,&lv_checkbox_class); + if ( !lv_obj_has_state(obj, LV_STATE_DISABLED) && ! lv_obj_has_flag(parameter,LV_OBJ_FLAG_HIDDEN)) { + thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); + bool value = atoi(get_paramater(page,param_user_data->parameter)); + lv_lock(); + lv_obj_set_state(obj,LV_STATE_CHECKED,value); + lv_unlock(); + } } void reload_textarea_value(lv_obj_t * page,lv_obj_t * parameter) { lv_obj_t * obj = lv_obj_get_child_by_type(parameter,0,&lv_textarea_class); - thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); - const char * value = get_paramater(page,param_user_data->parameter); - lv_lock(); - lv_textarea_set_text(obj,value); - lv_unlock(); + // if ( !lv_obj_has_state(obj, LV_STATE_DISABLED) && ! lv_obj_has_flag(parameter,LV_OBJ_FLAG_HIDDEN)) { // ToDo: This need rework + thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); + const char * value = get_paramater(page,param_user_data->parameter); + lv_lock(); + lv_textarea_set_text(obj,value); + lv_unlock(); + // } } void reload_slider_value(lv_obj_t * page,lv_obj_t * parameter) { lv_obj_t * obj = lv_obj_get_child_by_type(parameter,0,&lv_slider_class); lv_obj_t * label = lv_obj_get_child_by_type(parameter,1,&lv_label_class); - thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); - char * value = get_paramater(page,param_user_data->parameter); - float current_value; - if (sscanf(value, "%f", ¤t_value) != 1) { - return; + if ( !lv_obj_has_state(obj, LV_STATE_DISABLED) && ! lv_obj_has_flag(parameter,LV_OBJ_FLAG_HIDDEN)) { + thread_data_t * param_user_data = (thread_data_t*) lv_obj_get_user_data(obj); + char * value = get_paramater(page,param_user_data->parameter); + float current_value; + if (sscanf(value, "%f", ¤t_value) != 1) { + return; + } + int32_t scaled_value = (int32_t)(current_value * powf(10, param_user_data->precision)); + // Create a buffer for the float value display + char format[16]; + snprintf(format, sizeof(format), "%%.%df", param_user_data->precision); + char s[32]; + snprintf(s, sizeof(s), format, current_value); + lv_lock(); + lv_slider_set_value(obj,scaled_value,LV_ANIM_OFF); + lv_label_set_text(label,s); + lv_unlock(); } - int32_t scaled_value = (int32_t)(current_value * powf(10, param_user_data->precision)); - // Create a buffer for the float value display - char format[16]; - snprintf(format, sizeof(format), "%%.%df", param_user_data->precision); - char s[32]; - snprintf(s, sizeof(s), format, current_value); - lv_lock(); - lv_slider_set_value(obj,scaled_value,LV_ANIM_OFF); - lv_label_set_text(label,s); - lv_unlock(); } char* get_values(thread_data_t * data) { @@ -810,4 +891,116 @@ const char* find_resource_file(const char* relative_path) { } return NULL; // Not found +} + +void gsmenu_toggle_rxmode() { + + + switch (RXMODE) + { + case APFPV: + lv_obj_add_flag(air_wfbng_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(router, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(osd_fps, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(air_gs_rendering, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(air_telemetry_msposd_text,LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(air_telemetry_msposd_section,LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(air_alink_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(gs_wfbng_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(size, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(fps, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(bitrate, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(gs_apfpv_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(video_mode, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(air_aalink_cont, LV_OBJ_FLAG_HIDDEN); + setenv("REMOTE_IP" , "192.168.0.1", 1); + setenv("AIR_FIRMWARE_TYPE" , "apfpv", 1); + break; + case WFB: + lv_obj_remove_flag(air_wfbng_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(router, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(osd_fps, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(air_gs_rendering, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(air_telemetry_msposd_text,LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(air_telemetry_msposd_section,LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(air_alink_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(gs_wfbng_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(size, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(fps, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(bitrate, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(gs_apfpv_cont, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(video_mode, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(air_aalink_cont, LV_OBJ_FLAG_HIDDEN); + setenv("REMOTE_IP" , "10.5.0.10", 1); + setenv("AIR_FIRMWARE_TYPE" , "wfb", 1); + wifi_rssi_monitor_reset(); + break; + + default: + break; + } +} + +void add_entry_to_menu_page(menu_page_data_t *menu_page_data, const char* text, lv_obj_t* obj, ReloadFunc reload_func) { + // Increase entry count + menu_page_data->entry_count++; + + // Reallocate memory for entries + PageEntry *new_entries = realloc(menu_page_data->page_entries, + sizeof(PageEntry) * menu_page_data->entry_count); + + if (new_entries) { + menu_page_data->page_entries = new_entries; + + // Add new entry at the end with string copy + menu_page_data->page_entries[menu_page_data->entry_count - 1] = + (PageEntry){ strdup(text), obj, reload_func }; + } else { + // Handle allocation failure + menu_page_data->entry_count--; // Revert count on failure + } +} + +void delete_menu_page_entry_by_obj(menu_page_data_t *menu_page_data, lv_obj_t* obj) { + if (!menu_page_data || !menu_page_data->page_entries || menu_page_data->entry_count == 0) { + return; + } + + // Find the index of the entry with matching object + int found_index = -1; + for (size_t i = 0; i < menu_page_data->entry_count; i++) { + if (menu_page_data->page_entries[i].target == obj) { + found_index = i; + break; + } + } + + if (found_index == -1) { + return; + } + + // Free the duplicated string + if (menu_page_data->page_entries[found_index].caption) { + free((void*)menu_page_data->page_entries[found_index].caption); + } + + // Shift entries + for (size_t i = found_index; i < menu_page_data->entry_count - 1; i++) { + menu_page_data->page_entries[i] = menu_page_data->page_entries[i + 1]; + } + + menu_page_data->entry_count--; + + // Only reallocate if we have entries left + if (menu_page_data->entry_count > 0) { + PageEntry *new_entries = realloc(menu_page_data->page_entries, + sizeof(PageEntry) * menu_page_data->entry_count); + // If realloc succeeds, use the new pointer. If it fails, keep the old one. + if (new_entries) { + menu_page_data->page_entries = new_entries; + } + } else { + free(menu_page_data->page_entries); + menu_page_data->page_entries = NULL; + } } \ No newline at end of file diff --git a/src/gsmenu/helper.h b/src/gsmenu/helper.h index f280148..d7a117c 100644 --- a/src/gsmenu/helper.h +++ b/src/gsmenu/helper.h @@ -25,6 +25,8 @@ void dropdown_event_handler(lv_event_t * e); lv_obj_t * create_dropdown(lv_obj_t * parent, const char * icon, const char * label_txt, const char * txt,const char * parameter, menu_page_data_t* menu_page_data,bool blocking); +lv_obj_t * create_checkbox(lv_obj_t * parent, const char * icon, const char * label_txt, const char * parameter, menu_page_data_t* menu_page_data,bool blocking); + void generic_button_callback(lv_event_t * e); lv_obj_t * create_button(lv_obj_t * parent, const char * txt); @@ -41,10 +43,16 @@ char* get_paramater(lv_obj_t * page, char * param); void reload_label_value(lv_obj_t * page,lv_obj_t * parameter); void reload_switch_value(lv_obj_t * page,lv_obj_t * parameter); void reload_dropdown_value(lv_obj_t * page,lv_obj_t * parameter); +void reload_checkbox_value(lv_obj_t * page,lv_obj_t * parameter); void reload_textarea_value(lv_obj_t * page,lv_obj_t * parameter); void reload_slider_value(lv_obj_t * page,lv_obj_t * parameter); void get_slider_value(lv_obj_t * parent); void get_dropdown_value(lv_obj_t * parent); void generic_back_event_handler(lv_event_t * e); -const char* find_resource_file(const char* relative_path); \ No newline at end of file +const char* find_resource_file(const char* relative_path); + +void gsmenu_toggle_rxmode(); + +void add_entry_to_menu_page(menu_page_data_t *menu_page_data, const char* text, lv_obj_t* obj, ReloadFunc reload_func); +void delete_menu_page_entry_by_obj(menu_page_data_t *menu_page_data, lv_obj_t* obj); diff --git a/src/gsmenu/ui.c b/src/gsmenu/ui.c index 4152da9..218d010 100644 --- a/src/gsmenu/ui.c +++ b/src/gsmenu/ui.c @@ -3,21 +3,27 @@ #include #include "../../lvgl/lvgl.h" +#include "../main.h" #include "../input.h" #include "helper.h" #include "air_presets.h" #include "air_wfbng.h" #include "air_alink.h" +#include "air_aalink.h" #include "air_camera.h" #include "air_telemetry.h" #include "air_actions.h" #include "gs_dvr.h" #include "gs_main.h" #include "gs_wfbng.h" +#include "gs_apfpv.h" #include "gs_system.h" #include "gs_wifi.h" #include "gs_actions.h" #include "styles.h" +#include "gs_connection_checker.h" + +extern enum RXMode RXMODE; static void back_event_handler(lv_event_t * e); extern lv_obj_t * menu; @@ -33,11 +39,13 @@ lv_obj_t * sub_gs_main_page; lv_obj_t * sub_air_presets_page; lv_obj_t * sub_air_wfbng_page; lv_obj_t * sub_air_alink_page; +lv_obj_t * sub_air_aalink_page; lv_obj_t * sub_air_camera_page; lv_obj_t * sub_air_telemetry_page; lv_obj_t * sub_air_actions_page; lv_obj_t * sub_gs_dvr_page; lv_obj_t * sub_gs_wfbng_page; +lv_obj_t * sub_gs_apfpv_page; lv_obj_t * sub_gs_system_page; lv_obj_t * sub_wlan_page; lv_obj_t * sub_gs_actions_page; @@ -45,15 +53,19 @@ lv_obj_t * sub_gs_actions_page; lv_obj_t * air_presets_cont; lv_obj_t * air_wfbng_cont; lv_obj_t * air_alink_cont; +lv_obj_t * air_aalink_cont; lv_obj_t * air_camera_cont; lv_obj_t * air_telemetry_cont; lv_obj_t * air_actions_cont; lv_obj_t * gs_dvr_cont; lv_obj_t * gs_wfbng_cont; +lv_obj_t * gs_apfpv_cont; lv_obj_t * gs_system_cont; lv_obj_t * gs_wlan_cont; lv_obj_t * gs_actions_cont; +extern lv_obj_t * ap_fpv_channel; + extern bool menu_active; extern uint64_t gtotal_tunnel_data; // global variable for easyer access in gsmenu uint64_t last_count = 0; @@ -66,6 +78,11 @@ void recursive_state_set(lv_obj_t *obj, bool enable) { if (!obj) return; // Set state for the current object + const lv_obj_class_t * object_class = lv_obj_get_class(obj); + if(object_class == &lv_textarea_class) { + return; + } + if (enable) { lv_obj_remove_state(obj, LV_STATE_DISABLED); } else { @@ -85,6 +102,7 @@ void check_connection_timer(lv_timer_t * timer) static uint32_t last_value = 0; static uint32_t last_increase_time = 0; + if (RXMODE == APFPV) update_network_status(); uint32_t current_value = (uint32_t)gtotal_tunnel_data; // Reset detection (either manual reset or wraparound) @@ -113,20 +131,24 @@ void check_connection_timer(lv_timer_t * timer) recursive_state_set(air_presets_cont, false); recursive_state_set(air_wfbng_cont, false); recursive_state_set(air_alink_cont, false); + recursive_state_set(air_aalink_cont, false); recursive_state_set(air_camera_cont, false); recursive_state_set(air_telemetry_cont, false); recursive_state_set(air_actions_cont, false); recursive_state_set(sub_air_presets_page, false); recursive_state_set(sub_air_wfbng_page, false); recursive_state_set(sub_air_alink_page, false); + recursive_state_set(sub_air_aalink_page, false); recursive_state_set(sub_air_camera_page, false); recursive_state_set(sub_air_telemetry_page, false); recursive_state_set(sub_air_actions_page, false); + lv_obj_add_state(lv_obj_get_child_by_type(ap_fpv_channel,0,&lv_dropdown_class),LV_STATE_DISABLED); setenv("GSMENU_VTX_DETECTED" , "0", 1); lv_obj_t * current_page = lv_menu_get_cur_main_page(menu); if (sub_air_presets_page == current_page || sub_air_wfbng_page == current_page || sub_air_alink_page == current_page || + sub_air_aalink_page == current_page || sub_air_camera_page == current_page || sub_air_telemetry_page == current_page || sub_air_actions_page == current_page @@ -149,21 +171,25 @@ void check_connection_timer(lv_timer_t * timer) recursive_state_set(air_presets_cont, true); recursive_state_set(air_wfbng_cont, true); recursive_state_set(air_alink_cont, true); + recursive_state_set(air_aalink_cont, true); recursive_state_set(air_camera_cont, true); recursive_state_set(air_telemetry_cont, true); recursive_state_set(air_actions_cont, true); recursive_state_set(sub_air_presets_page, true); recursive_state_set(sub_air_wfbng_page, true); recursive_state_set(sub_air_alink_page, true); + recursive_state_set(sub_air_aalink_page, true); recursive_state_set(sub_air_camera_page, true); recursive_state_set(sub_air_telemetry_page, true); recursive_state_set(sub_air_actions_page, true); + lv_obj_remove_state(lv_obj_get_child_by_type(ap_fpv_channel,0,&lv_dropdown_class), LV_STATE_DISABLED); setenv("GSMENU_VTX_DETECTED" , "1", 1); lv_obj_t * current_page = lv_menu_get_cur_main_page(menu); if (sub_air_presets_page == current_page || sub_air_wfbng_page == current_page || sub_air_alink_page == current_page || + sub_air_aalink_page == current_page || sub_air_camera_page == current_page || sub_air_telemetry_page == current_page || sub_air_actions_page == current_page @@ -188,21 +214,25 @@ void check_connection_timer(lv_timer_t * timer) recursive_state_set(air_presets_cont, false); recursive_state_set(air_wfbng_cont, false); recursive_state_set(air_alink_cont, false); + recursive_state_set(air_aalink_cont, false); recursive_state_set(air_camera_cont, false); recursive_state_set(air_telemetry_cont, false); recursive_state_set(air_actions_cont, false); recursive_state_set(sub_air_presets_page, false); recursive_state_set(sub_air_wfbng_page, false); recursive_state_set(sub_air_alink_page, false); + recursive_state_set(sub_air_aalink_page, false); recursive_state_set(sub_air_camera_page, false); recursive_state_set(sub_air_telemetry_page, false); recursive_state_set(sub_air_actions_page, false); + lv_obj_add_state(lv_obj_get_child_by_type(ap_fpv_channel,0,&lv_dropdown_class),LV_STATE_DISABLED); setenv("GSMENU_VTX_DETECTED" , "0", 1); lv_obj_t * current_page = lv_menu_get_cur_main_page(menu); if (sub_air_presets_page == current_page || sub_air_wfbng_page == current_page || sub_air_alink_page == current_page || + sub_air_aalink_page == current_page || sub_air_camera_page == current_page || sub_air_telemetry_page == current_page || sub_air_actions_page == current_page @@ -272,6 +302,11 @@ lv_obj_t * pp_menu_create(lv_obj_t * screen) lv_menu_separator_create(sub_air_alink_page); create_air_alink_menu(sub_air_alink_page); + sub_air_aalink_page = lv_menu_page_create(menu, LV_SYMBOL_WIFI" AALink"); + lv_obj_set_style_pad_hor(sub_air_aalink_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0); + lv_menu_separator_create(sub_air_aalink_page); + create_air_aalink_menu(sub_air_aalink_page); + sub_air_camera_page = lv_menu_page_create(menu, LV_SYMBOL_IMAGE" Camera"); lv_obj_set_style_pad_hor(sub_air_camera_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0); lv_menu_separator_create(sub_air_camera_page); @@ -297,6 +332,11 @@ lv_obj_t * pp_menu_create(lv_obj_t * screen) lv_menu_separator_create(sub_gs_wfbng_page); create_gs_wfbng_menu(sub_gs_wfbng_page); + sub_gs_apfpv_page = lv_menu_page_create(menu, LV_SYMBOL_WIFI" APFPV"); + lv_obj_set_style_pad_hor(sub_gs_apfpv_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0); + lv_menu_separator_create(sub_gs_apfpv_page); + create_apfpv_menu(sub_gs_apfpv_page); + sub_gs_system_page = lv_menu_page_create(menu, LV_SYMBOL_SETTINGS" System"); lv_obj_set_style_pad_hor(sub_gs_system_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0); lv_menu_separator_create(sub_gs_system_page); @@ -335,6 +375,11 @@ lv_obj_t * pp_menu_create(lv_obj_t * screen) lv_menu_set_load_page_event(menu, air_alink_cont, sub_air_alink_page); lv_obj_add_event_cb(air_alink_cont,back_event_handler,LV_EVENT_KEY,NULL); + air_aalink_cont = create_text(section, LV_SYMBOL_WIFI, "AALink", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + lv_group_add_obj(main_group,air_aalink_cont); + lv_menu_set_load_page_event(menu, air_aalink_cont, sub_air_aalink_page); + lv_obj_add_event_cb(air_aalink_cont,back_event_handler,LV_EVENT_KEY,NULL); + air_camera_cont = create_text(section, LV_SYMBOL_IMAGE, "Camera", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); lv_group_add_obj(main_group,air_camera_cont); lv_menu_set_load_page_event(menu, air_camera_cont, sub_air_camera_page); @@ -364,6 +409,11 @@ lv_obj_t * pp_menu_create(lv_obj_t * screen) lv_menu_set_load_page_event(menu, gs_wfbng_cont, sub_gs_wfbng_page); lv_obj_add_event_cb(gs_wfbng_cont,back_event_handler,LV_EVENT_KEY,NULL); + gs_apfpv_cont = create_text(section, LV_SYMBOL_WIFI, "APFPV", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); + lv_group_add_obj(main_group,gs_apfpv_cont); + lv_menu_set_load_page_event(menu, gs_apfpv_cont, sub_gs_apfpv_page); + lv_obj_add_event_cb(gs_apfpv_cont,back_event_handler,LV_EVENT_KEY,NULL); + gs_system_cont = create_text(section, LV_SYMBOL_SETTINGS, "System Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); lv_group_add_obj(main_group,gs_system_cont); lv_menu_set_load_page_event(menu, gs_system_cont, sub_gs_system_page); @@ -387,6 +437,8 @@ lv_obj_t * pp_menu_create(lv_obj_t * screen) last_value = gtotal_tunnel_data; setenv("GSMENU_VTX_DETECTED" , "0", 1); + gsmenu_toggle_rxmode(); + lv_group_set_default(default_group); return menu; } @@ -401,11 +453,13 @@ static void back_event_handler(lv_event_t * e) lv_obj_remove_state(air_presets_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_wfbng_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_alink_cont, LV_STATE_CHECKED); + lv_obj_remove_state(air_aalink_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_camera_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_telemetry_cont, LV_STATE_CHECKED); lv_obj_remove_state(air_actions_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_dvr_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_wfbng_cont, LV_STATE_CHECKED); + lv_obj_remove_state(gs_apfpv_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_system_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_wlan_cont, LV_STATE_CHECKED); lv_obj_remove_state(gs_actions_cont, LV_STATE_CHECKED); diff --git a/src/gsmenu/ui.h b/src/gsmenu/ui.h index ca25c06..44683f2 100644 --- a/src/gsmenu/ui.h +++ b/src/gsmenu/ui.h @@ -17,7 +17,7 @@ typedef struct { void (*page_load_callback)(lv_obj_t * page); lv_group_t *indev_group; size_t entry_count; - PageEntry page_entries[]; + PageEntry *page_entries; } menu_page_data_t; lv_obj_t * pp_header_create(lv_obj_t * screen); diff --git a/src/main.cpp b/src/main.cpp index 2070701..b9c14e6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,7 +55,8 @@ extern "C" { #include "os_mon.hpp" #include "pixelpilot_config.h" #include - +#include "WiFiRSSIMonitor.hpp" +#include "gsmenu/gs_system.h" #define READ_BUF_SIZE (1024*1024) // SZ_1M https://github.com/rockchip-linux/mpp/blob/ed377c99a733e2cdbcc457a6aa3f0fcd438a9dff/osal/inc/mpp_common.h#L179 #define MAX_FRAMES 24 // min 16 and 20+ recommended (mpp/readme.txt) @@ -109,6 +110,9 @@ OsSensors os_sensors; // TODO: pass as argument to `main_loop` uint32_t video_plane_id_override = 0; uint32_t osd_plane_id_override = 0; +WiFiRSSIMonitor wifi_monitor; +extern enum RXMode RXMODE; + void init_buffer(MppFrame frame) { output_list->video_frm_width = mpp_frame_get_width(frame); output_list->video_frm_height = mpp_frame_get_height(frame); @@ -540,6 +544,9 @@ void main_loop() { // TODO: put gsmenu main loop here msg_manager.check_message(); os_sensors.run(); + if (RXMODE == APFPV) { + wifi_monitor.run(); + } sleep(1); } return; diff --git a/src/main.h b/src/main.h index 1689172..62bdd14 100644 --- a/src/main.h +++ b/src/main.h @@ -1,6 +1,5 @@ #pragma once - void sig_handler(int signum); void switch_pipeline_source(const char * source_type, const char * source_path); diff --git a/src/wfbcli.cpp b/src/wfbcli.cpp index 4f587bd..902f07c 100644 --- a/src/wfbcli.cpp +++ b/src/wfbcli.cpp @@ -20,6 +20,7 @@ #include #include "spdlog/spdlog.h" +#include "gsmenu/gs_system.h" #include "wfbcli.hpp" extern "C" { #include "osd.h" @@ -30,6 +31,7 @@ extern "C" { int wfb_thread_signal = 0; uint64_t gtotal_tunnel_data = 0; // global variable for easyer access in gsmenu +extern enum RXMode RXMODE; int process_rx(const msgpack::object& packet) { @@ -298,41 +300,51 @@ int reconnect_to_server(const char *host, int port) { hints.ai_socktype = SOCK_STREAM; // TCP while (!wfb_thread_signal) { - SPDLOG_DEBUG("wfb-cli attempting to connect to API server..."); - // Get address info - if ((status = getaddrinfo(host, std::to_string(port).c_str(), &hints, &res)) != 0) { - SPDLOG_ERROR("wfb-cli incorrect host {} or port {}. Error: {}", - host, port, gai_strerror(status)); - std::this_thread::sleep_for(std::chrono::seconds(1)); - continue; - } - // Loop through results and try to connect - for (p = res; p != nullptr; p = p->ai_next) { - sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); - if (sock < 0) { - SPDLOG_ERROR("wfb-cli socket creation failed"); - continue; - } - if (connect(sock, p->ai_addr, p->ai_addrlen) < 0) { - char ip_str[INET6_ADDRSTRLEN]; - if (p->ai_family == AF_INET) { - struct sockaddr_in* ipv4 = (struct sockaddr_in*)p->ai_addr; - inet_ntop(AF_INET, &ipv4->sin_addr, ip_str, sizeof(ip_str)); - } else { - struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)p->ai_addr; - inet_ntop(AF_INET6, &ipv6->sin6_addr, ip_str, sizeof(ip_str)); + switch (RXMODE) + { + case APFPV: + SPDLOG_DEBUG("rxMode is apfpv, idle WFB thread"); + break; + + case WFB: + { + SPDLOG_DEBUG("wfb-cli attempting to connect to API server..."); + // Get address info + if ((status = getaddrinfo(host, std::to_string(port).c_str(), &hints, &res)) != 0) { + SPDLOG_ERROR("wfb-cli incorrect host {} or port {}. Error: {}", + host, port, gai_strerror(status)); + std::this_thread::sleep_for(std::chrono::seconds(1)); + continue; + } + // Loop through results and try to connect + for (p = res; p != nullptr; p = p->ai_next) { + sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sock < 0) { + SPDLOG_ERROR("wfb-cli socket creation failed"); + continue; + } + if (connect(sock, p->ai_addr, p->ai_addrlen) < 0) { + char ip_str[INET6_ADDRSTRLEN]; + if (p->ai_family == AF_INET) { + struct sockaddr_in* ipv4 = (struct sockaddr_in*)p->ai_addr; + inet_ntop(AF_INET, &ipv4->sin_addr, ip_str, sizeof(ip_str)); + } else { + struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)p->ai_addr; + inet_ntop(AF_INET6, &ipv6->sin6_addr, ip_str, sizeof(ip_str)); + } + SPDLOG_ERROR("wfb-cli connection to {}:{} ({}) failed", host, port, ip_str); + close(sock); // Clean up the socket if connection fails + continue; + } + SPDLOG_DEBUG("wfb-cli successfully connected to API server."); + freeaddrinfo(res); + return sock; // success + } + freeaddrinfo(res); + res = nullptr; + SPDLOG_WARN("Reconnection failed. Retrying in 1 second"); } - SPDLOG_ERROR("wfb-cli connection to {}:{} ({}) failed", host, port, ip_str); - close(sock); // Clean up the socket if connection fails - continue; - } - SPDLOG_DEBUG("wfb-cli successfully connected to API server."); - freeaddrinfo(res); - return sock; // success } - freeaddrinfo(res); - res = nullptr; - SPDLOG_WARN("Reconnection failed. Retrying in 1 second"); std::this_thread::sleep_for(std::chrono::seconds(1)); } return -1;