feat(gateway): add ESP-Touch smartconfig provisioning support and enhance network management
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -12,10 +12,10 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
||||
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
|
||||
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
|
||||
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
|
||||
- `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway.
|
||||
- `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway.
|
||||
- `gateway_runtime/`: persistent runtime state, command queueing, and device info services.
|
||||
- `gateway_usb_setup/`: optional USB Serial/JTAG setup bridge; disabled by default so USB remains available for debug at boot.
|
||||
|
||||
## Current status
|
||||
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, and a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, Wi-Fi STA lifecycle, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing. Startup behavior is configured in `main/Kconfig.projbuild`: BLE is enabled by default, Wi-Fi STA and ESP-NOW setup mode are disabled by default, and the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, and a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing. Startup behavior is configured in `main/Kconfig.projbuild`: BLE is enabled by default, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, and the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
|
||||
@@ -293,6 +293,13 @@ config GATEWAY_ESPNOW_SETUP_SUPPORTED
|
||||
help
|
||||
Enables ESP-NOW setup ingress when setup AP mode is entered.
|
||||
|
||||
config GATEWAY_SMARTCONFIG_SUPPORTED
|
||||
bool "ESP-Touch smartconfig provisioning is supported"
|
||||
depends on GATEWAY_WIFI_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Enables Lua-compatible ESP-Touch smartconfig provisioning for Wi-Fi credentials.
|
||||
|
||||
config GATEWAY_START_ESPNOW_SETUP_ENABLED
|
||||
bool "Enter ESP-NOW setup mode at startup"
|
||||
depends on GATEWAY_ESPNOW_SETUP_SUPPORTED
|
||||
@@ -300,6 +307,21 @@ config GATEWAY_START_ESPNOW_SETUP_ENABLED
|
||||
help
|
||||
Starts the setup AP and ESP-NOW setup ingress immediately at boot. Disabled by default.
|
||||
|
||||
config GATEWAY_START_SMARTCONFIG_ENABLED
|
||||
bool "Start ESP-Touch smartconfig at startup"
|
||||
depends on GATEWAY_SMARTCONFIG_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Starts ESP-Touch provisioning at boot. Disabled by default so Wi-Fi stays off unless configured.
|
||||
|
||||
config GATEWAY_SMARTCONFIG_TIMEOUT_SEC
|
||||
int "ESP-Touch smartconfig timeout seconds"
|
||||
depends on GATEWAY_SMARTCONFIG_SUPPORTED
|
||||
range 15 255
|
||||
default 60
|
||||
help
|
||||
Timeout passed to ESP-IDF smartconfig before provisioning restarts internally.
|
||||
|
||||
choice GATEWAY_USB_STARTUP_MODE
|
||||
prompt "USB Serial/JTAG startup mode"
|
||||
default GATEWAY_USB_STARTUP_DEBUG_JTAG
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
#define CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS 20
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC
|
||||
#define CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC 60
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr const char* kProjectName = "DALI_485_Gateway";
|
||||
constexpr const char* kProjectVersion = "0.1.0";
|
||||
@@ -94,6 +98,18 @@ constexpr bool kEspnowSetupStartupEnabled = true;
|
||||
constexpr bool kEspnowSetupStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED
|
||||
constexpr bool kSmartconfigSupported = true;
|
||||
#else
|
||||
constexpr bool kSmartconfigSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED
|
||||
constexpr bool kSmartconfigStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kSmartconfigStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
constexpr bool kUsbSetupStartupEnabled = true;
|
||||
#else
|
||||
@@ -351,6 +367,10 @@ extern "C" void app_main(void) {
|
||||
network_config.espnow_setup_enabled = profile.enable_espnow;
|
||||
network_config.espnow_setup_startup_enabled =
|
||||
profile.enable_espnow && kEspnowSetupStartupEnabled;
|
||||
network_config.smartconfig_enabled = profile.enable_wifi && kSmartconfigSupported;
|
||||
network_config.smartconfig_startup_enabled =
|
||||
profile.enable_wifi && kSmartconfigSupported && kSmartconfigStartupEnabled;
|
||||
network_config.smartconfig_timeout_sec = CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC;
|
||||
#ifdef CONFIG_GATEWAY_NETWORK_HTTP_ENABLED
|
||||
network_config.http_enabled = true;
|
||||
#else
|
||||
|
||||
@@ -628,7 +628,10 @@ CONFIG_GATEWAY_START_BLE_ENABLED=y
|
||||
CONFIG_GATEWAY_WIFI_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_WIFI_STA_ENABLED is not set
|
||||
CONFIG_GATEWAY_ESPNOW_SETUP_SUPPORTED=y
|
||||
CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
|
||||
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
|
||||
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||
# end of Gateway Startup Services
|
||||
|
||||
@@ -46,6 +46,7 @@ struct GatewayChannelSnapshot {
|
||||
|
||||
struct GatewayControllerSnapshot {
|
||||
bool setup_mode{false};
|
||||
bool wireless_setup_mode{false};
|
||||
bool ble_enabled{false};
|
||||
bool wifi_enabled{false};
|
||||
bool ip_router_enabled{false};
|
||||
@@ -73,6 +74,8 @@ class GatewayController {
|
||||
void addGatewayNameSink(GatewayNameSink sink);
|
||||
|
||||
bool setupMode() const;
|
||||
bool wirelessSetupMode() const;
|
||||
void setWirelessSetupMode(bool enabled);
|
||||
bool bleEnabled() const;
|
||||
bool wifiEnabled() const;
|
||||
bool ipRouterEnabled() const;
|
||||
@@ -174,6 +177,7 @@ class GatewayController {
|
||||
std::map<uint8_t, SceneStore> scenes_;
|
||||
std::map<uint8_t, GroupStore> groups_;
|
||||
bool setup_mode_{false};
|
||||
bool wireless_setup_mode_{false};
|
||||
bool ble_enabled_{false};
|
||||
bool wifi_enabled_{false};
|
||||
bool ip_router_enabled_{true};
|
||||
|
||||
@@ -185,6 +185,14 @@ bool GatewayController::setupMode() const {
|
||||
return setup_mode_;
|
||||
}
|
||||
|
||||
bool GatewayController::wirelessSetupMode() const {
|
||||
return wireless_setup_mode_;
|
||||
}
|
||||
|
||||
void GatewayController::setWirelessSetupMode(bool enabled) {
|
||||
wireless_setup_mode_ = enabled;
|
||||
}
|
||||
|
||||
bool GatewayController::bleEnabled() const {
|
||||
return ble_enabled_;
|
||||
}
|
||||
@@ -200,6 +208,7 @@ bool GatewayController::ipRouterEnabled() const {
|
||||
GatewayControllerSnapshot GatewayController::snapshot() {
|
||||
GatewayControllerSnapshot out;
|
||||
out.setup_mode = setup_mode_;
|
||||
out.wireless_setup_mode = wireless_setup_mode_;
|
||||
out.ble_enabled = ble_enabled_;
|
||||
out.wifi_enabled = wifi_enabled_;
|
||||
out.ip_router_enabled = ip_router_enabled_;
|
||||
@@ -302,8 +311,13 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
case 0x04:
|
||||
if (addr == 0) {
|
||||
wifi_enabled_ = false;
|
||||
} else if (addr == 1 || addr == 101) {
|
||||
wireless_setup_mode_ = false;
|
||||
} else if (addr == 1) {
|
||||
wifi_enabled_ = true;
|
||||
wireless_setup_mode_ = false;
|
||||
} else if (addr == 100 || addr == 101) {
|
||||
wifi_enabled_ = true;
|
||||
wireless_setup_mode_ = true;
|
||||
}
|
||||
for (const auto& sink : wifi_state_sinks_) {
|
||||
sink(addr);
|
||||
@@ -317,12 +331,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
if (setup_mode_ && config_.setup_supported) {
|
||||
feature |= 0x01;
|
||||
}
|
||||
if (config_.ble_supported) {
|
||||
if (wireless_setup_mode_ && config_.wifi_supported) {
|
||||
feature |= 0x02;
|
||||
}
|
||||
if (config_.wifi_supported) {
|
||||
feature |= 0x04;
|
||||
}
|
||||
if (config_.ip_router_supported && ip_router_enabled_) {
|
||||
feature |= 0x08;
|
||||
}
|
||||
@@ -593,6 +604,9 @@ bool GatewayController::setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bo
|
||||
if (scene_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (scene_data->enabled == enabled) {
|
||||
return true;
|
||||
}
|
||||
scene_data->enabled = enabled;
|
||||
return saveScene(gateway_id, scene_id);
|
||||
}
|
||||
@@ -604,21 +618,29 @@ bool GatewayController::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uin
|
||||
if (scene_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
scene_data->brightness = std::min<uint8_t>(brightness, 254);
|
||||
scene_data->color_mode = std::min<uint8_t>(color_mode, 2);
|
||||
if (scene_data->color_mode == 0) {
|
||||
scene_data->data1 = data1;
|
||||
scene_data->data2 = data2;
|
||||
scene_data->data3 = 0;
|
||||
} else if (scene_data->color_mode == 1) {
|
||||
scene_data->data1 = data1;
|
||||
scene_data->data2 = data2;
|
||||
scene_data->data3 = data3;
|
||||
} else {
|
||||
scene_data->data1 = 0;
|
||||
scene_data->data2 = 0;
|
||||
scene_data->data3 = 0;
|
||||
const uint8_t next_brightness = std::min<uint8_t>(brightness, 254);
|
||||
const uint8_t next_color_mode = std::min<uint8_t>(color_mode, 2);
|
||||
uint8_t next_data1 = 0;
|
||||
uint8_t next_data2 = 0;
|
||||
uint8_t next_data3 = 0;
|
||||
if (next_color_mode == 0) {
|
||||
next_data1 = data1;
|
||||
next_data2 = data2;
|
||||
} else if (next_color_mode == 1) {
|
||||
next_data1 = data1;
|
||||
next_data2 = data2;
|
||||
next_data3 = data3;
|
||||
}
|
||||
if (scene_data->brightness == next_brightness && scene_data->color_mode == next_color_mode &&
|
||||
scene_data->data1 == next_data1 && scene_data->data2 == next_data2 &&
|
||||
scene_data->data3 == next_data3) {
|
||||
return true;
|
||||
}
|
||||
scene_data->brightness = next_brightness;
|
||||
scene_data->color_mode = next_color_mode;
|
||||
scene_data->data1 = next_data1;
|
||||
scene_data->data2 = next_data2;
|
||||
scene_data->data3 = next_data3;
|
||||
return saveScene(gateway_id, scene_id);
|
||||
}
|
||||
|
||||
@@ -627,7 +649,11 @@ bool GatewayController::setSceneName(uint8_t gateway_id, uint8_t scene_id, std::
|
||||
if (scene_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
scene_data->name = NormalizeName(name);
|
||||
const auto normalized = NormalizeName(name);
|
||||
if (scene_data->name == normalized) {
|
||||
return true;
|
||||
}
|
||||
scene_data->name = normalized;
|
||||
return saveSceneName(gateway_id, scene_id, scene_data->name);
|
||||
}
|
||||
|
||||
@@ -636,6 +662,13 @@ bool GatewayController::deleteScene(uint8_t gateway_id, uint8_t scene_id) {
|
||||
if (scene_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const bool already_default = !scene_data->enabled && scene_data->brightness == 254 &&
|
||||
scene_data->color_mode == 2 && scene_data->data1 == 0 &&
|
||||
scene_data->data2 == 0 && scene_data->data3 == 0 &&
|
||||
scene_data->name.empty();
|
||||
if (already_default) {
|
||||
return true;
|
||||
}
|
||||
*scene_data = InternalScene{};
|
||||
deleteSceneStorage(gateway_id, scene_id);
|
||||
deleteSceneNameStorage(gateway_id, scene_id);
|
||||
@@ -688,6 +721,9 @@ bool GatewayController::setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bo
|
||||
if (group_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (group_data->enabled == enabled) {
|
||||
return true;
|
||||
}
|
||||
group_data->enabled = enabled;
|
||||
return saveGroup(gateway_id, group_id);
|
||||
}
|
||||
@@ -698,8 +734,13 @@ bool GatewayController::setGroupDetail(uint8_t gateway_id, uint8_t group_id, uin
|
||||
if (group_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
group_data->target_type = normalizeGroupTargetType(target_type);
|
||||
group_data->target_value = normalizeGroupTargetValue(group_data->target_type, target_value);
|
||||
const uint8_t next_target_type = normalizeGroupTargetType(target_type);
|
||||
const uint8_t next_target_value = normalizeGroupTargetValue(next_target_type, target_value);
|
||||
if (group_data->target_type == next_target_type && group_data->target_value == next_target_value) {
|
||||
return true;
|
||||
}
|
||||
group_data->target_type = next_target_type;
|
||||
group_data->target_value = next_target_value;
|
||||
return saveGroup(gateway_id, group_id);
|
||||
}
|
||||
|
||||
@@ -708,7 +749,11 @@ bool GatewayController::setGroupName(uint8_t gateway_id, uint8_t group_id, std::
|
||||
if (group_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
group_data->name = NormalizeName(name);
|
||||
const auto normalized = NormalizeName(name);
|
||||
if (group_data->name == normalized) {
|
||||
return true;
|
||||
}
|
||||
group_data->name = normalized;
|
||||
return saveGroupName(gateway_id, group_id, group_data->name);
|
||||
}
|
||||
|
||||
@@ -717,6 +762,11 @@ bool GatewayController::deleteGroup(uint8_t gateway_id, uint8_t group_id) {
|
||||
if (group_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const bool already_default = !group_data->enabled && group_data->target_type == 2 &&
|
||||
group_data->target_value == 0 && group_data->name.empty();
|
||||
if (already_default) {
|
||||
return true;
|
||||
}
|
||||
*group_data = InternalGroup{};
|
||||
deleteGroupStorage(gateway_id, group_id);
|
||||
deleteGroupNameStorage(gateway_id, group_id);
|
||||
@@ -1125,7 +1175,10 @@ bool GatewayController::eraseKey(std::string_view key) {
|
||||
return false;
|
||||
}
|
||||
const esp_err_t err = nvs_erase_key(storage_, std::string(key).c_str());
|
||||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
|
||||
if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
return true;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
return nvs_commit(storage_) == ESP_OK;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -127,6 +128,7 @@ class GatewayRuntime {
|
||||
GatewaySettingsStore settings_;
|
||||
std::optional<std::vector<uint8_t>> current_command_;
|
||||
std::deque<std::vector<uint8_t>> pending_commands_;
|
||||
mutable std::map<uint8_t, std::string> gateway_names_;
|
||||
size_t gateway_count_{0};
|
||||
bool ble_enabled_{false};
|
||||
CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone};
|
||||
|
||||
@@ -119,7 +119,14 @@ std::optional<std::string> GatewaySettingsStore::getWifiPassword() const {
|
||||
|
||||
bool GatewaySettingsStore::setWifiCredentials(std::string_view ssid,
|
||||
std::string_view password) {
|
||||
return writeString(kWifiSsidKey, ssid) && writeString(kWifiPasswordKey, password);
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const esp_err_t ssid_err = nvs_set_str(handle_, kWifiSsidKey, std::string(ssid).c_str());
|
||||
const esp_err_t password_err =
|
||||
nvs_set_str(handle_, kWifiPasswordKey, std::string(password).c_str());
|
||||
return ssid_err == ESP_OK && password_err == ESP_OK && nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::clearWifiCredentials() {
|
||||
@@ -129,12 +136,17 @@ bool GatewaySettingsStore::clearWifiCredentials() {
|
||||
|
||||
esp_err_t ssid_err = nvs_erase_key(handle_, kWifiSsidKey);
|
||||
esp_err_t password_err = nvs_erase_key(handle_, kWifiPasswordKey);
|
||||
const bool ssid_missing = ssid_err == ESP_ERR_NVS_NOT_FOUND;
|
||||
const bool password_missing = password_err == ESP_ERR_NVS_NOT_FOUND;
|
||||
if (ssid_err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
ssid_err = ESP_OK;
|
||||
}
|
||||
if (password_err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
password_err = ESP_OK;
|
||||
}
|
||||
if (ssid_missing && password_missing) {
|
||||
return true;
|
||||
}
|
||||
return ssid_err == ESP_OK && password_err == ESP_OK && nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
@@ -348,10 +360,16 @@ void GatewayRuntime::setWirelessInfo(WirelessInfo info) {
|
||||
}
|
||||
|
||||
bool GatewayRuntime::clearWirelessInfo() {
|
||||
bool had_credentials = false;
|
||||
{
|
||||
LockGuard guard(command_lock_);
|
||||
had_credentials = wireless_info_.has_value() &&
|
||||
(!wireless_info_->ssid.empty() || !wireless_info_->password.empty());
|
||||
wireless_info_.reset();
|
||||
}
|
||||
if (!had_credentials) {
|
||||
return true;
|
||||
}
|
||||
return settings_.clearWifiCredentials();
|
||||
}
|
||||
|
||||
@@ -379,19 +397,37 @@ GatewayDeviceInfo GatewayRuntime::deviceInfo() const {
|
||||
}
|
||||
|
||||
bool GatewayRuntime::bleEnabled() const {
|
||||
LockGuard guard(command_lock_);
|
||||
return ble_enabled_;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setBleEnabled(bool enabled) {
|
||||
{
|
||||
LockGuard guard(command_lock_);
|
||||
if (ble_enabled_ == enabled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!settings_.setBleEnabled(enabled)) {
|
||||
return false;
|
||||
}
|
||||
LockGuard guard(command_lock_);
|
||||
ble_enabled_ = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const {
|
||||
return settings_.getGatewayName(gateway_id, defaultGatewayName(gateway_id));
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = gateway_names_.find(gateway_id);
|
||||
if (cached != gateway_names_.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
auto name = settings_.getGatewayName(gateway_id, defaultGatewayName(gateway_id));
|
||||
if (name.size() > kMaxGatewayNameBytes) {
|
||||
name.resize(kMaxGatewayNameBytes);
|
||||
}
|
||||
gateway_names_[gateway_id] = name;
|
||||
return name;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setGatewayName(uint8_t gateway_id, std::string_view name) {
|
||||
@@ -403,7 +439,17 @@ bool GatewayRuntime::setGatewayName(uint8_t gateway_id, std::string_view name) {
|
||||
normalized = defaultGatewayName(gateway_id);
|
||||
}
|
||||
|
||||
return settings_.setGatewayName(gateway_id, normalized);
|
||||
if (gatewayName(gateway_id) == normalized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!settings_.setGatewayName(gateway_id, normalized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockGuard guard(command_lock_);
|
||||
gateway_names_[gateway_id] = normalized;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::gatewaySerialHex(uint8_t gateway_id) const {
|
||||
|
||||
Reference in New Issue
Block a user