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:
2026-07-07 09:00:38 +02:00
co-authored by Cursor
parent 11ad6b43ad
commit cd94e36a9d
35 changed files with 999 additions and 83 deletions
+23 -11
View File
@@ -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,
+11 -6
View File
@@ -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);
}
+6 -1
View File
@@ -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,
+21 -1
View File
@@ -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 {};