From 98518c5424344894e60e648dbdb211032f7e80e7 Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Mon, 6 Jul 2026 17:15:17 +0200 Subject: [PATCH] Integrate companion-chip status into GET /api/health (fw 0.6.0). Expose Si4684, ADAU1701, and BT1035 boot flags via CompanionChipStatus after HardwareBootstrap; update API manual schema for Slice 8. Co-authored-by: Cursor --- .../core/include/core/CompanionChipStatus.hpp | 33 +++++++++++++++++ .../core/include/core/HealthStatus.hpp | 35 +++++++++++++++++++ Software/components/core/src/HealthStatus.cpp | 25 ++++++++++--- .../components/core/src/HealthStatusJson.cpp | 26 +++++++------- .../core/test/health_status_test.cpp | 20 ++++++++++- .../net/include/net/NetBootstrap.hpp | 4 ++- .../net/include/net/SetupWebServer.hpp | 10 +++--- Software/components/net/src/NetBootstrap.cpp | 19 ++++++---- .../components/net/src/SetupWebServer.cpp | 30 +++++++++++----- Software/docs/manual/ch-api.tex | 5 ++- Software/main/hardware_bootstrap.cpp | 9 +++++ Software/main/hardware_bootstrap.hpp | 14 ++++++++ Software/main/main.cpp | 3 +- 13 files changed, 191 insertions(+), 42 deletions(-) create mode 100644 Software/components/core/include/core/CompanionChipStatus.hpp diff --git a/Software/components/core/include/core/CompanionChipStatus.hpp b/Software/components/core/include/core/CompanionChipStatus.hpp new file mode 100644 index 0000000..7dc760f --- /dev/null +++ b/Software/components/core/include/core/CompanionChipStatus.hpp @@ -0,0 +1,33 @@ +/** + * @file CompanionChipStatus.hpp + * @brief Boot-ready flags for Si4684, ADAU1701, and FSC-BT1035. + * + * DigiRadio firmware — https://github.com/manvalan/DigiRadio + * + * Copyright 2026 Michele Bigi + * SPDX-License-Identifier: Apache-2.0 + * + * @author Michele Bigi + * @date 2026-07-06 + */ +#pragma once + +namespace core { + +/** + * @brief CompanionChipStatus — companion-chip boot snapshot for /api/health. + * + * @dname CompanionChipStatus + * @return n/a (type) + * @pubstate Immutable aggregate; all true after successful HardwareBootstrap. + * + * @author Michele Bigi + * @date 2026-07-06 + */ +struct CompanionChipStatus { + bool si4684Ready; ///< Si4684 HOST_LOAD completed. + bool adau1701Ready; ///< ADAU1701 SigmaStudio download completed. + bool bt1035Ready; ///< BT1035 Line-In init (AT+AUXCFG=1) completed. +}; + +} // namespace core diff --git a/Software/components/core/include/core/HealthStatus.hpp b/Software/components/core/include/core/HealthStatus.hpp index 453cbfe..246e34f 100644 --- a/Software/components/core/include/core/HealthStatus.hpp +++ b/Software/components/core/include/core/HealthStatus.hpp @@ -18,6 +18,9 @@ #pragma once #include "core/FirmwareVersion.hpp" +#include "core/CompanionChipStatus.hpp" + +#include namespace core { @@ -64,6 +67,21 @@ public: */ [[nodiscard]] static HealthStatus ok(FirmwareVersion firmware); + /** + * @brief ok — build health response including companion-chip flags. + * + * @dname ok + * @param firmware Active firmware version to report. + * @param chips Si4684 / ADAU1701 / BT1035 boot snapshot. + * @return HealthStatus with HealthState::Ok and chips populated. + * @pubstate none + * + * @author Michele Bigi + * @date 2026-07-06 + */ + [[nodiscard]] static HealthStatus ok(FirmwareVersion firmware, + CompanionChipStatus chips); + /** * @brief state — read the health indicator. * @@ -88,11 +106,28 @@ public: */ [[nodiscard]] const FirmwareVersion& firmware() const noexcept; + /** + * @brief chips — optional companion-chip boot flags. + * + * @dname chips + * @return Chip status when set by ok(..., chips); otherwise nullopt. + * @pubstate reads chips_. + * + * @author Michele Bigi + * @date 2026-07-06 + */ + [[nodiscard]] const std::optional& chips() const + noexcept; + private: explicit HealthStatus(HealthState state, FirmwareVersion firmware); + explicit HealthStatus(HealthState state, FirmwareVersion firmware, + CompanionChipStatus chips); + HealthState state_; FirmwareVersion firmware_; + std::optional chips_; }; } // namespace core diff --git a/Software/components/core/src/HealthStatus.cpp b/Software/components/core/src/HealthStatus.cpp index 0bcccfe..46603f2 100644 --- a/Software/components/core/src/HealthStatus.cpp +++ b/Software/components/core/src/HealthStatus.cpp @@ -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 */ @@ -25,9 +20,24 @@ HealthStatus HealthStatus::ok(FirmwareVersion firmware) return HealthStatus(HealthState::Ok, std::move(firmware)); } +HealthStatus HealthStatus::ok(FirmwareVersion firmware, + CompanionChipStatus chips) +{ + return HealthStatus(HealthState::Ok, std::move(firmware), chips); +} + HealthStatus::HealthStatus(HealthState state, FirmwareVersion firmware) : state_(state) , firmware_(std::move(firmware)) + , chips_(std::nullopt) +{ +} + +HealthStatus::HealthStatus(HealthState state, FirmwareVersion firmware, + CompanionChipStatus chips) + : state_(state) + , firmware_(std::move(firmware)) + , chips_(chips) { } @@ -41,4 +51,9 @@ const FirmwareVersion& HealthStatus::firmware() const noexcept return firmware_; } +const std::optional& HealthStatus::chips() const noexcept +{ + return chips_; +} + } // namespace core diff --git a/Software/components/core/src/HealthStatusJson.cpp b/Software/components/core/src/HealthStatusJson.cpp index bf9179f..c616757 100644 --- a/Software/components/core/src/HealthStatusJson.cpp +++ b/Software/components/core/src/HealthStatusJson.cpp @@ -18,21 +18,13 @@ #include "core/HealthStatusJson.hpp" +#include +#include + namespace core { namespace { -/** - * @brief healthStateToken — map HealthState to the API status string. - * - * @dname healthStateToken - * @param state Health indicator to encode. - * @return JSON string token without quotes (e.g. ok). - * @pubstate none - * - * @author Michele Bigi - * @date 2026-07-06 - */ [[nodiscard]] const char* healthStateToken(HealthState state) noexcept { switch (state) { @@ -46,8 +38,16 @@ namespace { std::string serializeHealthStatusJson(const HealthStatus& status) { - return std::string("{\"status\":\"") + healthStateToken(status.state()) - + "\",\"fw\":\"" + std::string(status.firmware().value()) + "\"}"; + std::string json = std::string("{\"status\":\"") + healthStateToken(status.state()) + + "\",\"fw\":\"" + std::string(status.firmware().value()) + "\""; + if (const std::optional& chips = status.chips(); chips) { + json += std::string(",\"chips\":{") + + "\"si4684\":" + (chips->si4684Ready ? "true" : "false") + + ",\"adau1701\":" + (chips->adau1701Ready ? "true" : "false") + + ",\"bt1035\":" + (chips->bt1035Ready ? "true" : "false") + "}"; + } + json += '}'; + return json; } } // namespace core diff --git a/Software/components/core/test/health_status_test.cpp b/Software/components/core/test/health_status_test.cpp index a9c2071..5080a85 100644 --- a/Software/components/core/test/health_status_test.cpp +++ b/Software/components/core/test/health_status_test.cpp @@ -17,6 +17,7 @@ */ #include "core/FirmwareVersion.hpp" +#include "core/CompanionChipStatus.hpp" #include "core/HealthStatus.hpp" #include "core/HealthStatusJson.hpp" @@ -85,5 +86,22 @@ namespace { */ int main() { - return runHealthStatusJsonTest(); + if (runHealthStatusJsonTest() != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + const core::HealthStatus withChips = core::HealthStatus::ok( + core::FirmwareVersion("0.6.0"), + core::CompanionChipStatus{ + .si4684Ready = true, + .adau1701Ready = true, + .bt1035Ready = true, + }); + const std::string chipsJson = core::serializeHealthStatusJson(withChips); + if (!expectEqual( + chipsJson, + R"({"status":"ok","fw":"0.6.0","chips":{"si4684":true,"adau1701":true,"bt1035":true}})")) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } diff --git a/Software/components/net/include/net/NetBootstrap.hpp b/Software/components/net/include/net/NetBootstrap.hpp index ad19e24..1cf473e 100644 --- a/Software/components/net/include/net/NetBootstrap.hpp +++ b/Software/components/net/include/net/NetBootstrap.hpp @@ -17,6 +17,7 @@ */ #pragma once +#include "core/CompanionChipStatus.hpp" #include "core/ISecureStore.hpp" #include "net/NetError.hpp" #include "net/NetState.hpp" @@ -65,7 +66,8 @@ public: */ [[nodiscard]] static std::expected start(core::ISecureStore& store, tuner::TunerService& tuner, - audio::AudioService& audio); + audio::AudioService& audio, + core::CompanionChipStatus companionChips); NetBootstrap(const NetBootstrap&) = delete; NetBootstrap& operator=(const NetBootstrap&) = delete; diff --git a/Software/components/net/include/net/SetupWebServer.hpp b/Software/components/net/include/net/SetupWebServer.hpp index 3619b48..6d8c587 100644 --- a/Software/components/net/include/net/SetupWebServer.hpp +++ b/Software/components/net/include/net/SetupWebServer.hpp @@ -17,6 +17,7 @@ */ #pragma once +#include "core/CompanionChipStatus.hpp" #include "core/ISecureStore.hpp" #include "net/NetError.hpp" #include "net/NetState.hpp" @@ -52,6 +53,7 @@ struct HttpRouteContext { core::ISecureStore* store; ///< Secure store for Wi-Fi provisioning. tuner::TunerService* tuner; ///< Tuner service for tuner REST routes. audio::AudioService* audio; ///< Audio service for ADAU1701 REST routes. + core::CompanionChipStatus companionChips; ///< Boot flags for /api/health. }; /** @@ -131,10 +133,10 @@ public: * @author Michele Bigi * @date 2026-07-06 */ - [[nodiscard]] std::expected start(core::ISecureStore& store, - NetState netState, - tuner::TunerService& tuner, - audio::AudioService& audio); + [[nodiscard]] std::expected start( + core::ISecureStore& store, NetState netState, + tuner::TunerService& tuner, audio::AudioService& audio, + core::CompanionChipStatus companionChips); private: httpd_handle* server_; diff --git a/Software/components/net/src/NetBootstrap.cpp b/Software/components/net/src/NetBootstrap.cpp index f240f35..dda0bf5 100644 --- a/Software/components/net/src/NetBootstrap.cpp +++ b/Software/components/net/src/NetBootstrap.cpp @@ -100,7 +100,8 @@ constexpr char kTag[] = "NetBootstrap"; */ [[nodiscard]] std::expected startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner, - audio::AudioService& audio) + audio::AudioService& audio, + core::CompanionChipStatus companionChips) { esp_netif_create_default_wifi_ap(); @@ -111,7 +112,8 @@ startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner, SetupWebServer webServer; if (auto webResult = - webServer.start(store, NetState::SoftApSetup, tuner, audio); + webServer.start(store, NetState::SoftApSetup, tuner, audio, + companionChips); !webResult) { return std::unexpected(webResult.error()); } @@ -134,7 +136,8 @@ startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner, */ [[nodiscard]] std::expected startStaMode(core::ISecureStore& store, tuner::TunerService& tuner, - audio::AudioService& audio) + audio::AudioService& audio, + core::CompanionChipStatus companionChips) { auto credsResult = store.loadWifiCredentials(); if (!credsResult) { @@ -151,7 +154,8 @@ startStaMode(core::ISecureStore& store, tuner::TunerService& tuner, SetupWebServer webServer; if (auto webResult = - webServer.start(store, NetState::StaConnected, tuner, audio); + webServer.start(store, NetState::StaConnected, tuner, audio, + companionChips); !webResult) { return std::unexpected(webResult.error()); } @@ -165,7 +169,8 @@ startStaMode(core::ISecureStore& store, tuner::TunerService& tuner, std::expected NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner, - audio::AudioService& audio) + audio::AudioService& audio, + core::CompanionChipStatus companionChips) { if (auto platform = initPlatform(); !platform) { return std::unexpected(platform.error()); @@ -176,14 +181,14 @@ NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner, } if (store.hasWifiCredentials()) { - auto staResult = startStaMode(store, tuner, audio); + auto staResult = startStaMode(store, tuner, audio, companionChips); if (staResult) { return staResult; } ESP_LOGW(kTag, "STA join failed — falling back to setup SoftAP"); } - return startSetupMode(store, tuner, audio); + return startSetupMode(store, tuner, audio, companionChips); } NetBootstrap::NetBootstrap(std::optional softAp, diff --git a/Software/components/net/src/SetupWebServer.cpp b/Software/components/net/src/SetupWebServer.cpp index 7cbb1ec..2cdbbca 100644 --- a/Software/components/net/src/SetupWebServer.cpp +++ b/Software/components/net/src/SetupWebServer.cpp @@ -20,6 +20,7 @@ #include "core/AudioProfile.hpp" #include "core/AudioProfileJson.hpp" +#include "core/CompanionChipStatus.hpp" #include "core/FirmwareVersion.hpp" #include "core/HealthStatus.hpp" #include "core/HealthStatusJson.hpp" @@ -44,7 +45,7 @@ namespace net { namespace { constexpr char kTag[] = "SetupWebServer"; -constexpr char kFirmwareVersion[] = "0.5.0"; +constexpr char kFirmwareVersion[] = "0.6.0"; constexpr unsigned kRebootDelaySec = 3; extern const uint8_t www_index_html_gz_start[] asm( @@ -165,8 +166,17 @@ template */ esp_err_t healthGetHandler(httpd_req_t* req) { - const core::HealthStatus status = - core::HealthStatus::ok(core::FirmwareVersion(kFirmwareVersion)); + auto* ctx = routeContextFrom(req); + const core::CompanionChipStatus chips = + ctx != nullptr + ? ctx->companionChips + : core::CompanionChipStatus{ + .si4684Ready = false, + .adau1701Ready = false, + .bt1035Ready = false, + }; + const core::HealthStatus status = core::HealthStatus::ok( + core::FirmwareVersion(kFirmwareVersion), chips); const std::string json = core::serializeHealthStatusJson(status); httpd_resp_set_type(req, "application/json"); return httpd_resp_send(req, json.c_str(), json.size()); @@ -622,7 +632,7 @@ SetupWebServer::SetupWebServer() , netState_(NetState::Uninitialized) , tuner_(nullptr) , audio_(nullptr) - , routeContext_{nullptr, nullptr, nullptr} + , routeContext_{nullptr, nullptr, nullptr, {}} { } @@ -639,7 +649,7 @@ SetupWebServer::SetupWebServer(SetupWebServer&& other) noexcept other.netState_ = NetState::Uninitialized; other.tuner_ = nullptr; other.audio_ = nullptr; - other.routeContext_ = {nullptr, nullptr, nullptr}; + other.routeContext_ = {nullptr, nullptr, nullptr, {}}; } SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept @@ -659,7 +669,7 @@ SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept other.netState_ = NetState::Uninitialized; other.tuner_ = nullptr; other.audio_ = nullptr; - other.routeContext_ = {nullptr, nullptr, nullptr}; + other.routeContext_ = {nullptr, nullptr, nullptr, {}}; } return *this; } @@ -670,14 +680,15 @@ SetupWebServer::~SetupWebServer() httpd_stop(server_); server_ = nullptr; } - routeContext_ = {nullptr, nullptr, nullptr}; + routeContext_ = {nullptr, nullptr, nullptr, {}}; } std::expected SetupWebServer::start( core::ISecureStore& store, NetState netState, tuner::TunerService& tuner, - audio::AudioService& audio) + audio::AudioService& audio, + core::CompanionChipStatus companionChips) { if (server_ != nullptr) { return {}; @@ -690,6 +701,7 @@ std::expected SetupWebServer::start( routeContext_.store = &store; routeContext_.tuner = &tuner; routeContext_.audio = &audio; + routeContext_.companionChips = companionChips; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = 80; @@ -697,7 +709,7 @@ std::expected SetupWebServer::start( if (httpd_start(&server_, &config) != ESP_OK) { ESP_LOGE(kTag, "httpd_start failed"); - routeContext_ = {nullptr, nullptr, nullptr}; + routeContext_ = {nullptr, nullptr, nullptr, {}}; return std::unexpected(NetError::HttpServerStartFailed); } diff --git a/Software/docs/manual/ch-api.tex b/Software/docs/manual/ch-api.tex index adc2a26..59f3b7a 100644 --- a/Software/docs/manual/ch-api.tex +++ b/Software/docs/manual/ch-api.tex @@ -36,13 +36,16 @@ Returns a health-check DTO serialised by \begin{drnote}[Response schema] \begin{drcode}[JSON] -{"status":"ok","fw":"0.5.0"} +{"status":"ok","fw":"0.6.0", + "chips":{"si4684":true,"adau1701":true,"bt1035":true}} \end{drcode} \begin{itemize} \item \texttt{status} --- coarse indicator; \texttt{ok} when the firmware is running normally. \item \texttt{fw} --- firmware release string (\texttt{core::FirmwareVersion}). + \item \texttt{chips} --- companion-chip boot flags after + \texttt{HardwareBootstrap::boot()} (Slice~8 integration). \end{itemize} \end{drnote} diff --git a/Software/main/hardware_bootstrap.cpp b/Software/main/hardware_bootstrap.cpp index 18ef927..cf3393d 100644 --- a/Software/main/hardware_bootstrap.cpp +++ b/Software/main/hardware_bootstrap.cpp @@ -115,4 +115,13 @@ audio::AudioService& HardwareBootstrap::audioService() return gAudioService; } +core::CompanionChipStatus HardwareBootstrap::companionChipStatus() noexcept +{ + return core::CompanionChipStatus{ + .si4684Ready = gSi4684.isBooted(), + .adau1701Ready = gAdau1701.isBooted(), + .bt1035Ready = gBt1035.isBooted(), + }; +} + } // namespace hardware diff --git a/Software/main/hardware_bootstrap.hpp b/Software/main/hardware_bootstrap.hpp index 6b19ab8..81626bb 100644 --- a/Software/main/hardware_bootstrap.hpp +++ b/Software/main/hardware_bootstrap.hpp @@ -12,6 +12,8 @@ */ #pragma once +#include "core/CompanionChipStatus.hpp" + #include namespace audio { @@ -94,6 +96,18 @@ public: * @date 2026-07-06 */ [[nodiscard]] static audio::AudioService& audioService(); + + /** + * @brief companionChipStatus — snapshot of companion-chip boot flags. + * + * @dname companionChipStatus + * @return Ready flags for Si4684, ADAU1701, BT1035. + * @pubstate reads static driver isBooted() after boot(). + * + * @author Michele Bigi + * @date 2026-07-06 + */ + [[nodiscard]] static core::CompanionChipStatus companionChipStatus() noexcept; }; } // namespace hardware diff --git a/Software/main/main.cpp b/Software/main/main.cpp index e0a149c..38964b6 100644 --- a/Software/main/main.cpp +++ b/Software/main/main.cpp @@ -61,7 +61,8 @@ extern "C" void app_main() static secure_store::NvsSecureStore store; auto netResult = net::NetBootstrap::start( - store, tunerService, hardware::HardwareBootstrap::audioService()); + store, tunerService, hardware::HardwareBootstrap::audioService(), + hardware::HardwareBootstrap::companionChipStatus()); if (!netResult) { ESP_LOGE(kTag, "network bootstrap failed"); return;