Add EEPROM EUI-48 device identity across firmware and API.
Read the factory 24AA025E48 serial after ADAU I2C boot, derive SoftAP SSID, BT name, STA hostname, and expose serialNumber on GET /api/health with graceful fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,7 +7,7 @@ idf_component_register(
|
||||
"src/NetBootstrap.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
EMBED_FILES "www/index.html.gz"
|
||||
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server secure_store tuner audio bluetooth station integration bt1035
|
||||
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server mdns secure_store tuner audio bluetooth station integration bt1035
|
||||
)
|
||||
|
||||
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/CompanionChipStatus.hpp"
|
||||
#include "core/DeviceIdentity.hpp"
|
||||
#include "core/ISecureStore.hpp"
|
||||
#include "net/NetError.hpp"
|
||||
#include "net/NetState.hpp"
|
||||
@@ -74,6 +75,7 @@ public:
|
||||
* @param stations Station preset service for REST routes.
|
||||
* @param integration Application orchestration for preset recall.
|
||||
* @param companionChips Boot flags exposed on GET /api/health.
|
||||
* @param deviceIdentity EEPROM-derived SSID, hostname, and serial.
|
||||
* @return NetBootstrap on success, or a NetError.
|
||||
* @pubstate none
|
||||
*
|
||||
@@ -85,7 +87,8 @@ public:
|
||||
audio::AudioService& audio, bluetooth::BluetoothService& bluetooth,
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
core::CompanionChipStatus companionChips);
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity);
|
||||
|
||||
NetBootstrap(const NetBootstrap&) = delete;
|
||||
NetBootstrap& operator=(const NetBootstrap&) = delete;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/CompanionChipStatus.hpp"
|
||||
#include "core/DeviceIdentity.hpp"
|
||||
#include "core/ISecureStore.hpp"
|
||||
#include "net/NetError.hpp"
|
||||
#include "net/NetState.hpp"
|
||||
@@ -69,6 +70,7 @@ struct HttpRouteContext {
|
||||
station::StationService* stations; ///< Preset list REST routes.
|
||||
integration::IntegrationService* integration; ///< Preset recall orchestration.
|
||||
core::CompanionChipStatus companionChips; ///< Boot flags for /api/health.
|
||||
core::DeviceIdentity deviceIdentity; ///< EEPROM-derived unit identity.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -146,6 +148,7 @@ public:
|
||||
* @param stations Station preset service for list REST routes.
|
||||
* @param integration Application orchestration for preset recall.
|
||||
* @param companionChips Boot flags for GET /api/health.
|
||||
* @param deviceIdentity Unit identity for /api/health serialNumber.
|
||||
* @return Ok on success, or NetError::HttpServerStartFailed.
|
||||
* @pubstate writes server_, store_, netState_, and service pointers on success.
|
||||
*
|
||||
@@ -158,7 +161,8 @@ public:
|
||||
bluetooth::BluetoothService& bluetooth,
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
core::CompanionChipStatus companionChips);
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity);
|
||||
|
||||
private:
|
||||
httpd_handle* server_;
|
||||
|
||||
@@ -49,6 +49,19 @@ public:
|
||||
*/
|
||||
[[nodiscard]] static SoftApConfig setupDefault();
|
||||
|
||||
/**
|
||||
* @brief forSsid — construct SoftAP settings with a custom SSID.
|
||||
*
|
||||
* @dname forSsid
|
||||
* @param ssid Network name (max 32 bytes per 802.11).
|
||||
* @return SoftApConfig with the given SSID.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] static SoftApConfig forSsid(std::string_view ssid);
|
||||
|
||||
/**
|
||||
* @brief ssid — read the broadcast SSID.
|
||||
*
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "net/NetError.hpp"
|
||||
|
||||
#include <expected>
|
||||
#include <string_view>
|
||||
|
||||
namespace net {
|
||||
|
||||
@@ -91,7 +92,8 @@ public:
|
||||
* @brief connect — join the network described by creds.
|
||||
*
|
||||
* @dname connect
|
||||
* @param creds Validated domain credentials from ISecureStore.
|
||||
* @param creds Validated domain credentials from ISecureStore.
|
||||
* @param hostname STA hostname / mDNS label (no .local suffix).
|
||||
* @return Ok on success, or NetError::StaConnectTimeout /
|
||||
* NetError::StaConnectFailed.
|
||||
* @pubstate writes connected_ on success; uses creds via Secret.
|
||||
@@ -100,7 +102,8 @@ public:
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, NetError>
|
||||
connect(const core::WifiCredentials& creds);
|
||||
connect(const core::WifiCredentials& creds,
|
||||
std::string_view hostname = {});
|
||||
|
||||
private:
|
||||
bool connected_;
|
||||
|
||||
@@ -101,11 +101,12 @@ startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner,
|
||||
bluetooth::BluetoothService& bluetooth,
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
core::CompanionChipStatus companionChips)
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity)
|
||||
{
|
||||
esp_netif_create_default_wifi_ap();
|
||||
|
||||
SoftApHost softAp(SoftApConfig::setupDefault());
|
||||
SoftApHost softAp(SoftApConfig::forSsid(deviceIdentity.softApSsid()));
|
||||
if (auto apResult = softAp.start(); !apResult) {
|
||||
return std::unexpected(apResult.error());
|
||||
}
|
||||
@@ -113,12 +114,15 @@ startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner,
|
||||
SetupWebServer webServer;
|
||||
if (auto webResult =
|
||||
webServer.start(store, NetState::SoftApSetup, tuner, audio,
|
||||
bluetooth, stations, integration, companionChips);
|
||||
bluetooth, stations, integration, companionChips,
|
||||
deviceIdentity);
|
||||
!webResult) {
|
||||
return std::unexpected(webResult.error());
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag, "setup mode ready — SSID DigiRadio-setup");
|
||||
ESP_LOGI(kTag, "setup mode ready — SSID %.*s",
|
||||
static_cast<int>(deviceIdentity.softApSsid().size()),
|
||||
deviceIdentity.softApSsid().data());
|
||||
return NetBootstrap(std::move(softAp), std::nullopt, std::move(webServer),
|
||||
NetState::SoftApSetup);
|
||||
}
|
||||
@@ -140,7 +144,8 @@ startStaMode(core::ISecureStore& store, tuner::TunerService& tuner,
|
||||
bluetooth::BluetoothService& bluetooth,
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
core::CompanionChipStatus companionChips)
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity)
|
||||
{
|
||||
auto credsResult = store.loadWifiCredentials();
|
||||
if (!credsResult) {
|
||||
@@ -151,19 +156,24 @@ startStaMode(core::ISecureStore& store, tuner::TunerService& tuner,
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
StaClient sta;
|
||||
if (auto staResult = sta.connect(credsResult.value()); !staResult) {
|
||||
if (auto staResult =
|
||||
sta.connect(credsResult.value(), deviceIdentity.hostname());
|
||||
!staResult) {
|
||||
return std::unexpected(staResult.error());
|
||||
}
|
||||
|
||||
SetupWebServer webServer;
|
||||
if (auto webResult =
|
||||
webServer.start(store, NetState::StaConnected, tuner, audio,
|
||||
bluetooth, stations, integration, companionChips);
|
||||
bluetooth, stations, integration, companionChips,
|
||||
deviceIdentity);
|
||||
!webResult) {
|
||||
return std::unexpected(webResult.error());
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag, "STA mode ready");
|
||||
ESP_LOGI(kTag, "STA mode ready — hostname %.*s.local",
|
||||
static_cast<int>(deviceIdentity.hostname().size()),
|
||||
deviceIdentity.hostname().data());
|
||||
return NetBootstrap(std::nullopt, std::move(sta), std::move(webServer),
|
||||
NetState::StaConnected);
|
||||
}
|
||||
@@ -176,7 +186,8 @@ NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner,
|
||||
bluetooth::BluetoothService& bluetooth,
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
core::CompanionChipStatus companionChips)
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity)
|
||||
{
|
||||
if (auto platform = initPlatform(); !platform) {
|
||||
return std::unexpected(platform.error());
|
||||
@@ -188,7 +199,8 @@ NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner,
|
||||
|
||||
if (store.hasWifiCredentials()) {
|
||||
auto staResult = startStaMode(store, tuner, audio, bluetooth, stations,
|
||||
integration, companionChips);
|
||||
integration, companionChips,
|
||||
deviceIdentity);
|
||||
if (staResult) {
|
||||
return staResult;
|
||||
}
|
||||
@@ -196,7 +208,7 @@ NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner,
|
||||
}
|
||||
|
||||
return startSetupMode(store, tuner, audio, bluetooth, stations, integration,
|
||||
companionChips);
|
||||
companionChips, deviceIdentity);
|
||||
}
|
||||
|
||||
NetBootstrap::NetBootstrap(std::optional<SoftApHost> softAp,
|
||||
|
||||
@@ -217,7 +217,9 @@ esp_err_t healthGetHandler(httpd_req_t* req)
|
||||
.bt1035Ready = false,
|
||||
};
|
||||
const core::HealthStatus status = core::HealthStatus::ok(
|
||||
core::FirmwareVersion(kFirmwareVersion), chips);
|
||||
core::FirmwareVersion(kFirmwareVersion), chips,
|
||||
ctx != nullptr ? ctx->deviceIdentity.serialNumber()
|
||||
: std::string_view("unknown"));
|
||||
const std::string json = core::serializeHealthStatusJson(status);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
return httpd_resp_send(req, json.c_str(), json.size());
|
||||
@@ -916,7 +918,7 @@ SetupWebServer::SetupWebServer(SetupWebServer&& other) noexcept
|
||||
other.stations_ = nullptr;
|
||||
other.integration_ = nullptr;
|
||||
other.routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, {}};
|
||||
nullptr, {}, core::DeviceIdentity::unknown()};
|
||||
}
|
||||
|
||||
SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
|
||||
@@ -943,7 +945,7 @@ SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
|
||||
other.stations_ = nullptr;
|
||||
other.integration_ = nullptr;
|
||||
other.routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, {}};
|
||||
nullptr, {}, core::DeviceIdentity::unknown()};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -954,7 +956,8 @@ SetupWebServer::~SetupWebServer()
|
||||
httpd_stop(server_);
|
||||
server_ = nullptr;
|
||||
}
|
||||
routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, {}};
|
||||
routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, {},
|
||||
core::DeviceIdentity::unknown()};
|
||||
}
|
||||
|
||||
std::expected<void, NetError> SetupWebServer::start(
|
||||
@@ -965,7 +968,8 @@ std::expected<void, NetError> SetupWebServer::start(
|
||||
bluetooth::BluetoothService& bluetooth,
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
core::CompanionChipStatus companionChips)
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity)
|
||||
{
|
||||
if (server_ != nullptr) {
|
||||
return {};
|
||||
@@ -985,6 +989,7 @@ std::expected<void, NetError> SetupWebServer::start(
|
||||
routeContext_.stations = &stations;
|
||||
routeContext_.integration = &integration;
|
||||
routeContext_.companionChips = companionChips;
|
||||
routeContext_.deviceIdentity = deviceIdentity;
|
||||
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = 80;
|
||||
@@ -993,7 +998,7 @@ std::expected<void, NetError> SetupWebServer::start(
|
||||
if (httpd_start(&server_, &config) != ESP_OK) {
|
||||
ESP_LOGE(kTag, "httpd_start failed");
|
||||
routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
{}};
|
||||
{}, core::DeviceIdentity::unknown()};
|
||||
return std::unexpected(NetError::HttpServerStartFailed);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,12 @@ constexpr std::uint8_t kSetupMaxConnections = 4;
|
||||
|
||||
SoftApConfig SoftApConfig::setupDefault()
|
||||
{
|
||||
return SoftApConfig(kSetupSsid, kSetupChannel, kSetupMaxConnections);
|
||||
return forSsid(kSetupSsid);
|
||||
}
|
||||
|
||||
SoftApConfig SoftApConfig::forSsid(std::string_view ssid)
|
||||
{
|
||||
return SoftApConfig(ssid, kSetupChannel, kSetupMaxConnections);
|
||||
}
|
||||
|
||||
SoftApConfig::SoftApConfig(std::string_view ssid,
|
||||
|
||||
@@ -20,12 +20,15 @@
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "mdns.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace net {
|
||||
|
||||
@@ -104,12 +107,17 @@ StaClient& StaClient::operator=(StaClient&& other) noexcept
|
||||
}
|
||||
|
||||
std::expected<void, NetError>
|
||||
StaClient::connect(const core::WifiCredentials& creds)
|
||||
StaClient::connect(const core::WifiCredentials& creds, std::string_view hostname)
|
||||
{
|
||||
if (connected_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string hostLabel;
|
||||
if (!hostname.empty()) {
|
||||
hostLabel.assign(hostname.begin(), hostname.end());
|
||||
}
|
||||
|
||||
s_wifiEventGroup = xEventGroupCreate();
|
||||
if (s_wifiEventGroup == nullptr) {
|
||||
return std::unexpected(NetError::StaConnectFailed);
|
||||
@@ -132,6 +140,13 @@ StaClient::connect(const core::WifiCredentials& creds)
|
||||
return std::unexpected(NetError::WifiConfigFailed);
|
||||
}
|
||||
|
||||
if (!hostLabel.empty()) {
|
||||
esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (netif != nullptr) {
|
||||
esp_netif_set_hostname(netif, hostLabel.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
wifi_config_t wifiCfg = {};
|
||||
const std::string_view ssid = creds.ssid().value();
|
||||
const std::size_t ssidCopy =
|
||||
@@ -167,6 +182,11 @@ StaClient::connect(const core::WifiCredentials& creds)
|
||||
|
||||
if ((bits & kConnectedBit) != 0) {
|
||||
connected_ = true;
|
||||
if (!hostLabel.empty()) {
|
||||
if (mdns_init() == ESP_OK) {
|
||||
mdns_hostname_set(hostLabel.c_str());
|
||||
}
|
||||
}
|
||||
ESP_LOGI(kTag, "connected to %.*s",
|
||||
static_cast<int>(ssid.size()), ssid.data());
|
||||
return {};
|
||||
|
||||
Reference in New Issue
Block a user