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:
@@ -15,6 +15,8 @@ add_library(digiradio_core STATIC
|
||||
"${CORE_SRC_DIR}/FirmwareVersion.cpp"
|
||||
"${CORE_SRC_DIR}/HealthStatus.cpp"
|
||||
"${CORE_SRC_DIR}/HealthStatusJson.cpp"
|
||||
"${CORE_SRC_DIR}/Eui48.cpp"
|
||||
"${CORE_SRC_DIR}/DeviceIdentity.cpp"
|
||||
"${CORE_SRC_DIR}/Secret.cpp"
|
||||
"${CORE_SRC_DIR}/WifiSsid.cpp"
|
||||
"${CORE_SRC_DIR}/WifiCredentials.cpp"
|
||||
@@ -49,6 +51,10 @@ target_include_directories(digiradio_core PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||
)
|
||||
|
||||
add_executable(device_identity_test device_identity_test.cpp)
|
||||
target_link_libraries(device_identity_test PRIVATE digiradio_core)
|
||||
add_test(NAME device_identity_test COMMAND device_identity_test)
|
||||
|
||||
add_executable(health_status_test health_status_test.cpp)
|
||||
target_link_libraries(health_status_test PRIVATE digiradio_core)
|
||||
add_test(NAME health_status_test COMMAND health_status_test)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @file device_identity_test.cpp
|
||||
* @brief Host tests for Eui48 and DeviceIdentity derivation.
|
||||
*
|
||||
* 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"
|
||||
#include "core/Eui48.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool expectEqual(std::string_view actual,
|
||||
std::string_view expected)
|
||||
{
|
||||
if (actual == expected) {
|
||||
return true;
|
||||
}
|
||||
std::cerr << "expected: " << expected << "\nactual: " << actual << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runKnownEuiTest()
|
||||
{
|
||||
const std::array<std::uint8_t, 6> bytes = {
|
||||
0x00U, 0x04U, 0xA3U, 0x12U, 0x34U, 0x56U,
|
||||
};
|
||||
const core::Eui48 eui = core::Eui48::fromBytes(bytes);
|
||||
if (!expectEqual(eui.serialNumber(), "0004A3123456")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(eui.shortSuffix(), "123456")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const core::DeviceIdentity identity = core::DeviceIdentity::fromEui48(eui);
|
||||
if (!identity.isKnown()) {
|
||||
std::cerr << "expected known identity\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.serialNumber(), "0004A3123456")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.softApSsid(), "DigiRadio-123456")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.bluetoothName(), "DigiRadio-123456")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.hostname(), "digiradio-123456")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runUnknownIdentityTest()
|
||||
{
|
||||
const core::DeviceIdentity identity = core::DeviceIdentity::unknown();
|
||||
if (identity.isKnown()) {
|
||||
std::cerr << "expected unknown identity\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.serialNumber(), "unknown")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.softApSsid(), "DigiRadio-setup")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.bluetoothName(), "DigiRadio")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!expectEqual(identity.hostname(), "digiradio")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
if (runKnownEuiTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runUnknownIdentityTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -7,11 +7,6 @@
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
@@ -27,18 +22,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief expectEqual — assert two strings match.
|
||||
*
|
||||
* @dname expectEqual
|
||||
* @param actual Observed value.
|
||||
* @param expected Expected value.
|
||||
* @return true when equal.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] bool expectEqual(const std::string& actual,
|
||||
const std::string& expected)
|
||||
{
|
||||
@@ -49,23 +32,13 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief runHealthStatusJsonTest — verify nominal JSON output.
|
||||
*
|
||||
* @dname runHealthStatusJsonTest
|
||||
* @param none
|
||||
* @return EXIT_SUCCESS or EXIT_FAILURE.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] int runHealthStatusJsonTest()
|
||||
{
|
||||
const core::HealthStatus status =
|
||||
core::HealthStatus::ok(core::FirmwareVersion("0.1.0"));
|
||||
const std::string json = core::serializeHealthStatusJson(status);
|
||||
if (!expectEqual(json, R"({"status":"ok","fw":"0.1.0"})")) {
|
||||
if (!expectEqual(json,
|
||||
R"({"status":"ok","fw":"0.1.0","serialNumber":"unknown"})")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
@@ -73,17 +46,6 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief main — host test entry point.
|
||||
*
|
||||
* @dname main
|
||||
* @param none
|
||||
* @return EXIT_SUCCESS when all tests pass.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
if (runHealthStatusJsonTest() != EXIT_SUCCESS) {
|
||||
@@ -96,11 +58,12 @@ int main()
|
||||
.si4684Ready = true,
|
||||
.adau1701Ready = true,
|
||||
.bt1035Ready = true,
|
||||
});
|
||||
},
|
||||
"0004A3123456");
|
||||
const std::string chipsJson = core::serializeHealthStatusJson(withChips);
|
||||
if (!expectEqual(
|
||||
chipsJson,
|
||||
R"({"status":"ok","fw":"0.6.0","chips":{"si4684":true,"adau1701":true,"bt1035":true}})")) {
|
||||
R"({"status":"ok","fw":"0.6.0","serialNumber":"0004A3123456","chips":{"si4684":true,"adau1701":true,"bt1035":true}})")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
Reference in New Issue
Block a user