Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions firmware/include/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ 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 {
Expand Down
6 changes: 6 additions & 0 deletions firmware/partitions.csv
Original file line number Diff line number Diff line change
@@ -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,
1 change: 1 addition & 0 deletions firmware/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ platform = espressif32@6.12.0
board = esp32-c3-devkitm-1
framework = espidf
monitor_speed = 115200
board_build.partitions = partitions.csv
5 changes: 5 additions & 0 deletions firmware/sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion firmware/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,21 @@ class FlashDeviceConfig : public IDeviceConfig {
FlashDeviceConfig(flash_memory::FlashService &flash,
const DeviceConfigParams &params);

/**
* @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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
42 changes: 42 additions & 0 deletions firmware/src/connectivity/wifi/application/wifi_service.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "connectivity/wifi/application/wifi_service.hpp"

#include <esp_log.h>
#include <nvs.h>

#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
42 changes: 42 additions & 0 deletions firmware/src/connectivity/wifi/application/wifi_service.hpp
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions firmware/src/connectivity/wifi/domain/i_wifi_station.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <esp_err.h>

#include <functional>
#include <string>

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<void(Status)>;

/**
* @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
Loading
Loading