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:
@@ -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]
|
||||
|
||||
@@ -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 4–5)
|
||||
% ------------------------------------------------------------------
|
||||
|
||||
\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}.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user