From 541a1c2d087bd0b069ed7860bfd5ce21fd4a0471 Mon Sep 17 00:00:00 2001 From: JMTamayo Date: Sat, 20 Jun 2026 18:08:09 -0500 Subject: [PATCH 1/4] build: enable Wi-Fi and enlarge the app partition Require the esp_wifi/esp_event/esp_netif/esp_timer components and enable software BLE/Wi-Fi coexistence. Add a custom partition table with a larger app partition, since the BLE + Wi-Fi binary exceeds the default 1 MB; nvs is kept at 0x9000/0x6000 to preserve the host provisioner contract. --- firmware/partitions.csv | 6 ++++++ firmware/platformio.ini | 1 + firmware/sdkconfig.defaults | 5 +++++ firmware/src/CMakeLists.txt | 3 ++- 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 firmware/partitions.csv diff --git a/firmware/partitions.csv b/firmware/partitions.csv new file mode 100644 index 0000000..9ab7038 --- /dev/null +++ b/firmware/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size +# NVS kept at 0x9000 / 0x6000 to preserve the host provisioner contract. +# Larger app partition to fit the BLE + Wi-Fi binary. +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x300000, diff --git a/firmware/platformio.ini b/firmware/platformio.ini index 6871df4..de5c234 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -16,3 +16,4 @@ platform = espressif32@6.12.0 board = esp32-c3-devkitm-1 framework = espidf monitor_speed = 115200 +board_build.partitions = partitions.csv diff --git a/firmware/sdkconfig.defaults b/firmware/sdkconfig.defaults index 9f5e576..b0a2b26 100644 --- a/firmware/sdkconfig.defaults +++ b/firmware/sdkconfig.defaults @@ -26,3 +26,8 @@ CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y CONFIG_BT_NIMBLE_ROLE_CENTRAL=n CONFIG_BT_NIMBLE_ROLE_OBSERVER=n + +# --- Wi-Fi station + BLE/Wi-Fi software coexistence -------------------------- +# The connectivity/wifi module runs in station mode alongside BLE provisioning; +# ESP32-C3 shares one radio, so software coexistence must be enabled. +CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y diff --git a/firmware/src/CMakeLists.txt b/firmware/src/CMakeLists.txt index 370a387..c5b16a0 100644 --- a/firmware/src/CMakeLists.txt +++ b/firmware/src/CMakeLists.txt @@ -1,2 +1,3 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) -idf_component_register(SRCS ${app_sources} REQUIRES bt) +idf_component_register(SRCS ${app_sources} + REQUIRES bt esp_wifi esp_event esp_netif esp_timer) From 4cd2ed343cc1281ccf69901499eb44e7ccb4fc5b Mon Sep 17 00:00:00 2001 From: JMTamayo Date: Sat, 20 Jun 2026 18:08:59 -0500 Subject: [PATCH 2/4] feat(connectivity): add Wi-Fi station module Add connectivity/wifi following the hexagonal structure: the IWifiStation port, WifiService (reads credentials from IWifiConfig), the esp_wifi adapter and the Wifi facade. Connects asynchronously in station mode using the stored credentials, reports connection status and reconnects indefinitely on drop. Caps TX power after start to work around the ESP32-C3 SuperMini antenna. Wire it in main to start after provisioning. --- firmware/include/config.hpp | 5 + .../wifi/application/wifi_service.cpp | 42 ++++ .../wifi/application/wifi_service.hpp | 42 ++++ .../wifi/domain/i_wifi_station.hpp | 61 ++++++ .../wifi/infrastructure/esp_wifi_station.cpp | 198 ++++++++++++++++++ .../wifi/infrastructure/esp_wifi_station.hpp | 69 ++++++ firmware/src/connectivity/wifi/tag.hpp | 7 + firmware/src/connectivity/wifi/wifi.cpp | 30 +++ firmware/src/connectivity/wifi/wifi.hpp | 66 ++++++ firmware/src/main.cpp | 18 +- 10 files changed, 535 insertions(+), 3 deletions(-) create mode 100644 firmware/src/connectivity/wifi/application/wifi_service.cpp create mode 100644 firmware/src/connectivity/wifi/application/wifi_service.hpp create mode 100644 firmware/src/connectivity/wifi/domain/i_wifi_station.hpp create mode 100644 firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.cpp create mode 100644 firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.hpp create mode 100644 firmware/src/connectivity/wifi/tag.hpp create mode 100644 firmware/src/connectivity/wifi/wifi.cpp create mode 100644 firmware/src/connectivity/wifi/wifi.hpp diff --git a/firmware/include/config.hpp b/firmware/include/config.hpp index 404859e..fc9625f 100644 --- a/firmware/include/config.hpp +++ b/firmware/include/config.hpp @@ -25,6 +25,11 @@ namespace wifi { constexpr const char *SSID_KEY = "wifi_ssid"; constexpr const char *PASSWORD_KEY = "wifi_password"; +constexpr std::uint32_t RECONNECT_DELAY_MS = 5000; + +// Max WiFi TX power in 0.25 dBm units (34 = 8.5 dBm); lowered for the SuperMini antenna. +constexpr std::int8_t MAX_TX_POWER = 34; + } // namespace wifi namespace mqtt { diff --git a/firmware/src/connectivity/wifi/application/wifi_service.cpp b/firmware/src/connectivity/wifi/application/wifi_service.cpp new file mode 100644 index 0000000..e5bb490 --- /dev/null +++ b/firmware/src/connectivity/wifi/application/wifi_service.cpp @@ -0,0 +1,42 @@ +#include "connectivity/wifi/application/wifi_service.hpp" + +#include +#include + +#include "connectivity/wifi/tag.hpp" + +namespace connectivity::wifi { + +WifiService::WifiService(IWifiStation &station, config::IWifiConfig &config) + : _station(station), _config(config) {} + +esp_err_t WifiService::connect() { + std::string ssid; + esp_err_t err = _config.getSsid(ssid); + if (err == ESP_ERR_NVS_NOT_FOUND || (err == ESP_OK && ssid.empty())) { + ESP_LOGW(TAG, "no WiFi credentials; provision via BLE first"); + + return ESP_ERR_INVALID_STATE; + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to read ssid: %s", esp_err_to_name(err)); + + return err; + } + + std::string password; + err = _config.getPassword(password); + if (err == ESP_ERR_NVS_NOT_FOUND) { + password.clear(); + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to read password: %s", esp_err_to_name(err)); + + return err; + } + + return _station.connect(ssid, password); +} + +bool WifiService::isConnected() const { return _station.isConnected(); } + +} // namespace connectivity::wifi diff --git a/firmware/src/connectivity/wifi/application/wifi_service.hpp b/firmware/src/connectivity/wifi/application/wifi_service.hpp new file mode 100644 index 0000000..d356730 --- /dev/null +++ b/firmware/src/connectivity/wifi/application/wifi_service.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "connectivity/config/domain/i_wifi_config.hpp" +#include "connectivity/wifi/domain/i_wifi_station.hpp" + +namespace connectivity::wifi { + +/** + * @brief Application service that drives the WiFi station from stored config. + * + * Reads the credentials from the configuration store and asks the station to + * connect. The radio handles reconnection on its own with the stored config. + */ +class WifiService { + public: + /** + * @brief Builds the service over a station and a configuration store. + * + * @param station WiFi station port. Must outlive this instance. + * @param config WiFi configuration store. Must outlive this instance. + */ + WifiService(IWifiStation &station, config::IWifiConfig &config); + + /** + * @brief Connects using the credentials in storage. + * + * @return ESP_OK if the attempt was launched, ESP_ERR_INVALID_STATE if there + * are no credentials yet, or an ESP-IDF error code on failure. + */ + esp_err_t connect(); + + /** + * @brief Whether the station currently holds a connection. + */ + bool isConnected() const; + + private: + IWifiStation &_station; + config::IWifiConfig &_config; +}; + +} // namespace connectivity::wifi diff --git a/firmware/src/connectivity/wifi/domain/i_wifi_station.hpp b/firmware/src/connectivity/wifi/domain/i_wifi_station.hpp new file mode 100644 index 0000000..e67c647 --- /dev/null +++ b/firmware/src/connectivity/wifi/domain/i_wifi_station.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include +#include + +namespace connectivity::wifi { + +/// Connection state of the WiFi station. +enum class Status { Disconnected, Connecting, Connected }; + +/// Notified on every station status transition. +using StatusHandler = std::function; + +/** + * @brief Port for a WiFi station radio. + * + * Abstracts the connection mechanics (esp_wifi here) behind start/connect/stop + * operations and an asynchronous status callback. Keeps the radio stack out of + * the application layer. + */ +class IWifiStation { + public: + virtual ~IWifiStation() = default; + + /** + * @brief One-time initialization of the network stack and event handlers. + * + * @param onStatus Callback invoked on every status transition. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t begin(StatusHandler onStatus) = 0; + + /** + * @brief Connects to the given network (non-blocking). + * + * On drop, the station retries on its own. Status is reported via the + * callback registered in @c begin(). + * + * @param ssid Network SSID. + * @param password Network password (empty for an open network). + * @return ESP_OK if the attempt was launched, or an ESP-IDF error. + */ + virtual esp_err_t connect(const std::string &ssid, + const std::string &password) = 0; + + /** + * @brief Stops auto-reconnect and disconnects from the network. + * + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t disconnect() = 0; + + /** + * @brief Whether the station currently holds an IP-level connection. + */ + virtual bool isConnected() const = 0; +}; + +} // namespace connectivity::wifi diff --git a/firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.cpp b/firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.cpp new file mode 100644 index 0000000..643316d --- /dev/null +++ b/firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.cpp @@ -0,0 +1,198 @@ +#include "connectivity/wifi/infrastructure/esp_wifi_station.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "connectivity/wifi/tag.hpp" + +namespace connectivity::wifi { + +namespace { + +// Single station per device, so the esp_wifi/event state lives at file scope. +StatusHandler g_onStatus; +bool g_connected = false; +bool g_initialized = false; +bool g_autoReconnect = false; +std::uint32_t g_reconnectDelayMs = 5000; +std::int8_t g_maxTxPower = 80; +esp_timer_handle_t g_reconnectTimer = nullptr; + +void notify(Status status) { + if (g_onStatus) { + g_onStatus(status); + } +} + +void reconnectTimerCb(void *) { + ESP_LOGI(TAG, "retrying connection"); + notify(Status::Connecting); + esp_wifi_connect(); +} + +void scheduleReconnect() { + if (g_reconnectTimer == nullptr) { + return; + } + + esp_timer_stop(g_reconnectTimer); + esp_timer_start_once(g_reconnectTimer, + static_cast(g_reconnectDelayMs) * 1000); +} + +void wifiEventHandler(void *, esp_event_base_t base, std::int32_t id, void *) { + if (base != WIFI_EVENT) { + return; + } + + if (id == WIFI_EVENT_STA_DISCONNECTED) { + g_connected = false; + notify(Status::Disconnected); + if (g_autoReconnect) { + ESP_LOGW(TAG, "disconnected; retrying in %u ms", + static_cast(g_reconnectDelayMs)); + scheduleReconnect(); + } + } +} + +void ipEventHandler(void *, esp_event_base_t base, std::int32_t id, + void *data) { + if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) { + auto *event = static_cast(data); + ESP_LOGI(TAG, "connected; got IP " IPSTR, IP2STR(&event->ip_info.ip)); + g_connected = true; + notify(Status::Connected); + } +} + +} // namespace + +EspWifiStation::EspWifiStation(std::uint32_t reconnectDelayMs, + std::int8_t maxTxPower) { + g_reconnectDelayMs = reconnectDelayMs; + g_maxTxPower = maxTxPower; +} + +esp_err_t EspWifiStation::begin(StatusHandler onStatus) { + if (g_initialized) { + return ESP_OK; + } + + g_onStatus = std::move(onStatus); + + esp_err_t err = esp_netif_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err)); + + return err; + } + + err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", + esp_err_to_name(err)); + + return err; + } + + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + err = esp_wifi_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err)); + + return err; + } + + err = esp_event_handler_instance_register( + WIFI_EVENT, ESP_EVENT_ANY_ID, &wifiEventHandler, nullptr, nullptr); + if (err != ESP_OK) { + return err; + } + + err = esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, + &ipEventHandler, nullptr, nullptr); + if (err != ESP_OK) { + return err; + } + + esp_timer_create_args_t timerArgs = {}; + timerArgs.callback = &reconnectTimerCb; + timerArgs.name = "wifi_reconnect"; + err = esp_timer_create(&timerArgs, &g_reconnectTimer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_timer_create failed: %s", esp_err_to_name(err)); + + return err; + } + + err = esp_wifi_set_mode(WIFI_MODE_STA); + if (err != ESP_OK) { + return err; + } + + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err)); + + return err; + } + + // Cap TX power after start (antenna workaround for ESP32-C3 SuperMini). + err = esp_wifi_set_max_tx_power(g_maxTxPower); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_set_max_tx_power failed: %s", esp_err_to_name(err)); + } else { + ESP_LOGI(TAG, "max tx power set to %d (0.25 dBm units)", g_maxTxPower); + } + + g_initialized = true; + ESP_LOGI(TAG, "wifi station started"); + + return ESP_OK; +} + +esp_err_t EspWifiStation::connect(const std::string &ssid, + const std::string &password) { + wifi_config_t cfg = {}; + std::strncpy(reinterpret_cast(cfg.sta.ssid), ssid.c_str(), + sizeof(cfg.sta.ssid) - 1); + std::strncpy(reinterpret_cast(cfg.sta.password), password.c_str(), + sizeof(cfg.sta.password) - 1); + cfg.sta.threshold.authmode = + password.empty() ? WIFI_AUTH_OPEN : WIFI_AUTH_WPA2_PSK; + + esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_set_config failed: %s", esp_err_to_name(err)); + + return err; + } + + g_autoReconnect = true; + ESP_LOGI(TAG, "connecting to '%s'", ssid.c_str()); + notify(Status::Connecting); + + return esp_wifi_connect(); +} + +esp_err_t EspWifiStation::disconnect() { + g_autoReconnect = false; + if (g_reconnectTimer != nullptr) { + esp_timer_stop(g_reconnectTimer); + } + g_connected = false; + + return esp_wifi_disconnect(); +} + +bool EspWifiStation::isConnected() const { return g_connected; } + +} // namespace connectivity::wifi diff --git a/firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.hpp b/firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.hpp new file mode 100644 index 0000000..49156d0 --- /dev/null +++ b/firmware/src/connectivity/wifi/infrastructure/esp_wifi_station.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include "connectivity/wifi/domain/i_wifi_station.hpp" + +namespace connectivity::wifi { + +/** + * @brief esp_wifi station adapter. + * + * Implements @c IWifiStation in station mode. There is a single station per + * device, so the esp_wifi/event-loop state lives at file scope in the + * implementation; this keeps the WiFi stack headers out of the rest of the + * codebase. On disconnect it retries indefinitely with a fixed delay. + */ +class EspWifiStation : public IWifiStation { + public: + /** + * @brief Builds the adapter. + * + * @param reconnectDelayMs Delay between reconnection attempts. + * @param maxTxPower Maximum TX power in 0.25 dBm units, applied after + * the station starts (antenna workaround). + */ + EspWifiStation(std::uint32_t reconnectDelayMs, std::int8_t maxTxPower); + + /** + * @brief Initializes the network stack and starts the station. + * + * Sets up esp_netif, the default event loop, the WiFi driver and the WiFi/IP + * event handlers, then starts the station in STA mode. Idempotent: a no-op if + * already initialized. + * + * @param onStatus Callback invoked on every status transition. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + esp_err_t begin(StatusHandler onStatus) override; + + /** + * @brief Launches a non-blocking connection attempt. + * + * Stores the credentials in the driver and calls esp_wifi_connect(); the + * outcome arrives asynchronously via the status callback (Connected on + * GOT_IP, Disconnected otherwise). Enables auto-reconnect. + * + * @param ssid Network SSID. + * @param password Network password (empty selects an open network). + * @return ESP_OK if the attempt was launched, or an ESP-IDF error. + */ + esp_err_t connect(const std::string &ssid, + const std::string &password) override; + + /** + * @brief Disables auto-reconnect, cancels any pending retry and disconnects. + * + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + esp_err_t disconnect() override; + + /** + * @brief Whether the station currently holds an IP-level connection. + * + * Set when an IP is acquired and cleared on disconnect. + */ + bool isConnected() const override; +}; + +} // namespace connectivity::wifi diff --git a/firmware/src/connectivity/wifi/tag.hpp b/firmware/src/connectivity/wifi/tag.hpp new file mode 100644 index 0000000..fd79002 --- /dev/null +++ b/firmware/src/connectivity/wifi/tag.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace connectivity::wifi { + +constexpr const char *TAG = "connectivity/wifi"; + +} // namespace connectivity::wifi diff --git a/firmware/src/connectivity/wifi/wifi.cpp b/firmware/src/connectivity/wifi/wifi.cpp new file mode 100644 index 0000000..506e516 --- /dev/null +++ b/firmware/src/connectivity/wifi/wifi.cpp @@ -0,0 +1,30 @@ +#include "connectivity/wifi/wifi.hpp" + +namespace connectivity::wifi { + +Wifi::Wifi(config::IWifiConfig &config, std::uint32_t reconnectDelayMs, + std::int8_t maxTxPower) + : _station(reconnectDelayMs, maxTxPower), _service(_station, config) {} + +esp_err_t Wifi::start() { + esp_err_t err = _station.begin([this](Status status) { + if (_userHandler) { + _userHandler(status); + } + }); + if (err != ESP_OK) { + return err; + } + + return _service.connect(); +} + +esp_err_t Wifi::stop() { return _station.disconnect(); } + +bool Wifi::isConnected() const { return _service.isConnected(); } + +void Wifi::onStatusChange(StatusHandler handler) { + _userHandler = std::move(handler); +} + +} // namespace connectivity::wifi diff --git a/firmware/src/connectivity/wifi/wifi.hpp b/firmware/src/connectivity/wifi/wifi.hpp new file mode 100644 index 0000000..5e4a0b1 --- /dev/null +++ b/firmware/src/connectivity/wifi/wifi.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include "connectivity/config/domain/i_wifi_config.hpp" +#include "connectivity/wifi/application/wifi_service.hpp" +#include "connectivity/wifi/domain/i_wifi_station.hpp" +#include "connectivity/wifi/infrastructure/esp_wifi_station.hpp" + +namespace connectivity::wifi { + +/** + * @brief Module facade for WiFi station connectivity. + * + * Composition root of the wifi module: owns the esp_wifi adapter and the + * application service, wiring them to the WiFi configuration store. Connects + * asynchronously using the stored credentials and keeps the link up with + * indefinite reconnection. Exposes the connection status for the application. + */ +class Wifi { + public: + /** + * @brief Builds the WiFi module. + * + * @param config WiFi configuration store. Must outlive this + * instance. + * @param reconnectDelayMs Delay between reconnection attempts. + * @param maxTxPower Maximum TX power in 0.25 dBm units (antenna + * workaround for ESP32-C3 SuperMini boards). + */ + Wifi(config::IWifiConfig &config, std::uint32_t reconnectDelayMs, + std::int8_t maxTxPower); + + /** + * @brief Brings the station up and connects using stored credentials. + * + * @return ESP_OK if the connection attempt was launched, + * ESP_ERR_INVALID_STATE if there are no credentials yet, or an ESP-IDF error + * code on failure. + */ + esp_err_t start(); + + /** + * @brief Stops auto-reconnect and disconnects. + * + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + esp_err_t stop(); + + /** + * @brief Whether the station currently holds a connection. + */ + bool isConnected() const; + + /** + * @brief Registers a callback notified on every status transition. + */ + void onStatusChange(StatusHandler handler); + + private: + EspWifiStation _station; + WifiService _service; + StatusHandler _userHandler; +}; + +} // namespace connectivity::wifi diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 51779c8..25b177b 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -8,6 +8,7 @@ #include "connectivity/config/infrastructure/flash_mqtt_config.hpp" #include "connectivity/config/infrastructure/flash_wifi_config.hpp" #include "connectivity/provisioning/provisioning.hpp" +#include "connectivity/wifi/wifi.hpp" #include "peripherals/flash_memory/flash_memory.hpp" #include "peripherals/led/led.hpp" #include "peripherals/relay/relay.hpp" @@ -15,6 +16,10 @@ static const char *TAG = "app/main"; static flash_memory::FlashMemory flashMemory; +static relay::Relay loadRelay(config::relay::GPIO, config::relay::INVERTED); +static led::Led builtinLed(config::builtin_led::GPIO, + config::builtin_led::INVERTED); + static connectivity::config::FlashDeviceConfig deviceConfig( flashMemory, {config::nvs::NAMESPACE, config::device::ID_KEY, config::device::PROJECT_NAME}); @@ -25,9 +30,7 @@ static connectivity::config::FlashMqttConfig mqttConfig( flashMemory, {config::nvs::NAMESPACE, config::mqtt::HOST_KEY, config::mqtt::PORT_KEY, config::mqtt::USER_KEY, config::mqtt::PASSWORD_KEY}); -static relay::Relay loadRelay(config::relay::GPIO, config::relay::INVERTED); -static led::Led builtinLed(config::builtin_led::GPIO, - config::builtin_led::INVERTED); + static connectivity::provisioning::Provisioning provisioning( deviceConfig, wifiConfig, mqttConfig, {.service = config::ble::SERVICE_UUID, @@ -39,6 +42,10 @@ static connectivity::provisioning::Provisioning provisioning( .mqttUser = config::ble::MQTT_USER_UUID, .mqttPassword = config::ble::MQTT_PASSWORD_UUID}); +static connectivity::wifi::Wifi wifi(wifiConfig, + config::wifi::RECONNECT_DELAY_MS, + config::wifi::MAX_TX_POWER); + extern "C" void app_main() { if (beginPeripherals(flashMemory, loadRelay, builtinLed) != ESP_OK) { ESP_LOGE(TAG, "setup failed; halting"); @@ -51,6 +58,11 @@ extern "C" void app_main() { ESP_LOGE(TAG, "provisioning start failed: %s", esp_err_to_name(err)); } + err = wifi.start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "wifi start failed: %s", esp_err_to_name(err)); + } + // Idle loop: keeps app_main alive until the real application logic is added. while (true) { vTaskDelay(pdMS_TO_TICKS(1000)); From 47263db017717db3224f8b7a201363007970548f Mon Sep 17 00:00:00 2001 From: JMTamayo Date: Sat, 20 Jun 2026 18:09:10 -0500 Subject: [PATCH 3/4] docs(connectivity): document config and provisioning adapter overrides Add Doxygen comments to the override methods of the flash-backed config adapters and the NimBLE GATT server, matching the documentation style used elsewhere. --- .../infrastructure/flash_device_config.hpp | 14 +++++ .../infrastructure/flash_mqtt_config.hpp | 52 +++++++++++++++++++ .../infrastructure/flash_wifi_config.hpp | 26 ++++++++++ .../infrastructure/nimble_gatt_server.hpp | 18 +++++++ 4 files changed, 110 insertions(+) diff --git a/firmware/src/connectivity/config/infrastructure/flash_device_config.hpp b/firmware/src/connectivity/config/infrastructure/flash_device_config.hpp index dbab64f..2e929dc 100644 --- a/firmware/src/connectivity/config/infrastructure/flash_device_config.hpp +++ b/firmware/src/connectivity/config/infrastructure/flash_device_config.hpp @@ -34,7 +34,21 @@ class FlashDeviceConfig : public IDeviceConfig { FlashDeviceConfig(flash_memory::FlashService &flash, const DeviceConfigParams ¶ms); + /** + * @brief Reads the device id from storage. + * + * @param out Receives the value on success. + * @return ESP_OK, ESP_ERR_NVS_NOT_FOUND if not provisioned, or an ESP-IDF + * error. + */ esp_err_t getId(std::string &out) const override; + + /** + * @brief Returns the injected project name. + * + * @param out Receives the project name. + * @return ESP_OK on success. + */ esp_err_t getProjectName(std::string &out) const override; private: diff --git a/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.hpp b/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.hpp index 071d571..3b609a5 100644 --- a/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.hpp +++ b/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.hpp @@ -37,16 +37,68 @@ class FlashMqttConfig : public IMqttConfig { FlashMqttConfig(flash_memory::FlashService &flash, const MqttConfigKeys &keys); + /** + * @brief Reads the broker host from storage. + * + * @param out Receives the value on success. + * @return ESP_OK, ESP_ERR_NVS_NOT_FOUND if unset, or an ESP-IDF error. + */ esp_err_t getHost(std::string &out) const override; + + /** + * @brief Persists the broker host. + * + * @param value Value to store. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t setHost(const std::string &value) override; + /** + * @brief Reads the broker port from storage. + * + * @param out Receives the value on success. + * @return ESP_OK, ESP_ERR_NVS_NOT_FOUND if unset, or an ESP-IDF error. + */ esp_err_t getPort(std::string &out) const override; + + /** + * @brief Persists the broker port. + * + * @param value Value to store. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t setPort(const std::string &value) override; + /** + * @brief Reads the user from storage. + * + * @param out Receives the value on success. + * @return ESP_OK, ESP_ERR_NVS_NOT_FOUND if unset, or an ESP-IDF error. + */ esp_err_t getUser(std::string &out) const override; + + /** + * @brief Persists the user. + * + * @param value Value to store. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t setUser(const std::string &value) override; + /** + * @brief Reads the password from storage. + * + * @param out Receives the value on success. + * @return ESP_OK, ESP_ERR_NVS_NOT_FOUND if unset, or an ESP-IDF error. + */ esp_err_t getPassword(std::string &out) const override; + + /** + * @brief Persists the password. + * + * @param value Value to store. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t setPassword(const std::string &value) override; private: diff --git a/firmware/src/connectivity/config/infrastructure/flash_wifi_config.hpp b/firmware/src/connectivity/config/infrastructure/flash_wifi_config.hpp index e3dc75e..c3cbbee 100644 --- a/firmware/src/connectivity/config/infrastructure/flash_wifi_config.hpp +++ b/firmware/src/connectivity/config/infrastructure/flash_wifi_config.hpp @@ -35,10 +35,36 @@ class FlashWifiConfig : public IWifiConfig { FlashWifiConfig(flash_memory::FlashService &flash, const WifiConfigKeys &keys); + /** + * @brief Reads the SSID from storage. + * + * @param out Receives the value on success. + * @return ESP_OK, ESP_ERR_NVS_NOT_FOUND if unset, or an ESP-IDF error. + */ esp_err_t getSsid(std::string &out) const override; + + /** + * @brief Persists the SSID. + * + * @param value Value to store. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t setSsid(const std::string &value) override; + /** + * @brief Reads the password from storage. + * + * @param out Receives the value on success. + * @return ESP_OK, ESP_ERR_NVS_NOT_FOUND if unset, or an ESP-IDF error. + */ esp_err_t getPassword(std::string &out) const override; + + /** + * @brief Persists the password. + * + * @param value Value to store. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t setPassword(const std::string &value) override; private: diff --git a/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.hpp b/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.hpp index a7a4f2e..b6a0838 100644 --- a/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.hpp +++ b/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.hpp @@ -17,8 +17,26 @@ namespace connectivity::provisioning { */ class NimbleGattServer : public IGattServer { public: + /** + * @brief Brings up the NimBLE host (first call) and starts advertising. + * + * Registers the GATT service from @p service on the first call; later calls + * only re-start advertising under @p deviceName. + * + * @param deviceName Advertised device name. + * @param service Service and characteristics to expose. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t start(const std::string &deviceName, const ServiceSpec &service) override; + + /** + * @brief Stops advertising and drops the active connection. + * + * The NimBLE host stays initialized so a later @c start() is cheap. + * + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ esp_err_t stop() override; }; From 7b9f950769b134a453ca342b781893433462880b Mon Sep 17 00:00:00 2001 From: JMTamayo Date: Sat, 20 Jun 2026 18:14:16 -0500 Subject: [PATCH 4/4] style: wrap long comment to satisfy clang-format --- firmware/include/config.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/firmware/include/config.hpp b/firmware/include/config.hpp index fc9625f..3e86588 100644 --- a/firmware/include/config.hpp +++ b/firmware/include/config.hpp @@ -27,7 +27,8 @@ constexpr const char *PASSWORD_KEY = "wifi_password"; constexpr std::uint32_t RECONNECT_DELAY_MS = 5000; -// Max WiFi TX power in 0.25 dBm units (34 = 8.5 dBm); lowered for the SuperMini antenna. +// Max WiFi TX power in 0.25 dBm units (34 = 8.5 dBm); lowered for the SuperMini +// antenna. constexpr std::int8_t MAX_TX_POWER = 34; } // namespace wifi