feat(gateway): add ESP-Touch smartconfig provisioning support and enhance network management

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Tony
2026-04-30 18:49:38 +08:00
parent 4ce3513dd2
commit ae4669e1b3
10 changed files with 338 additions and 43 deletions
@@ -2,6 +2,7 @@
#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
@@ -13,6 +14,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "gateway_runtime.hpp"
#include "lwip/sockets.h"
namespace gateway {
@@ -20,12 +22,14 @@ namespace gateway {
class GatewayController;
class DaliDomainService;
struct DaliRawFrame;
class GatewayRuntime;
struct GatewayNetworkServiceConfig {
bool wifi_enabled{true};
bool espnow_setup_enabled{true};
bool espnow_setup_startup_enabled{false};
bool smartconfig_enabled{true};
bool smartconfig_startup_enabled{false};
uint8_t smartconfig_timeout_sec{60};
bool http_enabled{true};
bool udp_enabled{true};
uint16_t http_port{80};
@@ -65,6 +69,8 @@ class GatewayNetworkService {
esp_err_t ensureNetworkStack();
esp_err_t startWifi();
esp_err_t startSetupAp();
esp_err_t startSmartconfig();
void stopSmartconfig();
esp_err_t startEspNow();
void stopEspNow();
esp_err_t addEspNowPeer(const uint8_t* mac, bool broadcast = false);
@@ -96,8 +102,12 @@ class GatewayNetworkService {
esp_netif_t* wifi_sta_netif_{nullptr};
esp_netif_t* wifi_ap_netif_{nullptr};
bool wifi_started_{false};
bool wifi_event_handlers_registered_{false};
bool setup_ap_started_{false};
bool espnow_started_{false};
bool smartconfig_started_{false};
bool smartconfig_event_handler_registered_{false};
std::optional<WirelessInfo> smartconfig_pending_wireless_;
bool espnow_connected_{false};
std::array<uint8_t, 6> espnow_peer_{};
TaskHandle_t boot_button_task_handle_{nullptr};
@@ -10,6 +10,7 @@
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_ip_addr.h"
#include "esp_smartconfig.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "lwip/inet.h"
@@ -91,6 +92,17 @@ std::string MacToHex(const uint8_t mac[6]) {
return std::string(out);
}
std::string BoundedString(const uint8_t* data, size_t len) {
if (data == nullptr) {
return {};
}
size_t actual_len = 0;
while (actual_len < len && data[actual_len] != 0) {
++actual_len;
}
return std::string(reinterpret_cast<const char*>(data), actual_len);
}
std::string LocalMacHex(wifi_interface_t interface) {
uint8_t mac[6] = {};
if (esp_wifi_get_mac(interface, mac) != ESP_OK) {
@@ -194,6 +206,11 @@ esp_err_t GatewayNetworkService::start() {
if (err != ESP_OK) {
return err;
}
} else if (config_.smartconfig_startup_enabled) {
err = startSmartconfig();
if (err != ESP_OK) {
return err;
}
} else if (config_.wifi_enabled) {
err = startWifi();
if (err != ESP_OK) {
@@ -261,6 +278,7 @@ esp_err_t GatewayNetworkService::startWifi() {
if (wifi_started_) {
return ESP_OK;
}
stopSmartconfig();
stopEspNow();
setup_ap_started_ = false;
@@ -279,18 +297,21 @@ esp_err_t GatewayNetworkService::startWifi() {
return err;
}
err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&GatewayNetworkService::HandleWifiEvent, this);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to register Wi-Fi event handler: %s", esp_err_to_name(err));
return err;
}
if (!wifi_event_handlers_registered_) {
err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&GatewayNetworkService::HandleWifiEvent, this);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to register Wi-Fi event handler: %s", esp_err_to_name(err));
return err;
}
err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&GatewayNetworkService::HandleWifiEvent, this);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to register IP event handler: %s", esp_err_to_name(err));
return err;
err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&GatewayNetworkService::HandleWifiEvent, this);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to register IP event handler: %s", esp_err_to_name(err));
return err;
}
wifi_event_handlers_registered_ = true;
}
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_set_storage(WIFI_STORAGE_RAM));
@@ -329,12 +350,14 @@ esp_err_t GatewayNetworkService::startWifi() {
}
wifi_started_ = true;
controller_.setWirelessSetupMode(false);
ESP_LOGI(kTag, "Wi-Fi STA started has_credentials=%d",
device_info.wlan.has_value() && !device_info.wlan->ssid.empty());
return ESP_OK;
}
esp_err_t GatewayNetworkService::startSetupAp() {
stopSmartconfig();
if (wifi_ap_netif_ == nullptr) {
wifi_ap_netif_ = esp_netif_create_default_wifi_ap();
if (wifi_ap_netif_ == nullptr) {
@@ -397,10 +420,73 @@ esp_err_t GatewayNetworkService::startSetupAp() {
wifi_started_ = true;
setup_ap_started_ = true;
controller_.setWirelessSetupMode(true);
ESP_LOGI(kTag, "setup AP started ssid=%s ip=192.168.3.1", kSetupApSsid);
return ESP_OK;
}
esp_err_t GatewayNetworkService::startSmartconfig() {
if (!config_.smartconfig_enabled) {
ESP_LOGW(kTag, "smartconfig requested but not supported");
return ESP_ERR_NOT_SUPPORTED;
}
if (smartconfig_started_) {
return ESP_OK;
}
config_.wifi_enabled = true;
if (setup_ap_started_) {
stopEspNow();
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
wifi_started_ = false;
setup_ap_started_ = false;
}
esp_err_t err = startWifi();
if (err != ESP_OK) {
return err;
}
if (!smartconfig_event_handler_registered_) {
err = esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID,
&GatewayNetworkService::HandleWifiEvent, this);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to register smartconfig event handler: %s", esp_err_to_name(err));
return err;
}
smartconfig_event_handler_registered_ = true;
}
err = esp_smartconfig_set_type(SC_TYPE_ESPTOUCH);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set smartconfig type: %s", esp_err_to_name(err));
return err;
}
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_esptouch_set_timeout(config_.smartconfig_timeout_sec));
smartconfig_start_config_t smartconfig = SMARTCONFIG_START_CONFIG_DEFAULT();
err = esp_smartconfig_start(&smartconfig);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to start smartconfig: %s", esp_err_to_name(err));
return err;
}
smartconfig_pending_wireless_.reset();
smartconfig_started_ = true;
controller_.setWirelessSetupMode(true);
ESP_LOGI(kTag, "ESP-Touch smartconfig started timeout=%us", config_.smartconfig_timeout_sec);
return ESP_OK;
}
void GatewayNetworkService::stopSmartconfig() {
if (!smartconfig_started_) {
return;
}
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_smartconfig_stop());
smartconfig_started_ = false;
smartconfig_pending_wireless_.reset();
}
esp_err_t GatewayNetworkService::startEspNow() {
if (espnow_started_) {
return ESP_OK;
@@ -629,6 +715,42 @@ void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t
return;
}
if (event_base == SC_EVENT) {
if (event_id == SC_EVENT_SCAN_DONE) {
ESP_LOGI(kTag, "smartconfig scan done");
} else if (event_id == SC_EVENT_FOUND_CHANNEL) {
ESP_LOGI(kTag, "smartconfig found channel");
} else if (event_id == SC_EVENT_GOT_SSID_PSWD && event_data != nullptr) {
auto* event = static_cast<smartconfig_event_got_ssid_pswd_t*>(event_data);
wifi_config_t wifi_config = {};
std::memcpy(wifi_config.sta.ssid, event->ssid, sizeof(wifi_config.sta.ssid));
std::memcpy(wifi_config.sta.password, event->password, sizeof(wifi_config.sta.password));
wifi_config.sta.bssid_set = event->bssid_set;
if (event->bssid_set) {
std::memcpy(wifi_config.sta.bssid, event->bssid, sizeof(wifi_config.sta.bssid));
}
WirelessInfo wireless;
wireless.ssid = BoundedString(event->ssid, sizeof(event->ssid));
wireless.password = BoundedString(event->password, sizeof(event->password));
uint8_t mac[6] = {};
if (esp_wifi_get_mac(WIFI_IF_STA, mac) == ESP_OK) {
wireless.mac = MacToHex(mac);
}
smartconfig_pending_wireless_ = wireless;
ESP_LOGI(kTag, "smartconfig got credentials ssid=%s", wireless.ssid.c_str());
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect());
} else if (event_id == SC_EVENT_SEND_ACK_DONE) {
stopSmartconfig();
controller_.setWirelessSetupMode(false);
ESP_LOGI(kTag, "smartconfig ACK sent");
}
return;
}
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
const auto info = runtime_.deviceInfo();
if (info.wlan.has_value() && !info.wlan->ssid.empty()) {
@@ -643,7 +765,7 @@ void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t
const bool has_credentials = !info.wlan->ssid.empty();
info.wlan->ip.clear();
runtime_.setWirelessInfo(std::move(*info.wlan));
if (has_credentials) {
if (has_credentials && !smartconfig_started_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect());
}
}
@@ -654,8 +776,13 @@ void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
char ip[16] = {0};
esp_ip4addr_ntoa(&event->ip_info.ip, ip, sizeof(ip));
WirelessInfo wireless = runtime_.deviceInfo().wlan.value_or(WirelessInfo{});
WirelessInfo wireless = smartconfig_pending_wireless_.value_or(
runtime_.deviceInfo().wlan.value_or(WirelessInfo{}));
wireless.ip = ip;
uint8_t mac[6] = {};
if (esp_wifi_get_mac(WIFI_IF_STA, mac) == ESP_OK) {
wireless.mac = MacToHex(mac);
}
runtime_.setWirelessInfo(std::move(wireless));
ESP_LOGI(kTag, "Wi-Fi got IP %s", ip);
}
@@ -903,7 +1030,9 @@ void GatewayNetworkService::handleGatewayNotification(const std::vector<uint8_t>
void GatewayNetworkService::handleWifiControl(uint8_t mode) {
if (mode == 0) {
config_.wifi_enabled = false;
stopSmartconfig();
stopEspNow();
controller_.setWirelessSetupMode(false);
if (wifi_started_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
@@ -923,8 +1052,13 @@ void GatewayNetworkService::handleWifiControl(uint8_t mode) {
ESP_ERROR_CHECK_WITHOUT_ABORT(startSetupAp());
return;
}
if (mode == 100) {
ESP_ERROR_CHECK_WITHOUT_ABORT(startSmartconfig());
return;
}
if (mode == 1) {
config_.wifi_enabled = true;
stopSmartconfig();
if (setup_ap_started_) {
stopEspNow();
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
@@ -994,6 +1128,7 @@ std::string GatewayNetworkService::gatewaySnapshotJson() {
cJSON* gw_info = cJSON_CreateObject();
if (gw_info != nullptr) {
cJSON_AddBoolToObject(gw_info, "setupMode", snapshot.setup_mode);
cJSON_AddBoolToObject(gw_info, "wlSetupMode", snapshot.wireless_setup_mode);
cJSON_AddBoolToObject(gw_info, "bleEnabled", snapshot.ble_enabled);
cJSON_AddBoolToObject(gw_info, "wifiEnabled", snapshot.wifi_enabled);
cJSON_AddBoolToObject(gw_info, "IPRouter", snapshot.ip_router_enabled);