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:
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @file DeviceIdentity.cpp
|
||||
* @brief DeviceIdentity implementation.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
|
||||
#include "core/DeviceIdentity.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kSerialUnknown = "unknown";
|
||||
constexpr std::string_view kSoftApFallback = "DigiRadio-setup";
|
||||
constexpr std::string_view kBluetoothFallback = "DigiRadio";
|
||||
constexpr std::string_view kHostnameFallback = "digiradio";
|
||||
|
||||
[[nodiscard]] std::string prefixed(std::string_view prefix,
|
||||
std::string_view suffix)
|
||||
{
|
||||
return std::string(prefix) + std::string(suffix);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DeviceIdentity DeviceIdentity::unknown() noexcept
|
||||
{
|
||||
return DeviceIdentity(std::nullopt,
|
||||
std::string(kSerialUnknown),
|
||||
std::string(kSoftApFallback),
|
||||
std::string(kBluetoothFallback),
|
||||
std::string(kHostnameFallback));
|
||||
}
|
||||
|
||||
DeviceIdentity DeviceIdentity::fromEui48(Eui48 eui)
|
||||
{
|
||||
const std::string suffix = eui.shortSuffix();
|
||||
return DeviceIdentity(eui,
|
||||
eui.serialNumber(),
|
||||
prefixed("DigiRadio-", suffix),
|
||||
prefixed("DigiRadio-", suffix),
|
||||
prefixed("digiradio-", suffix));
|
||||
}
|
||||
|
||||
bool DeviceIdentity::isKnown() const noexcept
|
||||
{
|
||||
return eui_.has_value();
|
||||
}
|
||||
|
||||
std::string_view DeviceIdentity::serialNumber() const noexcept
|
||||
{
|
||||
return serialNumber_;
|
||||
}
|
||||
|
||||
std::string_view DeviceIdentity::softApSsid() const noexcept
|
||||
{
|
||||
return softApSsid_;
|
||||
}
|
||||
|
||||
std::string_view DeviceIdentity::bluetoothName() const noexcept
|
||||
{
|
||||
return bluetoothName_;
|
||||
}
|
||||
|
||||
std::string_view DeviceIdentity::hostname() const noexcept
|
||||
{
|
||||
return hostname_;
|
||||
}
|
||||
|
||||
const std::optional<Eui48>& DeviceIdentity::eui48() const noexcept
|
||||
{
|
||||
return eui_;
|
||||
}
|
||||
|
||||
DeviceIdentity::DeviceIdentity(std::optional<Eui48> eui,
|
||||
std::string serialNumber,
|
||||
std::string softApSsid,
|
||||
std::string bluetoothName,
|
||||
std::string hostname)
|
||||
: eui_(std::move(eui))
|
||||
, serialNumber_(std::move(serialNumber))
|
||||
, softApSsid_(std::move(softApSsid))
|
||||
, bluetoothName_(std::move(bluetoothName))
|
||||
, hostname_(std::move(hostname))
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @file Eui48.cpp
|
||||
* @brief Eui48 implementation.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
|
||||
#include "core/Eui48.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace core {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::string toUpperHex(std::string_view bytes)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << std::uppercase << std::hex << std::setfill('0');
|
||||
for (const unsigned char byte : bytes) {
|
||||
out << std::setw(2) << static_cast<unsigned>(byte);
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Eui48 Eui48::fromBytes(const std::array<std::uint8_t, 6>& bytes) noexcept
|
||||
{
|
||||
return Eui48(bytes);
|
||||
}
|
||||
|
||||
Eui48::Eui48(std::array<std::uint8_t, 6> bytes) noexcept
|
||||
: bytes_(bytes)
|
||||
{
|
||||
}
|
||||
|
||||
const std::array<std::uint8_t, 6>& Eui48::bytes() const noexcept
|
||||
{
|
||||
return bytes_;
|
||||
}
|
||||
|
||||
std::string Eui48::serialNumber() const
|
||||
{
|
||||
return toUpperHex(
|
||||
std::string_view(reinterpret_cast<const char*>(bytes_.data()),
|
||||
bytes_.size()));
|
||||
}
|
||||
|
||||
std::string Eui48::shortSuffix() const
|
||||
{
|
||||
return toUpperHex(
|
||||
std::string_view(reinterpret_cast<const char*>(bytes_.data() + 3U), 3U));
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -15,29 +15,42 @@
|
||||
|
||||
namespace core {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kSerialUnknown = "unknown";
|
||||
|
||||
} // namespace
|
||||
|
||||
HealthStatus HealthStatus::ok(FirmwareVersion firmware)
|
||||
{
|
||||
return HealthStatus(HealthState::Ok, std::move(firmware));
|
||||
}
|
||||
|
||||
HealthStatus HealthStatus::ok(FirmwareVersion firmware,
|
||||
CompanionChipStatus chips)
|
||||
CompanionChipStatus chips,
|
||||
std::string_view serialNumber)
|
||||
{
|
||||
return HealthStatus(HealthState::Ok, std::move(firmware), chips);
|
||||
return HealthStatus(HealthState::Ok,
|
||||
std::move(firmware),
|
||||
chips,
|
||||
std::string(serialNumber));
|
||||
}
|
||||
|
||||
HealthStatus::HealthStatus(HealthState state, FirmwareVersion firmware)
|
||||
: state_(state)
|
||||
, firmware_(std::move(firmware))
|
||||
, chips_(std::nullopt)
|
||||
, serialNumber_(kSerialUnknown)
|
||||
{
|
||||
}
|
||||
|
||||
HealthStatus::HealthStatus(HealthState state, FirmwareVersion firmware,
|
||||
CompanionChipStatus chips)
|
||||
CompanionChipStatus chips,
|
||||
std::string serialNumber)
|
||||
: state_(state)
|
||||
, firmware_(std::move(firmware))
|
||||
, chips_(chips)
|
||||
, serialNumber_(std::move(serialNumber))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -56,4 +69,9 @@ const std::optional<CompanionChipStatus>& HealthStatus::chips() const noexcept
|
||||
return chips_;
|
||||
}
|
||||
|
||||
std::string_view HealthStatus::serialNumber() const noexcept
|
||||
{
|
||||
return serialNumber_;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
|
||||
@@ -39,7 +39,9 @@ namespace {
|
||||
std::string serializeHealthStatusJson(const HealthStatus& status)
|
||||
{
|
||||
std::string json = std::string("{\"status\":\"") + healthStateToken(status.state())
|
||||
+ "\",\"fw\":\"" + std::string(status.firmware().value()) + "\"";
|
||||
+ "\",\"fw\":\"" + std::string(status.firmware().value())
|
||||
+ "\",\"serialNumber\":\""
|
||||
+ std::string(status.serialNumber()) + "\"";
|
||||
if (const std::optional<CompanionChipStatus>& chips = status.chips(); chips) {
|
||||
json += std::string(",\"chips\":{")
|
||||
+ "\"si4684\":" + (chips->si4684Ready ? "true" : "false")
|
||||
|
||||
Reference in New Issue
Block a user