Add Slice 5 ADAU1701 runtime audio control (firmware 0.5.0).

Safeload mixer/EQ/master on the ADAU1701, persist AudioProfile in NVS,
expose /api/audio routes and web UI controls, with host tests and manual sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 16:47:58 +02:00
co-authored by Cursor
parent 4b931b0aad
commit ab3651e678
58 changed files with 3191 additions and 41 deletions
+51 -4
View File
@@ -6,7 +6,7 @@ implemented in \texttt{SetupWebServer}. Request bodies are parsed into
domain types in the pure core (\texttt{components/core}) before any
persistence or driver call. Exact C++ signatures live in the generated
Doxygen output under \texttt{docs/api/}; this chapter documents the
wire protocol and behaviour as shipped in firmware~0.4.0 (Slices~1--4).
wire protocol and behaviour as shipped in firmware~0.5.0 (Slices~1--5).
\section{Transport and reachability}
@@ -36,7 +36,7 @@ Returns a health-check DTO serialised by
\begin{drnote}[Response schema]
\begin{drcode}[JSON]
{"status":"ok","fw":"0.4.0"}
{"status":"ok","fw":"0.5.0"}
\end{drcode}
\begin{itemize}
\item \texttt{status} --- coarse indicator; \texttt{ok} when the
@@ -172,10 +172,55 @@ Seeks FM upward (no request body). Returns
\texttt{\{"frequency\_khz":...\}} on success. HTTP status: \textbf{200 OK};
\textbf{409} on seek failure.
\subsection{\texttt{GET /api/audio/profile}}
\label{sec:api-audio-profile-get}
Returns the current ADAU1701 audio snapshot serialised by
\texttt{core::serializeAudioProfileJson()} from \texttt{core::AudioProfile}.
The handler reads \texttt{audio::AudioService::currentProfile()}.
\begin{drnote}[Response schema (excerpt)]
\begin{drcode}[JSON]
{"mixer":{"si4684_left_db":0,"si4684_right_db":0,"esp32_left_db":0,
"esp32_right_db":0,"mix_left_db":0,"mix_right_db":0},
"master":{"left_db":0,"right_db":0},
"eq":[{"gain_db":0,"center_hz":40,"q":1.414}, ...]}
\end{drcode}
Six EQ bands are always present (\texttt{eq} array length~6).
\end{drnote}
HTTP status: \textbf{200 OK}; \textbf{503} when the audio service is
unavailable.
\subsection{\texttt{PUT /api/audio/profile}}
\label{sec:api-audio-profile-put}
Applies a full audio profile. Body parsed by
\texttt{core::parseAudioProfileJson()}; on success
\texttt{audio::AudioService::applyProfile(..., persist=true)} safeloads
the ADAU1701 and writes NVS key \texttt{audio\_profile\_json}.
\begin{drnote}[Success response]
\begin{drcode}[JSON]
{"status":"saved"}
\end{drcode}
\end{drnote}
HTTP status: \textbf{200 OK}; \textbf{400} for parse/validation failures;
\textbf{500} when safeload or NVS persistence fails.
\subsection{\texttt{POST /api/audio/reset}}
\label{sec:api-audio-reset}
Restores \texttt{AudioProfile::factoryDefault()} (flat EQ, 0\,dB gains),
applies it to the DSP, and persists to NVS. Success response:
\texttt{\{"status":"saved"\}}. HTTP status: \textbf{200 OK}; \textbf{500}
on apply/persist failure.
\section{Boot and network state machine}
\label{sec:api-boot-flow}
At boot, \texttt{net::NetBootstrap::start(store, tuner)} consults
At boot, \texttt{net::NetBootstrap::start(store, tuner, audio)} consults
\texttt{ISecureStore::hasWifiCredentials()}:
\begin{enumerate}
@@ -193,7 +238,9 @@ This explicit \texttt{enum class NetState} replaces ad-hoc flags; see
\label{sec:api-storage}
Wi-Fi credentials are stored in NVS namespace \texttt{digiradio}, keys
\texttt{wifi\_ssid} and \texttt{wifi\_pwd}. Passwords are wrapped in
\texttt{wifi\_ssid} and \texttt{wifi\_pwd}. Audio profiles (non-secret) use
the same namespace, key \texttt{audio\_profile\_json}, via
\texttt{secure\_store::NvsAudioProfileStore}. Passwords are wrapped in
\texttt{core::Secret} in RAM and are never logged or returned by the API.
\begin{drcaution}[Encryption at rest]
+54 -6
View File
@@ -56,16 +56,17 @@ configured SoftAP. Imperative shell; no business logic.
\section{SetupWebServer}\label{cls:SetupWebServer}
Minimal HTTP server: gzipped setup UI, \texttt{GET /api/health},
\texttt{POST /api/wifi}, and tuner routes (\texttt{/api/tuner/*}).
JSON parsing and serialisation delegate to the pure core; credentials
persist via \texttt{ISecureStore}; tuner operations via
\texttt{tuner::TunerService}.
\texttt{POST /api/wifi}, tuner routes (\texttt{/api/tuner/*}), and audio
routes (\texttt{/api/audio/*}). JSON parsing and serialisation delegate to
the pure core; credentials persist via \texttt{ISecureStore}; tuner via
\texttt{tuner::TunerService}; audio via \texttt{audio::AudioService}.
\section{NetBootstrap}\label{cls:NetBootstrap}
Owns network resources for setup or STA mode.
\texttt{start(store, tuner)} initialises the platform, joins stored Wi-Fi
when credentials exist, or falls back to the \texttt{DigiRadio-setup}
SoftAP. Must outlive \texttt{app\_main} for the process lifetime.
\texttt{start(store, tuner, audio)} also wires the audio REST routes.
% ------------------------------------------------------------------
% Domain core + secure store (Slice 2)
@@ -135,10 +136,47 @@ DTOs.
RAII I2C driver for the ADAU1701 SigmaDSP. \texttt{boot()} asserts RESET\#,
initialises the shared I2C bus, and replays the SigmaStudio export from
\texttt{Firmware/ADAU1701-Firmware/} on every power-up (no EEPROM self-boot
on DigiRadio).
on DigiRadio). Runtime mixer, EQ, and master volume updates use the
ADAU1701 safeload mechanism via \texttt{applyProfile()} and related methods.
\section{Adau1701Dsp}\label{cls:Adau1701Dsp}
\texttt{core::IDsp} adapter over \texttt{Adau1701Driver}. Maps domain-level
audio control to safeload I2C transactions without exposing parameter RAM
addresses to services.
% ------------------------------------------------------------------
% Application services (Slice 4)
% Domain core — audio (Slice 5)
% ------------------------------------------------------------------
\section{GainDb}\label{cls:GainDb}
Validated decibel gain/attenuation ($-96$\,dB to $+12$\,dB). Used for
input volumes, master level, and EQ band gain. Converted to ADAU 8.23
fixpoint via \texttt{gainDbToLinearFixpoint()} before safeload.
\section{FrequencyHz}\label{cls:FrequencyHz}
Validated PEQ centre frequency (20--20\,000\,Hz) for the 48\,kHz
SigmaStudio project. Parsed at the HTTP boundary before coefficient design.
\section{EqBandIndex}\label{cls:EqBandIndex}
Strong index into the six-band \texttt{Param EQ1} module (0--5). Maps to
parameter RAM via \texttt{adau1701::paramAddrEqBandBase()}.
\section{EqProfile}\label{cls:EqProfile}
Six-band parametric EQ snapshot with default centre frequencies
(40\,Hz--12\,kHz). Designed in the pure core; coefficients safeloaded by
\texttt{Adau1701Driver::applyEq()}.
\section{IDsp}\label{cls:IDsp}
Abstract ADAU1701 control boundary. Defines \texttt{applyProfile()},
\texttt{setInputVolume()}, \texttt{setEqBand()}, and related intent-level
operations without ESP-IDF types.
\section{IAudioProfileStore}\label{cls:IAudioProfileStore}
Persistence boundary for \texttt{AudioProfile} (mixer, EQ, master). Device
implementation: \texttt{NvsAudioProfileStore}; host tests use fakes.
% ------------------------------------------------------------------
% Application services (Slice 45)
% ------------------------------------------------------------------
\section{ITuner}\label{cls:ITuner}
@@ -156,3 +194,13 @@ target and volume, and maps driver failures to \texttt{core::TunerError}.
\texttt{ITuner} adapter over \texttt{Si4684Driver}. Translates domain calls
into SPI commands and maps \texttt{Si4684Error} to \texttt{TunerError}.
Constructed once in \texttt{HardwareBootstrap} alongside the driver.
\section{AudioService}\label{cls:AudioService}
Application service for ADAU1701 mixer, EQ, and master volume. Holds a
reference to \texttt{core::IDsp}, tracks the in-memory \texttt{AudioProfile},
loads from \texttt{IAudioProfileStore} after boot, and applies changes via
safeload. Exposed on \texttt{/api/audio/*} and the web UI Audio section.
\section{NvsAudioProfileStore}\label{cls:NvsAudioProfileStore}
\texttt{IAudioProfileStore} implementation storing serialised
\texttt{AudioProfile} JSON in NVS namespace \texttt{digiradio}.
+3
View File
@@ -194,6 +194,9 @@ bring-up:
(\texttt{DigiRadio\_IC\_1.h}) replayed through
\texttt{SIGMA\_WRITE\_REGISTER\_BLOCK} after hardware reset. The DSP
program lives in RAM only; download runs on every boot.
\item \textbf{Audio profile}: \texttt{audio::AudioService::loadAndApply()}
restores the saved \texttt{core::AudioProfile} from NVS (or factory
defaults) via ADAU1701 safeload before network bring-up.
\end{enumerate}
If either driver returns an error, the firmware logs the failure and stops