diff --git a/.clangd b/.clangd old mode 100755 new mode 100644 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile old mode 100755 new mode 100644 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json old mode 100755 new mode 100644 diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json old mode 100755 new mode 100644 diff --git a/.vscode/launch.json b/.vscode/launch.json old mode 100755 new mode 100644 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100755 new mode 100644 index 87bf059..a8aa8b4 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,6 @@ "--background-index", "--query-driver=**", "--compile-commands-dir=g:\\Robot_v2\\DFU\\ESP32_COMM\\build" - ] +], +"idf.flashType": "UART" } diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/dependencies.lock b/dependencies.lock old mode 100755 new mode 100644 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index e443b9f..bfcc295 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,14 +1,13 @@ idf_component_register( SRCS "core/main.cpp" - "core/app_main.cpp" "relay/ota_relay.cpp" "transport/spi_master.cpp" "transport/protocol.cpp" "server/wifi_ota.cpp" "server/ble_ota.cpp" "monitor/health_monitor.cpp" - "debug/debug_log.cpp" + "monitor/debug_log.cpp" INCLUDE_DIRS "." "core" @@ -16,17 +15,19 @@ idf_component_register( "transport" "server" "monitor" - "debug" "include" REQUIRES + cjson esp_driver_spi + esp_driver_tsens + esp_driver_uart esp_wifi - esp_http_server - nvs_flash - esp_timer - esp_partition esp_netif esp_event - esp_system - cjson + esp_partition + esp_timer + esp_http_server + esp_blockdev + nvs_flash + freertos ) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild old mode 100755 new mode 100644 diff --git a/main/core/app_main.cpp b/main/core/app_main.cpp deleted file mode 100644 index 419cc6c..0000000 --- a/main/core/app_main.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * app_main.c - 应用程序初始化与健康监控 - */ -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "esp_timer.h" -#include "esp_chip_info.h" -#include "esp_system.h" -/* ESP-IDF 6.0: temperature_sensor.h 已移除,使用替代方案 */ - -#include "ota_relay.h" -#include "health_monitor.h" -#include "debug_log.h" - -extern health_state_t g_health_state; - -/* ESP-IDF 6.0: temperature_sensor 驱动已移除,使用系统负载估算温度 */ -static int32_t s_temp_estimate_celsius = 0; - -// 芯片温度监控(1秒间隔)- 使用系统负载估算 -static void temp_monitor_callback(void *arg) -{ - /* ESP-IDF 6.0 替代方案:使用空闲任务时间估算温度 - * 实际项目中建议使用外部温度传感器(如 I2C TMP107) - */ - (void)esp_timer_get_time(); // 获取时间戳用于负载估算(暂未使用) - DEBUG_DBG("TEMP", "芯片温度估算: %ld°C (基于系统负载)", s_temp_estimate_celsius); - - if (s_temp_estimate_celsius > 100) { - g_health_state = SYS_SAFE_SHUTDOWN; - DEBUG_ERR("TEMP", "危险: 温度 %ld°C > 100°C,触发安全关机", s_temp_estimate_celsius); - } else if (s_temp_estimate_celsius > 80) { - if (g_health_state < SYS_WARNING) { - g_health_state = SYS_WARNING; - DEBUG_WARN("TEMP", "警告: 温度 %ld°C > 80°C", s_temp_estimate_celsius); - } - } -} - -// 电压监控(启动时检查一次) -static void voltage_monitor_callback(void *arg) -{ - // ESP-IDF 6.0: esp_efuse_get_voltage 已移除 - // 使用回退方案:假设 3.3V - float voltage = 3.30f; - DEBUG_DBG("VOLT", "VDD 电压: %.2fV (估算)", voltage); - - if (voltage < 2.3f) { - g_health_state = SYS_SAFE_SHUTDOWN; - DEBUG_ERR("VOLT", "危险: 电压 %.2fV < 2.3V,触发安全关机", voltage); - } else if (voltage < 2.5f) { - if (g_health_state < SYS_WARNING) { - g_health_state = SYS_WARNING; - DEBUG_WARN("VOLT", "警告: 电压 %.2fV < 2.5V", voltage); - } - } -} - -// 系统监控任务 -void system_monitor_task(void *pvParameters) -{ - DEBUG_INFO("MON", "系统监控任务启动"); - - /* ESP-IDF 6.0: temperature_sensor 驱动已移除 - * 使用默认温度估计值(实际项目建议使用外部传感器) - */ - s_temp_estimate_celsius = 25; // 默认 25°C - - // 创建定时器进行周期性监控 - esp_timer_handle_t temp_timer; - esp_timer_create_args_t timer_args = {}; - timer_args.callback = &temp_monitor_callback; - timer_args.arg = nullptr; - timer_args.name = "temp_monitor"; - timer_args.dispatch_method = ESP_TIMER_TASK; - timer_args.skip_unhandled_events = false; - esp_timer_create(&timer_args, &temp_timer); - esp_timer_start_periodic(temp_timer, 1 * 1000000); // 1秒 - - // 启动时检查一次电压 - voltage_monitor_callback(NULL); - - while (1) { - // 每30秒记录一次健康状态 - DEBUG_INFO("MON", "健康状态: %d, 空闲堆: %lu", - g_health_state, esp_get_free_heap_size()); - - vTaskDelay(pdMS_TO_TICKS(30000)); - } -} - -// 当 eFuse 读取不可用时的电压回退方案 -uint32_t read_voltage_mv(void) -{ - return 3300; // 默认 3.3V 假设 -} \ No newline at end of file diff --git a/main/core/app_main.h b/main/core/app_main.h deleted file mode 100644 index a6c3ed1..0000000 --- a/main/core/app_main.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * app_main.h - 应用程序初始化与健康监控 - */ -#ifndef APP_MAIN_H -#define APP_MAIN_H - -#include - -/* 系统监控任务 */ -void system_monitor_task(void *pvParameters); - -/* 电压读取(回退方案) */ -uint32_t read_voltage_mv(void); - -#endif /* APP_MAIN_H */ \ No newline at end of file diff --git a/main/core/main.cpp b/main/core/main.cpp index 434d10e..e03b2ec 100644 --- a/main/core/main.cpp +++ b/main/core/main.cpp @@ -8,17 +8,15 @@ #include "esp_log.h" #include "esp_chip_info.h" -#include "core/app_main.h" -#include "ota_relay.h" -#include "spi_master.h" -#include "wifi_ota.h" -#include "ble_ota.h" -#include "protocol.h" -#include "debug_log.h" +#include "monitor/health_monitor.h" +#include "relay/ota_relay.h" +#include "transport/spi_master.h" +#include "server/wifi_ota.h" +#include "server/ble_ota.h" +#include "transport/protocol.h" +#include "monitor/debug_log.h" -extern "C" void app_main(void); - -void app_main(void) +extern "C" void app_main(void) { esp_chip_info_t chip_info; esp_chip_info(&chip_info); @@ -31,6 +29,17 @@ void app_main(void) DEBUG_INFO("MAIN", "Free heap: %lu bytes", esp_get_free_heap_size()); DEBUG_INFO("MAIN", "========================================"); + // 健康监控任务(温度 + 电压 + WiFi RSSI) + xTaskCreatePinnedToCore( + health_monitor_task, + "health_mon", + 4096, + NULL, + configMAX_PRIORITIES - 4, + NULL, + 0 + ); + // 初始化协议层(CRC、AES) protocol_init(); DEBUG_INFO("MAIN", "协议层初始化完成"); @@ -46,20 +55,6 @@ void app_main(void) // 创建 FreeRTOS 任务 BaseType_t xRet; - // 系统监控任务(最高优先级) - xRet = xTaskCreatePinnedToCore( - system_monitor_task, - "sys_monitor", - 4096, - NULL, - configMAX_PRIORITIES - 1, - NULL, - 1 // 核1 - ); - if (xRet != pdPASS) { - DEBUG_ERR("MAIN", "创建 system_monitor_task 失败"); - } - // OTA 中继任务(中高优先级) xRet = xTaskCreatePinnedToCore( ota_relay_task, @@ -96,7 +91,7 @@ void app_main(void) NULL, configMAX_PRIORITIES - 3, NULL, - 1 + 0 // ESP32-C3 单核,固定核0 ); if (xRet != pdPASS) { DEBUG_ERR("MAIN", "创建 ble_ota_task 失败"); diff --git a/main/idf_component.yml b/main/idf_component.yml old mode 100755 new mode 100644 diff --git a/main/include/app_config.h b/main/include/app_config.h index 71e4de7..2d71f1c 100644 --- a/main/include/app_config.h +++ b/main/include/app_config.h @@ -12,19 +12,21 @@ // ============================================================ // 0 = AP 模式(设备当热点) // 1 = STA 模式(连接路由器) +#define CONFIG_WIFI_MODE_IS_STA 1 inline constexpr uint8_t CONFIG_WIFI_MODE = 1; // ============================================================ // WiFi STA 模式配置(连接路由器) // ============================================================ -inline constexpr const char* WIFI_STA_SSID = "你的WiFi名称"; -inline constexpr const char* WIFI_STA_PASSWORD = "你的WiFi密码"; +inline constexpr const char* WIFI_STA_SSID = "004舰桥"; +inline constexpr const char* WIFI_STA_PASSWORD = "q13362875622"; inline constexpr uint32_t WIFI_STA_CONNECT_TIMEOUT_MS = 30000; // STA 静态 IP inline constexpr uint8_t WIFI_STA_USE_STATIC_IP = 0; // 设为 1 启用静态 IP +#define WIFI_STA_STATIC_IP_IS_ENABLED 0 -#if WIFI_STA_USE_STATIC_IP +#if WIFI_STA_STATIC_IP_IS_ENABLED inline constexpr const char* WIFI_STA_STATIC_IP = "192.168.1.100"; inline constexpr const char* WIFI_STA_STATIC_GW = "192.168.1.1"; inline constexpr const char* WIFI_STA_STATIC_NETMASK = "255.255.255.0"; diff --git a/main/debug/debug_log.cpp b/main/monitor/debug_log.cpp similarity index 100% rename from main/debug/debug_log.cpp rename to main/monitor/debug_log.cpp diff --git a/main/debug/debug_log.h b/main/monitor/debug_log.h similarity index 100% rename from main/debug/debug_log.h rename to main/monitor/debug_log.h diff --git a/main/monitor/health_monitor.cpp b/main/monitor/health_monitor.cpp index e3b5ea8..5b53643 100644 --- a/main/monitor/health_monitor.cpp +++ b/main/monitor/health_monitor.cpp @@ -1,11 +1,16 @@ /* - * health_monitor.c - 系统健康监控实现 + * health_monitor.cpp - 系统健康监控 + * 包含:健康状态管理 + WiFi RSSI 监控 + 温度监控 + 电压监控 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #include "esp_wifi.h" +#include "esp_timer.h" +#include "esp_err.h" +#include "driver/temperature_sensor.h" #include "health_monitor.h" #include "debug_log.h" @@ -13,7 +18,11 @@ health_state_t g_health_state = SYS_NORMAL; static SemaphoreHandle_t g_health_mutex = NULL; +static temperature_sensor_handle_t s_temp_handle = NULL; +// ---------------------------------------- +// 健康状态管理 +// ---------------------------------------- void health_monitor_init(void) { g_health_mutex = xSemaphoreCreateMutex(); @@ -22,7 +31,113 @@ void health_monitor_init(void) } } -// 每次向 GD32 发送 DATA_PACKET 前的健康检查 +health_state_t health_get_current(void) +{ + health_state_t state; + if (g_health_mutex) { + xSemaphoreTake(g_health_mutex, portMAX_DELAY); + } + state = g_health_state; + if (g_health_mutex) { + xSemaphoreGive(g_health_mutex); + } + return state; +} + +// ---------------------------------------- +// 温度监控(定时器回调) +// ---------------------------------------- +static void temp_monitor_callback(void *arg) +{ + float temp = 0.0f; + esp_err_t err = temperature_sensor_get_celsius(s_temp_handle, &temp); + if (err != ESP_OK) { + DEBUG_WARN("TEMP", "读取温度失败: %s", esp_err_to_name(err)); + return; + } + + DEBUG_DBG("TEMP", "芯片温度: %.1f°C", temp); + + if (temp >= TEMP_CRITICAL) { + g_health_state = SYS_SAFE_SHUTDOWN; + DEBUG_ERR("TEMP", "危险: 温度 %.1f°C >= %d°C,触发安全关机", temp, TEMP_CRITICAL); + } else if (temp >= TEMP_WARNING) { + if (g_health_state < SYS_WARNING) { + g_health_state = SYS_WARNING; + DEBUG_WARN("TEMP", "警告: 温度 %.1f°C >= %d°C", temp, TEMP_WARNING); + } + } +} + +// ---------------------------------------- +// 电压监控(启动时调用一次) +// ---------------------------------------- +static void voltage_monitor_callback(void *arg) +{ + float voltage = 3.30f; // ESP32-C3 无内置电压传感器,使用估算 + DEBUG_DBG("VOLT", "VDD 电压: %.2fV (估算)", voltage); + + if (voltage < 2.3f) { + g_health_state = SYS_SAFE_SHUTDOWN; + DEBUG_ERR("VOLT", "危险: 电压 %.2fV < 2.3V,触发安全关机", voltage); + } else if (voltage < 2.5f) { + if (g_health_state < SYS_WARNING) { + g_health_state = SYS_WARNING; + DEBUG_WARN("VOLT", "警告: 电压 %.2fV < 2.5V", voltage); + } + } +} + +// ---------------------------------------- +// 温度电压监控任务入口 +// ---------------------------------------- +void health_monitor_task(void *pvParameters) +{ + DEBUG_INFO("MON", "健康监控任务启动"); + + // 初始化内置温度传感器(测量范围 20°C ~ 80°C) + temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 80); + esp_err_t err = temperature_sensor_install(&temp_config, &s_temp_handle); + if (err != ESP_OK) { + DEBUG_ERR("MON", "温度传感器初始化失败: %s", esp_err_to_name(err)); + } else { + ESP_ERROR_CHECK(temperature_sensor_enable(s_temp_handle)); + DEBUG_INFO("MON", "温度传感器已启用 (范围 20°C ~ 80°C)"); + } + + // 定时器,每5秒读取一次温度 + esp_timer_handle_t temp_timer; + esp_timer_create_args_t timer_args = {}; + timer_args.callback = &temp_monitor_callback; + timer_args.arg = nullptr; + timer_args.name = "temp_monitor"; + timer_args.dispatch_method = ESP_TIMER_TASK; + timer_args.skip_unhandled_events = false; + esp_timer_create(&timer_args, &temp_timer); + esp_timer_start_periodic(temp_timer, 5 * 1000000); // 5秒 + + // 启动时检查一次电压 + voltage_monitor_callback(NULL); + + // 每5秒打印一次健康状态 + while (1) { + DEBUG_INFO("MON", "健康状态: %d, 空闲堆: %lu", + g_health_state, esp_get_free_heap_size()); + vTaskDelay(pdMS_TO_TICKS(5000)); + } +} + +// ---------------------------------------- +// 健康事件日志 +// ---------------------------------------- +void health_log_event(const char *event, int value) +{ + DEBUG_WARN("HEALTH", "[健康日志] %s: %d", event, value); +} + +// ---------------------------------------- +// OTA 传输前的健康检查 +// ---------------------------------------- health_status_t health_check_before_ota(void) { health_status_t status = HEALTH_MON_OK; @@ -50,25 +165,6 @@ health_status_t health_check_before_ota(void) return status; } -health_state_t health_get_current(void) -{ - health_state_t state; - if (g_health_mutex) { - xSemaphoreTake(g_health_mutex, portMAX_DELAY); - } - state = g_health_state; - if (g_health_mutex) { - xSemaphoreGive(g_health_mutex); - } - return state; -} - -// 健康事件的 Flash 日志写入 -void health_log_event(const char *event, int value) -{ - DEBUG_WARN("HEALTH", "[健康日志] %s: %d", event, value); -} - health_status_t health_update_from_wifi_rssi(int8_t rssi) { if (rssi < WIFI_RSSI_CRITICAL) { @@ -89,4 +185,4 @@ health_status_t health_update_from_wifi_rssi(int8_t rssi) } return HEALTH_MON_OK; -} \ No newline at end of file +} diff --git a/main/monitor/health_monitor.h b/main/monitor/health_monitor.h index 23f4583..34574c5 100644 --- a/main/monitor/health_monitor.h +++ b/main/monitor/health_monitor.h @@ -1,8 +1,7 @@ /* * health_monitor.h - 系统健康监控头文件 */ -#ifndef HEALTH_MONITOR_H -#define HEALTH_MONITOR_H +#pragma once #include #include "app_config.h" @@ -30,9 +29,9 @@ extern health_state_t g_health_state; #define WIFI_RSSI_WEAK WIFI_RSSI_CRITICAL // 函数声明 -extern health_status_t health_check_before_ota(void); -extern void health_log_event(const char *event, int value); -extern health_state_t health_get_current(void); -extern health_status_t health_update_from_wifi_rssi(int8_t rssi); - -#endif // HEALTH_MONITOR_H +void health_monitor_init(void); +void health_monitor_task(void *pvParameters); +health_status_t health_check_before_ota(void); +void health_log_event(const char *event, int value); +health_state_t health_get_current(void); +health_status_t health_update_from_wifi_rssi(int8_t rssi); diff --git a/main/relay/ota_relay.cpp b/main/relay/ota_relay.cpp index 5db096b..134557b 100644 --- a/main/relay/ota_relay.cpp +++ b/main/relay/ota_relay.cpp @@ -15,7 +15,7 @@ #include "spi_master.h" #include "protocol.h" #include "health_monitor.h" -#include "debug_log.h" +#include "monitor/debug_log.h" relay_state_t g_relay_state = RELAY_IDLE; diff --git a/main/server/ble_ota.cpp b/main/server/ble_ota.cpp index acff3cd..14abf46 100644 --- a/main/server/ble_ota.cpp +++ b/main/server/ble_ota.cpp @@ -15,7 +15,7 @@ #include "ble_ota.h" #include "ota_relay.h" #include "health_monitor.h" -#include "debug_log.h" +#include "monitor/debug_log.h" static bool s_ble_connected = false; diff --git a/main/server/wifi_ota.cpp b/main/server/wifi_ota.cpp index 3b5ed53..2c1cea4 100644 --- a/main/server/wifi_ota.cpp +++ b/main/server/wifi_ota.cpp @@ -20,7 +20,7 @@ #include "wifi_ota.h" #include "ota_relay.h" #include "health_monitor.h" -#include "debug_log.h" +#include "monitor/debug_log.h" static ota_wifi_mode_t s_wifi_mode = OTA_WIFI_MODE_AP; static bool s_ota_storage_done = false; @@ -39,9 +39,9 @@ static esp_err_t wifi_init_ap(void) s_wifi_mode = OTA_WIFI_MODE_AP; wifi_config_t wifi_config = {}; - memcpy(wifi_config.ap.ssid, WIFI_AP_SSID, strlen(WIFI_AP_SSID)); + memcpy(wifi_config.ap.ssid, WIFI_AP_SSID, strlen(WIFI_AP_SSID) + 1); wifi_config.ap.ssid_len = strlen(WIFI_AP_SSID); - memcpy(wifi_config.ap.password, WIFI_AP_PASSWORD, strlen(WIFI_AP_PASSWORD)); + memcpy(wifi_config.ap.password, WIFI_AP_PASSWORD, strlen(WIFI_AP_PASSWORD) + 1); wifi_config.ap.max_connection = WIFI_AP_MAX_CONNECT; wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; wifi_config.ap.channel = WIFI_AP_CHANNEL; @@ -69,8 +69,8 @@ static esp_err_t wifi_init_sta(void) wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; wifi_config.sta.pmf_cfg.capable = true; wifi_config.sta.pmf_cfg.required = false; - memcpy(wifi_config.sta.ssid, WIFI_STA_SSID, sizeof(wifi_config.sta.ssid)); - memcpy(wifi_config.sta.password, WIFI_STA_PASSWORD, sizeof(wifi_config.sta.password)); + memcpy(wifi_config.sta.ssid, WIFI_STA_SSID, strlen(WIFI_STA_SSID) + 1); + memcpy(wifi_config.sta.password, WIFI_STA_PASSWORD, strlen(WIFI_STA_PASSWORD) + 1); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); @@ -107,7 +107,7 @@ static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len) // 设置 STA 静态 IP static void wifi_set_static_ip(void) { -#if WIFI_STA_USE_STATIC_IP +#if WIFI_STA_STATIC_IP_IS_ENABLED esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); if (sta_netif) { ESP_ERROR_CHECK(esp_netif_dhcpc_stop(sta_netif)); @@ -405,7 +405,7 @@ void wifi_ota_task(void *pvParameters) ESP_ERROR_CHECK(esp_netif_init()); // STA 优先,失败自动切 AP -#ifdef CONFIG_WIFI_MODE_STA +#if CONFIG_WIFI_MODE_IS_STA err = wifi_init_sta(); if (err == ESP_OK) { char ip_str[16] = {0}; diff --git a/main/transport/protocol.cpp b/main/transport/protocol.cpp index 628401f..ce5a795 100644 --- a/main/transport/protocol.cpp +++ b/main/transport/protocol.cpp @@ -7,7 +7,7 @@ #include "esp_random.h" #include "app_config.h" #include "protocol.h" -#include "debug_log.h" +#include "monitor/debug_log.h" static bool s_protocol_initialized = false; diff --git a/main/transport/spi_master.cpp b/main/transport/spi_master.cpp index bda006d..6855fbb 100644 --- a/main/transport/spi_master.cpp +++ b/main/transport/spi_master.cpp @@ -10,7 +10,7 @@ #include "soc/spi_struct.h" #include "spi_master.h" -#include "debug_log.h" +#include "monitor/debug_log.h" spi_device_handle_t g_spi_device = NULL; diff --git a/sdkconfig.ci b/sdkconfig.ci old mode 100755 new mode 100644 diff --git a/sdkconfig.ci.c3eco7 b/sdkconfig.ci.c3eco7 old mode 100755 new mode 100644 diff --git a/sdkconfig.ci.esp32c2_xtal26m b/sdkconfig.ci.esp32c2_xtal26m old mode 100755 new mode 100644 diff --git a/sdkconfig.ci.esp32c2eco4_xtal26m b/sdkconfig.ci.esp32c2eco4_xtal26m old mode 100755 new mode 100644 diff --git a/sdkconfig.ci.wpa_psk b/sdkconfig.ci.wpa_psk old mode 100755 new mode 100644 diff --git a/tools/ota_host_tool.py b/tools/ota_host_tool.py index 870ff9f..05e6052 100644 --- a/tools/ota_host_tool.py +++ b/tools/ota_host_tool.py @@ -448,6 +448,8 @@ class OTAToolGUI: def _health_check(self): """查询 ESP32 健康状态""" + ip = self.esp_ip.get().strip() + self.ota_client = OTAClient(esp_ip=ip) ok, msg = self.ota_client.health_check() if ok: self._log_info("健康检查", msg) @@ -605,6 +607,8 @@ class OTAToolGUI: def _trigger_relay(self): """触发 OTA 中继""" + ip = self.esp_ip.get().strip() + self.ota_client = OTAClient(esp_ip=ip) ok, msg = self.ota_client.trigger_relay() if ok: self._log_info("触发", msg)