From 2e1af5a6e335196901006118f1502c66f83a14dd Mon Sep 17 00:00:00 2001 From: kevdotnet Date: Wed, 18 Mar 2026 17:24:32 +0100 Subject: [PATCH 1/4] MAVLink Hub: Cross-Forwarding Between Connected Drones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary This PR adds a MAVLink Hub feature to DroneBridge ESP32. When the device operates as a Wi-Fi Access Point, it can now act as a MAVLink router — forwarding MAVLink traffic between all connected clients (drones and GCS), not just between the serial flight controller and the network. Without this feature, a multi-drone setup where each drone has its own DroneBridge in STA mode can only send telemetry to a GCS via serial; the drones cannot see each other's MAVLink streams, and the GCS only sees the AP's local FC. With the hub enabled, all drones' telemetry is visible to all other connected clients. --- Changes New: MAVLink Hub (cross-forwarding) When operating in AP mode with MAVLink protocol, incoming UDP packets are forwarded to all other registered UDP clients (split-horizon: not back to the source). This enables: - Drone → GCS: All connected drones' telemetry reaches the GCS - Drone → Drone: Drones can see each other's MAVLink streams - GCS → All: GCS commands reach all connected drones The hub only activates in MAVLink protocol mode since it relies on MAVLink header sniffing to identify heartbeats and GCS packets. New: Hub toggle parameter (SYS_MAV_HUB_EN) A new boolean parameter mav_broadcast (MAVLink name: SYS_MAV_HUB_EN, default: enabled) controls whether cross-forwarding is active. When disabled: - Serial FC data still reaches identified GCS clients (SysID 255) and manually saved static IPs - Drones do not receive each other's traffic, reducing network load - Heartbeats are always forwarded regardless of this setting to keep links alive The toggle is only shown in the web UI when in AP or AP-LR mode. New: Blacklist parameter (MAV_BLACKLIST) A new string parameter mav_blacklist accepts a comma-separated list of MAVLink System IDs (e.g. 1,5,10) to exclude from forwarding. Heartbeats are always forwarded regardless of the blacklist. The blacklist is applied to: - Serial → UDP: FC telemetry is not sent to blacklisted clients - UDP → UDP: Cross-forwarded traffic does not reach blacklisted clients - UDP → Serial: Data is not forwarded to the local serial FC if its own SysID is blacklisted (protects the AP's FC UART from being flooded by other drones' traffic in large swarms) The blacklist UI is shown only when in AP mode and the hub toggle is enabled. New: GCS and SysID identification on UDP clients db_udp_client_t gains two new fields: - is_gcs: set to true when the client sends a MAVLink packet with SysID 255; sticky once set - system_id: updated on every received packet; used by the blacklist Both fields are populated before add_to_known_udp_clients is called, so GCS identification is available immediately for routing decisions. Existing clients have their is_gcs and system_id fields updated on subsequent packets. Changed: db_parse_mavlink_from_radio signature Added a forward_to_serial boolean parameter. When false, the MAVLink parser still processes incoming frames (for parameter handling, responding to requests, etc.) but does not write them to the serial port. This allows the hub's hub-off/blacklist logic to suppress serial writes without losing parameter protocol functionality. All existing call sites (ESP-NOW, BLE) pass true unconditionally since hub logic does not apply there. Changed: db_send_to_all_udp_clients signature The function now takes an explicit udp_conn_list_t * parameter instead of using the global udp_conn_list directly, making it consistent with the rest of the codebase. Bug fix: zero-initialize db_udp_client_t in Wi-Fi event handler (main.c) The db_udp_client struct in IP_EVENT_AP_STAIPASSIGNED was previously uninitialized, leaving the new is_gcs and system_id fields as garbage. Changed to = {0}. --- Behaviour when Hub is OFF (backwards-compatible default for single-drone setups) Single-drone users who don't need the hub can disable it. The routing logic then falls back to a conservative mode: - Clients with no WiFi MAC (manually saved static GCS IPs) always receive FC telemetry - Clients with a WiFi MAC only receive FC telemetry once they have identified themselves as a GCS (by sending a packet with SysID 255) - Heartbeats are always forwarded to keep GCS link status alive --- Parameter count DB_PARAM_TOTAL_NUM: 25 → 30 DB_PARAM_MAV_CNT: 17 → 21 Co-authored-by: H3rD3m1s3MyR1s3 --- frontend/dronebridge.js | 19 +++++ frontend/index.html | 14 ++++ main/db_esp32_control.c | 156 +++++++++++++++++++++++++++++++++++----- main/db_esp32_control.h | 2 + main/db_parameters.c | 30 +++++++- main/db_parameters.h | 9 ++- main/db_serial.c | 10 +-- main/db_serial.h | 2 +- main/main.c | 2 +- 9 files changed, 216 insertions(+), 28 deletions(-) diff --git a/frontend/dronebridge.js b/frontend/dronebridge.js index 16351e0..7a924b6 100644 --- a/frontend/dronebridge.js +++ b/frontend/dronebridge.js @@ -20,6 +20,17 @@ function change_radio_dis_arm_visibility() { } } +function change_mav_blacklist_visibility() { + let mav_broadcast = document.getElementById("mav_broadcast"); + let mav_broadcast_div = document.getElementById("mav_broadcast_div"); + let mav_blacklist_div = document.getElementById("mav_blacklist_div"); + if (mav_broadcast_div.style.display !== "none" && mav_broadcast.checked) { + mav_blacklist_div.style.display = "block"; + } else { + mav_blacklist_div.style.display = "none"; + } +} + function change_ap_ip_visibility() { const esp32Mode = document.getElementById("esp32_mode").value; const elements = { @@ -31,8 +42,15 @@ function change_ap_ip_visibility() { wifi_en_gn_div: document.getElementById("wifi_en_gn_div"), static_ip_config_div: document.getElementById("static_ip_config_div"), pass_div: document.getElementById("pass_div"), + mav_broadcast_div: document.getElementById("mav_broadcast_div"), }; + if (esp32Mode === "1" || esp32Mode === "3") { + elements.mav_broadcast_div.style.display = "block"; + } else { + elements.mav_broadcast_div.style.display = "none"; + } + if (esp32Mode === "2") { elements.ap_ip_div.style.display = "none"; elements.ap_channel_div.style.display = "none"; @@ -69,6 +87,7 @@ function change_ap_ip_visibility() { } else { elements.wifi_ssid_div.style.visibility = "visible"; } + change_mav_blacklist_visibility(); change_radio_dis_arm_visibility(); } diff --git a/frontend/index.html b/frontend/index.html index 092d637..cac5315 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -296,6 +296,20 @@

Serial

+
+
+
+ + +
+
+
+
diff --git a/main/db_esp32_control.c b/main/db_esp32_control.c index 3017139..37a109d 100644 --- a/main/db_esp32_control.c +++ b/main/db_esp32_control.c @@ -44,6 +44,7 @@ #include "main.h" #include "db_serial.h" #include "db_esp_now.h" +#include "db_mavlink_msgs.h" #define TAG "DB_CONTROL" @@ -168,11 +169,71 @@ int db_open_int_telemetry_udp_socket() { * @param data Buffer with the data to send * @param data_length Length of the data in the buffer */ -void db_send_to_all_udp_clients(const uint8_t *data, uint data_length) { - for (int i = 0; i < udp_conn_list->size; i++) { // send to all UDP clients - int sent = sendto(udp_conn_list->udp_socket, data, data_length, 0, - (struct sockaddr *) &udp_conn_list->db_udp_clients[i].udp_client, - sizeof(udp_conn_list->db_udp_clients[i].udp_client)); +/** + * Checks if a MAVLink system ID is in the blacklist + * @param system_id The system ID to check + * @return true if blacklisted, false otherwise + */ +bool is_system_id_blacklisted(uint8_t system_id) { + char *blacklist = DB_PARAM_MAV_BLACKLIST; + if (blacklist == NULL || blacklist[0] == '\0') return false; + + char temp_blacklist[DB_PARAM_VALUE_MAXLEN]; + strncpy(temp_blacklist, blacklist, sizeof(temp_blacklist)); + temp_blacklist[sizeof(temp_blacklist)-1] = '\0'; + + char *token = strtok(temp_blacklist, ","); + while (token != NULL) { + if (atoi(token) == (int)system_id) return true; + token = strtok(NULL, ","); + } + return false; +} + +/** + * Sends the data to all registered UDP clients. + * @param n_udp_conn_list The list of UDP clients + * @param data The data to be sent + * @param data_length Length of the data in the buffer + */ +void db_send_to_all_udp_clients(udp_conn_list_t *n_udp_conn_list, const uint8_t *data, uint data_length) { + // Simple MAVLink Sniffer to identify Heartbeats + bool is_heartbeat = false; + if (data_length >= 8) { + if (data[0] == 0xFE) { // MAVLink v1 + if (data[5] == 0) is_heartbeat = true; + } else if (data[0] == 0xFD && data_length >= 10) { // MAVLink v2 + if (data[7] == 0 && data[8] == 0 && data[9] == 0) is_heartbeat = true; + } + } + + for (int i = 0; i < n_udp_conn_list->size; i++) { // send to all UDP clients + // If Hub mode is OFF and we are in AP mode: + // 1. Always allow Heartbeats + // 2. Always allow clients with no MAC (manually saved static IPs, e.g. a GCS configured via the UI) + // since they were never registered via the WiFi STA event and start with is_gcs=false after reboot. + // 3. Only allow other telemetry to reach clients identified as GCS at runtime (SysID 255). + if (!is_heartbeat && !DB_PARAM_MAV_BROADCAST && (DB_PARAM_RADIO_MODE == DB_WIFI_MODE_AP || DB_PARAM_RADIO_MODE == DB_WIFI_MODE_AP_LR)) { + bool has_mac = false; + for (int m = 0; m < 6; m++) { + if (n_udp_conn_list->db_udp_clients[i].mac[m] != 0) { + has_mac = true; + break; + } + } + if (has_mac && !n_udp_conn_list->db_udp_clients[i].is_gcs) { + continue; // Skip: WiFi STA that has not identified itself as a GCS + } + } + + // Apply Blacklist: Skip if system ID is blacklisted and it's not a Heartbeat + if (!is_heartbeat && is_system_id_blacklisted(n_udp_conn_list->db_udp_clients[i].system_id)) { + continue; + } + + int sent = sendto(n_udp_conn_list->udp_socket, data, data_length, 0, + (struct sockaddr *) &n_udp_conn_list->db_udp_clients[i].udp_client, + sizeof(n_udp_conn_list->db_udp_clients[i].udp_client)); if (sent != data_length) { int err = errno; char *client_ip = inet_ntoa(((struct sockaddr_in *)&udp_conn_list->db_udp_clients[i].udp_client)->sin_addr); @@ -384,6 +445,12 @@ add_to_known_udp_clients(udp_conn_list_t *n_udp_conn_list, struct db_udp_client_ if ((n_udp_conn_list->db_udp_clients[i].udp_client.sin_port == new_db_udp_client.udp_client.sin_port) && (n_udp_conn_list->db_udp_clients[i].udp_client.sin_addr.s_addr == new_db_udp_client.udp_client.sin_addr.s_addr)) { + // Update GCS status if the new packet identifies it as GCS + if (new_db_udp_client.is_gcs && !n_udp_conn_list->db_udp_clients[i].is_gcs) { + n_udp_conn_list->db_udp_clients[i].is_gcs = true; + ESP_LOGI(TAG, "Identified existing UDP client as GCS"); + } + n_udp_conn_list->db_udp_clients[i].system_id = new_db_udp_client.system_id; return false; // client existing - do not add } } @@ -394,7 +461,7 @@ add_to_known_udp_clients(udp_conn_list_t *n_udp_conn_list, struct db_udp_client_ char ip_string[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(new_db_udp_client.udp_client.sin_addr), ip_string, INET_ADDRSTRLEN); sprintf(ip_port_string, "%s:%d", ip_string, htons (new_db_udp_client.udp_client.sin_port)); - ESP_LOGI(TAG, "Added %s to udp client distribution list - save to NVM: %i", ip_port_string, save_to_nvm); + ESP_LOGI(TAG, "Added %s to udp client distribution list (is_gcs: %i) - save to NVM: %i", ip_port_string, new_db_udp_client.is_gcs, save_to_nvm); // save to memory if (save_to_nvm) { save_udp_client_to_nvm(&new_db_udp_client, false); @@ -502,7 +569,7 @@ _Noreturn void control_module_esp_now() { if (DB_PARAM_SERIAL_PROTO == DB_SERIAL_PROTOCOL_MAVLINK) { // Parse, so we can listen in and react to certain messages - function will send parsed messages to serial link. // We can not write to serial first since we might inject packets and do not know when to do so to not "destroy" an existing packet - db_parse_mavlink_from_radio(NULL, NULL, db_espnow_uart_evt.data, db_espnow_uart_evt.data_len); + db_parse_mavlink_from_radio(NULL, NULL, db_espnow_uart_evt.data, db_espnow_uart_evt.data_len, true); } else { // no parsing with any other protocol - transparent here - just pass through write_to_serial(db_espnow_uart_evt.data, db_espnow_uart_evt.data_len); @@ -701,23 +768,76 @@ _Noreturn void control_module_udp_tcp() { (struct sockaddr *) &new_db_udp_client.udp_client, &udp_socklen); if (recv_length > 0) { data_processed = true; - if (DB_PARAM_SERIAL_PROTO == DB_SERIAL_PROTOCOL_MAVLINK) { - // Parse, so we can listen in and react to certain messages - function will send parsed messages to serial link. - // We can not write to serial first since we might inject packets and do not know when to do so to not "destroy" an existing packet - db_parse_mavlink_from_radio(connected_tcp_clients, udp_conn_list, udp_buffer, recv_length); - } else { - // no parsing with any other protocol - transparent here - write_to_serial(udp_buffer, recv_length); + + // Simple MAVLink Sniffer to identify GCS and Heartbeats + bool is_gcs_packet = false; + bool is_heartbeat = false; + uint8_t source_sys_id = 0; + if (recv_length >= 8) { + if (udp_buffer[0] == 0xFE) { // MAVLink v1 + source_sys_id = udp_buffer[3]; + if (source_sys_id == 255) is_gcs_packet = true; + if (udp_buffer[5] == 0) is_heartbeat = true; + } else if (udp_buffer[0] == 0xFD && recv_length >= 10) { // MAVLink v2 + source_sys_id = udp_buffer[5]; + if (source_sys_id == 255) is_gcs_packet = true; + if (udp_buffer[7] == 0 && udp_buffer[8] == 0 && udp_buffer[9] == 0) is_heartbeat = true; + } } + new_db_udp_client.system_id = source_sys_id; + new_db_udp_client.is_gcs = is_gcs_packet; + // all devices that send us UDP data will be added to the list of UDP receivers // Allows to register new app on different port. Used e.g. for UDP conn setup in sta-mode. - // Devices/Ports added this way cannot be removed in sta-mode since UDP is connectionless, and we cannot - // determine if the client is still existing. This will blow up the list connected devices. - // In AP-Mode the devices can be removed based on the IP/MAC address + // Register client before forwarding so that target checks (is_gcs) work for responses add_to_known_udp_clients(udp_conn_list, new_db_udp_client, false); + + if (DB_PARAM_SERIAL_PROTO == DB_SERIAL_PROTOCOL_MAVLINK) { + // Parse, so we can listen in and react to certain messages - function will send parsed messages to serial link if allowed. + // Packets from other drones are parsed (for ESP32 params) but not pushed to local FC UART if Hub is disabled. + // Apply blacklist to serial: if the local FC's SysID is blacklisted, only heartbeats reach it. + bool should_forward_to_serial = (is_gcs_packet || DB_PARAM_MAV_BROADCAST || is_heartbeat) + && (is_heartbeat || !is_system_id_blacklisted(db_get_mav_sys_id())); + db_parse_mavlink_from_radio(connected_tcp_clients, udp_conn_list, udp_buffer, recv_length, should_forward_to_serial); + + // Forward radio data to all other network clients (MAVLink Router/Hub functionality) + // This ensures STAs can see each other and the GCS can see all STAs + // Uses Split-Horizon: Do not send back to source. + // Only applies to MAVLink mode since hub logic relies on MAVLink header sniffing. + if (DB_PARAM_RADIO_MODE == DB_WIFI_MODE_AP || DB_PARAM_RADIO_MODE == DB_WIFI_MODE_AP_LR) { + for (int i = 0; i < udp_conn_list->size; i++) { + // Skip the source client + if (udp_conn_list->db_udp_clients[i].udp_client.sin_addr.s_addr == new_db_udp_client.udp_client.sin_addr.s_addr && + udp_conn_list->db_udp_clients[i].udp_client.sin_port == new_db_udp_client.udp_client.sin_port) { + continue; + } + + // Forwarding Logic: + // 1. Always forward Heartbeats (Msg ID 0) to everyone to keep links alive. + // 2. Always forward everything from a GCS (SysID 255). + // 3. If Hub mode is ON: Forward everything (unless blacklisted). + // 4. If Hub mode is OFF: Only forward to identified GCS clients. + if (is_heartbeat || is_gcs_packet || DB_PARAM_MAV_BROADCAST || udp_conn_list->db_udp_clients[i].is_gcs) { + // Apply blacklist: skip blacklisted targets (heartbeats always pass through) + if (!is_heartbeat && is_system_id_blacklisted(udp_conn_list->db_udp_clients[i].system_id)) { + continue; + } + sendto(udp_conn_list->udp_socket, udp_buffer, recv_length, 0, + (struct sockaddr *)&udp_conn_list->db_udp_clients[i].udp_client, + sizeof(struct sockaddr_in)); + } + } + } + } else { + // no parsing with any other protocol - only forward if it's from GCS or Hub is enabled + if (is_gcs_packet || DB_PARAM_MAV_BROADCAST || is_heartbeat) { + write_to_serial(udp_buffer, recv_length); + } + } } else { // received nothing, keep on going } + if (DB_PARAM_RADIO_MODE == DB_WIFI_MODE_STA) { handle_internal_telemetry(db_internal_telem_udp_sock, udp_buffer, &udp_socklen, &new_db_udp_client.udp_client); @@ -810,7 +930,7 @@ _Noreturn void control_module_ble() { // Parse, so we can listen in and react to certain messages - function will send parsed messages to serial link. // We cannot write to serial first since we might inject packets and do not know when to do so to not "destroy" an // existing packet - db_parse_mavlink_from_radio(NULL, NULL, bleData.data, bleData.data_len); + db_parse_mavlink_from_radio(NULL, NULL, bleData.data, bleData.data_len, true); } else { // no parsing with any other protocol - transparent here - just pass through write_to_serial(bleData.data, bleData.data_len); diff --git a/main/db_esp32_control.h b/main/db_esp32_control.h index 384ab55..5c5d791 100644 --- a/main/db_esp32_control.h +++ b/main/db_esp32_control.h @@ -33,6 +33,8 @@ struct db_udp_client_t { uint8_t mac[6]; // MAC address of connected client struct sockaddr_in udp_client; // socket address (IP & PORT) of connected client + bool is_gcs; // true if this client has sent a MAVLink packet from System ID 255 (GCS) + uint8_t system_id; // The last seen MAVLink System ID of this client }; typedef struct udp_conn_list_s { diff --git a/main/db_parameters.c b/main/db_parameters.c index 0c06f12..b299678 100644 --- a/main/db_parameters.c +++ b/main/db_parameters.c @@ -46,7 +46,7 @@ uint8_t DB_RADIO_MODE_DESIGNATED = DB_WIFI_MODE_AP; // initially assign the same /* ---------- String based parameters - not available via MAVLink ---------- */ db_parameter_t db_param_ssid, db_param_pass, db_param_wifi_ap_ip, db_param_wifi_sta_ip, db_param_wifi_sta_gw, - db_param_wifi_sta_netmask, db_param_udp_client_ip, db_param_wifi_hostname = {0}; + db_param_wifi_sta_netmask, db_param_udp_client_ip, db_param_wifi_hostname, db_param_mav_blacklist = {0}; /* ---------- From here with increasing param_index all parameters that are also available via MAVLink ---------- */ @@ -414,6 +414,28 @@ db_parameter_t db_param_rssi_dbm = { } }; +/** + * Enable/Disable cross-forwarding of MAVLink messages between clients (Hub mode) + */ +db_parameter_t db_param_mav_broadcast = { + .db_name = "mav_broadcast", + .type = UINT8, + .mav_t = { + .param_name = "SYS_MAV_HUB_EN", + .param_index = 20, + .param_type = MAV_PARAM_TYPE_UINT8, + }, + .value = { + .db_param_u8 = { + .value = true, + .default_value = true, + .min = false, + .max = true, + } + } +}; + + /** * Array containing all references to the DB parameters assigned with db_param_init_parameters() */ @@ -492,6 +514,8 @@ void db_param_init_parameters() { db_param_udp_client_ip = db_param_init_str_param("udp_client_ip", "WIFI_UDP_IP", "", 0, IP4ADDR_STRLEN_MAX); // Specifies the hostname. Used in Wi-Fi ap & client mode. db_param_wifi_hostname = db_param_init_str_param("wifi_hostname", "WIFI_HOSTNAME", CONFIG_LWIP_LOCAL_HOSTNAME, 1, 32); + // MAVLink system ID blacklist for cross-forwarding (comma separated list of IDs) + db_param_mav_blacklist = db_param_init_str_param("mav_blacklist", "MAV_BLACKLIST", "", 0, DB_PARAM_VALUE_MAXLEN); db_parameter_t *db_params_l[] = { &db_param_ssid, @@ -502,6 +526,7 @@ void db_param_init_parameters() { &db_param_wifi_sta_netmask, &db_param_udp_client_ip, &db_param_wifi_hostname, + &db_param_mav_blacklist, &db_param_radio_mode, &db_param_channel, &db_param_wifi_en_gn, @@ -518,7 +543,8 @@ void db_param_init_parameters() { &db_param_ltm_per_packet, &db_param_dis_radio_armed, &db_param_udp_client_port, - &db_param_rssi_dbm + &db_param_rssi_dbm, + &db_param_mav_broadcast }; memcpy(db_params, db_params_l, sizeof(db_params_l)); } diff --git a/main/db_parameters.h b/main/db_parameters.h index e3f1273..7ed5698 100644 --- a/main/db_parameters.h +++ b/main/db_parameters.h @@ -34,8 +34,8 @@ #define DB_MATURITY_VERSION "stable" #define DB_TYPE_VERSION 255 // FIRMWARE_VERSION_TYPE_OFFICIAL -> https://mavlink.io/en/messages/common.html#FIRMWARE_VERSION_TYPE -#define DB_PARAM_TOTAL_NUM 25 // total number of db parameters -#define DB_PARAM_MAV_CNT 17 // Number of MAVLink parameters returned by ESP32 in the PARAM message. Needed by GCS. +#define DB_PARAM_TOTAL_NUM 30 // total number of db parameters +#define DB_PARAM_MAV_CNT 21 // Number of MAVLink parameters returned by ESP32 in the PARAM message. Needed by GCS. #define DB_PARAM_NAME_MAXLEN 16 // max len of a parameter/key stored in the ESP32 NVM #define DB_PARAM_MAX_MAV_PARAM_NAME_LEN 16 // max len of the field used to store the mav param name (max len 16 by def.) @@ -91,6 +91,8 @@ #define DB_PARAM_GPIO_CTS db_param_gpio_cts.value.db_param_u8.value #define DB_PARAM_SERIAL_RTS_THRESH db_param_gpio_rts_thresh.value.db_param_u8.value #define DB_PARAM_EN_EXT_ANT db_param_radio_ant_ext.value.db_param_u8.value +#define DB_PARAM_MAV_BROADCAST db_param_mav_broadcast.value.db_param_u8.value +#define DB_PARAM_MAV_BLACKLIST (char *) db_param_mav_blacklist.value.db_param_str.value enum E_DB_WIFI_MODE { DB_WIFI_MODE_AP = 1, // Wi-Fi access point mode with 802.11b mode enabled @@ -187,6 +189,9 @@ extern db_parameter_t db_param_ltm_per_packet; extern db_parameter_t db_param_dis_radio_armed; extern db_parameter_t db_param_udp_client_port; extern db_parameter_t db_param_rssi_dbm; +extern db_parameter_t db_param_mav_broadcast; +extern db_parameter_t db_param_mav_blacklist; + void db_param_init_parameters(); void db_param_set_to_default(db_parameter_t *db_parameter); diff --git a/main/db_serial.c b/main/db_serial.c index 66acd37..f3fbdbd 100644 --- a/main/db_serial.c +++ b/main/db_serial.c @@ -258,17 +258,19 @@ void db_route_mavlink_response(uint8_t *buffer, uint16_t length, enum DB_MAVLINK * @param up_conns Structure containing all UDP connection data including the sockets * @param buffer Buffer containing the raw bytes to be parsed * @param bytes_read Number of bytes in the buffer - * @param origin Origin of the data - serial link or radio link + * @param forward_to_serial If true, complete MAVLink frames will be written to the local serial port */ -void db_parse_mavlink_from_radio(int *tcp_clients, udp_conn_list_t *udp_conns, uint8_t *buffer, int bytes_read) { +void db_parse_mavlink_from_radio(int *tcp_clients, udp_conn_list_t *udp_conns, uint8_t *buffer, int bytes_read, bool forward_to_serial) { static uint8_t mav_parser_rx_buf[296]; // at least 280 bytes which is the max len for a MAVLink v2 packet // Parse each byte received for (int i = 0; i < bytes_read; ++i) { fmav_result_t result = {0}; if (fmav_parse_and_check_to_frame_buf(&result, mav_parser_rx_buf, &fmav_status_radio, buffer[i])) { - // Parser detected a full message, write to serial - write_to_serial(mav_parser_rx_buf, result.frame_len); + // Parser detected a full message, write to serial if allowed + if (forward_to_serial) { + write_to_serial(mav_parser_rx_buf, result.frame_len); + } // Decode message and react to it if it was for us fmav_frame_buf_to_msg(&msg, &result, mav_parser_rx_buf); if (result.res == FASTMAVLINK_PARSE_RESULT_OK) { diff --git a/main/db_serial.h b/main/db_serial.h index fde509e..b3bf469 100644 --- a/main/db_serial.h +++ b/main/db_serial.h @@ -53,7 +53,7 @@ void db_parse_msp_ltm(int tcp_clients[], udp_conn_list_t *udp_connection, uint8_ void db_read_serial_parse_mavlink(int *tcp_clients, udp_conn_list_t *udp_conns, uint8_t *serial_buffer, unsigned int *serial_buff_pos); void db_read_serial_parse_transparent(int tcp_clients[], udp_conn_list_t *udp_connection, uint8_t serial_buffer[], unsigned int *serial_read_bytes); -void db_parse_mavlink_from_radio(int *tcp_clients, udp_conn_list_t *udp_conns, uint8_t *buffer, int bytes_read); +void db_parse_mavlink_from_radio(int *tcp_clients, udp_conn_list_t *udp_conns, uint8_t *buffer, int bytes_read, bool forward_to_serial); void db_route_mavlink_response(uint8_t *buffer, uint16_t length, enum DB_MAVLINK_DATA_ORIGIN origin, int *tcp_clients, udp_conn_list_t *udp_conns); diff --git a/main/main.c b/main/main.c index 739f1b1..5a6e198 100644 --- a/main/main.c +++ b/main/main.c @@ -162,7 +162,7 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t e ip_event_ap_staipassigned_t *event = (ip_event_ap_staipassigned_t *) event_data; ESP_LOGI(TAG, "IP_EVENT_AP_STAIPASSIGNED - New station IP:" IPSTR, IP2STR(&event->ip)); ESP_LOGI(TAG, "IP_EVENT_AP_STAIPASSIGNED - MAC: " MACSTR, MAC2STR(event->mac)); - struct db_udp_client_t db_udp_client; + struct db_udp_client_t db_udp_client = {0}; db_udp_client.udp_client.sin_family = PF_INET; db_udp_client.udp_client.sin_port = htons(APP_PORT_PROXY_UDP); db_udp_client.udp_client.sin_len = 16; From b4c6b7a1f6ac43c321dfb7ed328e0609fe87e8ee Mon Sep 17 00:00:00 2001 From: kevdotnet Date: Wed, 18 Mar 2026 17:46:30 +0100 Subject: [PATCH 2/4] Fix build errors (missing arguments in function calls) --- main/db_esp32_control.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/db_esp32_control.c b/main/db_esp32_control.c index 37a109d..749398b 100644 --- a/main/db_esp32_control.c +++ b/main/db_esp32_control.c @@ -314,7 +314,7 @@ void db_send_to_all_clients(int tcp_clients[], udp_conn_list_t *n_udp_conn_list, default: // Other modes (WiFi Modes using TCP/UDP) db_send_to_all_tcp_clients(tcp_clients, data, data_length); - db_send_to_all_udp_clients(data, data_length); + db_send_to_all_udp_clients(udp_conn_list, data, data_length); break; } } @@ -365,7 +365,7 @@ void db_send_to_all_radio_clients(uint8_t data[], uint16_t data_length) { default: // Other modes (WiFi Modes using TCP/UDP) db_send_to_all_tcp_clients(connected_tcp_clients, data, data_length); - db_send_to_all_udp_clients(data, data_length); + db_send_to_all_udp_clients(udp_conn_list, data, data_length); break; } } @@ -743,7 +743,7 @@ _Noreturn void control_module_udp_tcp() { if (DB_PARAM_SERIAL_PROTO == DB_SERIAL_PROTOCOL_MAVLINK) { // Parse, so we can listen in and react to certain messages - function will send parsed messages to serial link. // We can not write to serial first since we might inject packets and do not know when to do so to not "destroy" an existign packet - db_parse_mavlink_from_radio(connected_tcp_clients, udp_conn_list, tcp_client_buffer, recv_length); + db_parse_mavlink_from_radio(connected_tcp_clients, udp_conn_list, tcp_client_buffer, recv_length, true); } else { // no parsing with any other protocol - transparent here write_to_serial(tcp_client_buffer, recv_length); From 0f18f1b805482f2ce0e7da5ed3b72e025aa47c71 Mon Sep 17 00:00:00 2001 From: kevdotnet Date: Wed, 18 Mar 2026 18:14:30 +0100 Subject: [PATCH 3/4] Use udp_conn_list directly instead of passing it as a parameter. --- main/db_esp32_control.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/main/db_esp32_control.c b/main/db_esp32_control.c index 749398b..f33e13e 100644 --- a/main/db_esp32_control.c +++ b/main/db_esp32_control.c @@ -196,7 +196,7 @@ bool is_system_id_blacklisted(uint8_t system_id) { * @param data The data to be sent * @param data_length Length of the data in the buffer */ -void db_send_to_all_udp_clients(udp_conn_list_t *n_udp_conn_list, const uint8_t *data, uint data_length) { +void db_send_to_all_udp_clients(const uint8_t *data, uint data_length) { // Simple MAVLink Sniffer to identify Heartbeats bool is_heartbeat = false; if (data_length >= 8) { @@ -207,7 +207,7 @@ void db_send_to_all_udp_clients(udp_conn_list_t *n_udp_conn_list, const uint8_t } } - for (int i = 0; i < n_udp_conn_list->size; i++) { // send to all UDP clients + for (int i = 0; i < udp_conn_list->size; i++) { // send to all UDP clients // If Hub mode is OFF and we are in AP mode: // 1. Always allow Heartbeats // 2. Always allow clients with no MAC (manually saved static IPs, e.g. a GCS configured via the UI) @@ -216,24 +216,24 @@ void db_send_to_all_udp_clients(udp_conn_list_t *n_udp_conn_list, const uint8_t if (!is_heartbeat && !DB_PARAM_MAV_BROADCAST && (DB_PARAM_RADIO_MODE == DB_WIFI_MODE_AP || DB_PARAM_RADIO_MODE == DB_WIFI_MODE_AP_LR)) { bool has_mac = false; for (int m = 0; m < 6; m++) { - if (n_udp_conn_list->db_udp_clients[i].mac[m] != 0) { + if (udp_conn_list->db_udp_clients[i].mac[m] != 0) { has_mac = true; break; } } - if (has_mac && !n_udp_conn_list->db_udp_clients[i].is_gcs) { + if (has_mac && !udp_conn_list->db_udp_clients[i].is_gcs) { continue; // Skip: WiFi STA that has not identified itself as a GCS } } // Apply Blacklist: Skip if system ID is blacklisted and it's not a Heartbeat - if (!is_heartbeat && is_system_id_blacklisted(n_udp_conn_list->db_udp_clients[i].system_id)) { + if (!is_heartbeat && is_system_id_blacklisted(udp_conn_list->db_udp_clients[i].system_id)) { continue; } - int sent = sendto(n_udp_conn_list->udp_socket, data, data_length, 0, - (struct sockaddr *) &n_udp_conn_list->db_udp_clients[i].udp_client, - sizeof(n_udp_conn_list->db_udp_clients[i].udp_client)); + int sent = sendto(udp_conn_list->udp_socket, data, data_length, 0, + (struct sockaddr *) &udp_conn_list->db_udp_clients[i].udp_client, + sizeof(udp_conn_list->db_udp_clients[i].udp_client)); if (sent != data_length) { int err = errno; char *client_ip = inet_ntoa(((struct sockaddr_in *)&udp_conn_list->db_udp_clients[i].udp_client)->sin_addr); @@ -314,7 +314,7 @@ void db_send_to_all_clients(int tcp_clients[], udp_conn_list_t *n_udp_conn_list, default: // Other modes (WiFi Modes using TCP/UDP) db_send_to_all_tcp_clients(tcp_clients, data, data_length); - db_send_to_all_udp_clients(udp_conn_list, data, data_length); + db_send_to_all_udp_clients(data, data_length); break; } } @@ -365,7 +365,7 @@ void db_send_to_all_radio_clients(uint8_t data[], uint16_t data_length) { default: // Other modes (WiFi Modes using TCP/UDP) db_send_to_all_tcp_clients(connected_tcp_clients, data, data_length); - db_send_to_all_udp_clients(udp_conn_list, data, data_length); + db_send_to_all_udp_clients(data, data_length); break; } } From 96805b05e7996a15736722c5289b76750ecbc69d Mon Sep 17 00:00:00 2001 From: kevdotnet Date: Fri, 20 Mar 2026 16:07:43 +0100 Subject: [PATCH 4/4] Always let GCS messages through --- main/db_esp32_control.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main/db_esp32_control.c b/main/db_esp32_control.c index f33e13e..5184b32 100644 --- a/main/db_esp32_control.c +++ b/main/db_esp32_control.c @@ -796,8 +796,9 @@ _Noreturn void control_module_udp_tcp() { // Parse, so we can listen in and react to certain messages - function will send parsed messages to serial link if allowed. // Packets from other drones are parsed (for ESP32 params) but not pushed to local FC UART if Hub is disabled. // Apply blacklist to serial: if the local FC's SysID is blacklisted, only heartbeats reach it. + // GCS packets (SysID 255) always bypass the blacklist so commands always reach the FC. bool should_forward_to_serial = (is_gcs_packet || DB_PARAM_MAV_BROADCAST || is_heartbeat) - && (is_heartbeat || !is_system_id_blacklisted(db_get_mav_sys_id())); + && (is_heartbeat || is_gcs_packet || !is_system_id_blacklisted(db_get_mav_sys_id())); db_parse_mavlink_from_radio(connected_tcp_clients, udp_conn_list, udp_buffer, recv_length, should_forward_to_serial); // Forward radio data to all other network clients (MAVLink Router/Hub functionality) @@ -818,8 +819,8 @@ _Noreturn void control_module_udp_tcp() { // 3. If Hub mode is ON: Forward everything (unless blacklisted). // 4. If Hub mode is OFF: Only forward to identified GCS clients. if (is_heartbeat || is_gcs_packet || DB_PARAM_MAV_BROADCAST || udp_conn_list->db_udp_clients[i].is_gcs) { - // Apply blacklist: skip blacklisted targets (heartbeats always pass through) - if (!is_heartbeat && is_system_id_blacklisted(udp_conn_list->db_udp_clients[i].system_id)) { + // Apply blacklist: skip blacklisted targets (heartbeats and GCS packets always pass through) + if (!is_heartbeat && !is_gcs_packet && is_system_id_blacklisted(udp_conn_list->db_udp_clients[i].system_id)) { continue; } sendto(udp_conn_list->udp_socket, udp_buffer, recv_length, 0,