Add internet radio streaming with runtime API, modernize web UI, remove auto-tune/beep at boot

Streaming (main feature this session):
- New WebRadioConfig/WebRadioJson core types, ISecureStore-backed persistence
- New webradio::WebRadioService (thread-safe live config) + GET/POST /api/streaming
- web_radio_stream task now runtime-toggleable (no reboot), no hardcoded URL
- Content-Type diagnostic: warns clearly when a URL is a webpage, not an audio stream

Boot cleanup:
- Removed boot-time auto FM/DAB tune, auto-beep, and the (now-concluded) Si4684
  crystal IBIAS/CTUN empirical sweep from main.cpp — tuning/beep are on-demand
  via the existing REST API only

Web UI:
- Modernized styling (cards, gradients, toggle switches, light/dark theme)
- New Stream tab wired to /api/streaming

Fixes found via real idf.py build (not just clangd):
- Restored wrongly-removed si4684/Si4684Tuner.hpp include in main.cpp
- Fixed MP3Decode() argument types in web_radio_stream.cpp (unsigned char**/int*)

Quality-gate fixes:
- Host-test stub headers (esp_log.h, freertos/*) so TunerService.cpp's
  scanForStation logging/pacing compiles for station_service_test /
  integration_service_test instead of running stale binaries
- Added WifiScanner and WebRadioService manual sections; filled in missing
  Doxygen docs on BluetoothService, i2s_sdata_probe, test_firmware, Bt1035At
- Ignore clangd's .cache/ index directory

Also includes prior uncommitted work carried in the tree: Wi-Fi/Bluetooth
device scan REST API and UI (WifiScanner, BT scan), SigmaStudio TCP bridge,
and the current ADAU1701 SigmaStudio DSP program export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 21:15:22 +02:00
co-authored by Claude Sonnet 5
parent 8a8523515d
commit 6f7b6dd12c
131 changed files with 20309 additions and 1702 deletions
+46 -16
View File
@@ -8,9 +8,9 @@
The Feasycom FSC-BT1035 (Qualcomm QCC3056) is the wireless output stage of
DigiRadio: it receives PCM from the ADAU1701 over I\textsuperscript{2}S and
streams Bluetooth audio with aptX, aptX~HD, and aptX~Adaptive. This chapter
documents how the ESP32-S3 controls the module over UART (AT commands with
RTS/CTS), why I\textsuperscript{2}S slave mode is mandatory, and how
\texttt{bt1035::Bt1035Driver} implements the bring-up sequence.
documents how the ESP32-S3 controls the module over UART (AT commands, no
hardware flow control), why I\textsuperscript{2}S slave mode is mandatory,
and how \texttt{bt1035::Bt1035Driver} implements the bring-up sequence.
\begin{drref}[Hardware context]
Board wiring (UART pins, I\textsuperscript{2}S to the module, flow control)
@@ -30,9 +30,9 @@ accepted and encoded for transmission.
Without firmware init the module may stay in a default mode that ignores the
I\textsuperscript{2}S bus from the ADAU1701. The mandatory
\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=67} commands select I\textsuperscript{2}S
slave input at 48\,kHz --- omitting them silently breaks the entire wireless
output (Section~\ref{sec:bt1035-i2s}).
\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=35} commands select I\textsuperscript{2}S
slave input at 48\,kHz, 24-bit --- omitting them silently breaks the entire
wireless output (Section~\ref{sec:bt1035-i2s}).
\section{Control interface}
\label{sec:bt1035-uart}
@@ -48,7 +48,7 @@ output (Section~\ref{sec:bt1035-i2s}).
Port & UART2 (not the console UART) \\
Baud rate & 115200 \\
Data format & 8N1 \\
Flow control & Hardware RTS/CTS (required) \\
Flow control & Disabled on the ESP32 UART (\texttt{UART\_HW\_FLOWCTRL\_DISABLE}) \\
Reset & GPIO active-low pulse at boot \\
SYS\_CTL & Held active to enable the module \\
\bottomrule
@@ -57,6 +57,18 @@ output (Section~\ref{sec:bt1035-i2s}).
\label{tab:bt1035-uart}
\end{table}
\begin{drnote}[Flow control disabled, not just unused]
Earlier revisions wired \texttt{RTS}/\texttt{CTS} through
\texttt{Bt1035Pins} and configured \texttt{UART\_HW\_FLOWCTRL\_CTS\_RTS}.
That was reverted: \texttt{Bt1035Driver.cpp} now configures
\texttt{UART\_HW\_FLOWCTRL\_DISABLE} and passes
\texttt{UART\_PIN\_NO\_CHANGE} for both lines --- GPIO14/21 are not driven by
this driver. No AT command in \texttt{core::Bt1035AtCommand} configures flow
control on the module side either; this has not caused observed boot
failures, but has not been independently verified against the module's own
flow-control expectation.
\end{drnote}
\subsection{Response handling}
Every command expects a module reply containing \texttt{OK} or
@@ -69,7 +81,7 @@ Every command expects a module reply containing \texttt{OK} or
\end{itemize}
Host tests in \drpath{components/core/test/bt1035_at_test.cpp} lock the
init sequence (\texttt{AT+AUXCFG=3}, \texttt{AT+I2SCFG=67}) and the parser.
init sequence (\texttt{AT+AUXCFG=3}, \texttt{AT+I2SCFG=35}) and the parser.
\section{Mandatory I\textsuperscript{2}S slave mode}
\label{sec:bt1035-i2s}
@@ -80,7 +92,9 @@ with shared BCLK/LRCLK (see Chapter~\ref{ch:hardware}). The init sequence
must use:
\begin{itemize}
\item \texttt{AT+AUXCFG=3} --- I\textsuperscript{2}S mode (§5.1.25)
\item \texttt{AT+I2SCFG=67} --- I\textsuperscript{2}S slave, 48\,kHz, 32-bit (§5.1.4)
\item \texttt{AT+I2SCFG=35} --- I\textsuperscript{2}S slave, 48\,kHz, 24-bit (§5.1.4),
matching the ADAU1701 serial output word length configured in the
SigmaStudio Hardware Configuration
\end{itemize}
\texttt{AT+AUXCFG=1} (Line-In) does \textbf{not} match the schematic.
AGENTS.md treats skipping I\textsuperscript{2}S init as a production bug.
@@ -101,16 +115,18 @@ I\textsuperscript{2}S path is ready before Wi-Fi starts.
>={Latex}, node distance=3mm]
\node[drstep, fill=black!6] (sys) {SYS\_CTL high, RESET\# pulse};
\node[drstep, fill=black!6, below=of sys] (uart)
{Install UART2 @ 115200, RTS/CTS};
\node[drstep, fill=black!8, below=of uart] (at)
{Install UART2 @ 115200, flow control disabled};
\node[drstep, fill=black!7, below=of uart] (swrst)
{Send \texttt{AT+RESET} (best-effort), settle 500\,ms, flush RX};
\node[drstep, fill=black!8, below=of swrst] (at)
{Send \texttt{AT} --- expect OK};
\node[drstep, fill=black!10, below=of at] (aux)
{Send \texttt{AT+AUXCFG=3} --- expect OK (I\textsuperscript{2}S)};
\node[drstep, fill=black!10, below=of aux] (i2s)
{Send \texttt{AT+I2SCFG=67} --- expect OK (slave 48\,kHz)};
{Send \texttt{AT+I2SCFG=35} --- expect OK (slave 48\,kHz, 24-bit)};
\node[drstep, fill=black!6, below=of i2s] (done)
{\texttt{Bt1035Driver::isBooted()} = true};
\foreach \a/\b in {sys/uart, uart/at, at/aux, aux/i2s, i2s/done} {
\foreach \a/\b in {sys/uart, uart/swrst, swrst/at, at/aux, aux/i2s, i2s/done} {
\draw[->] (\a) -- (\b);
}
\end{tikzpicture}
@@ -118,6 +134,17 @@ I\textsuperscript{2}S path is ready before Wi-Fi starts.
\label{fig:bt1035-boot}
\end{figure}
\begin{drnote}[AT+RESET is best-effort, not gated]
Unlike \texttt{AT+AUXCFG=3}/\texttt{AT+I2SCFG=35} (mandatory,
\texttt{core::bootInitSequence()}, boot fails if either is refused),
\texttt{AT+RESET} is sent separately and its result is discarded: it is
unverified whether this Feasycom firmware acknowledges the command before
rebooting, resets silently without a reply, or rejects it outright. Gating
boot on an unconfirmed ack would risk breaking an otherwise-working sequence.
The 500\,ms settle delay and RX flush after sending it mirror the delay
already used after the hardware GPIO reset pulse.
\end{drnote}
Pairing, codec selection, and volume over Bluetooth are handled by the
module's own firmware and NVS; DigiRadio firmware currently implements
only the I\textsuperscript{2}S bring-up required for the wired audio path.
@@ -158,9 +185,10 @@ The firmware enumerates every command it sends. Wire formats follow
\begin{tabular}{@{}L{2.4cm}L{3.4cm}L{5.5cm}@{}}
\drhead Enum & Line sent & Programming guide \\
\midrule
\texttt{Reset} & \texttt{AT+RESET} & software reset, best-effort \\
\texttt{Ping} & \texttt{AT} & link check \\
\texttt{I2sMode} & \texttt{AT+AUXCFG=3} & §5.1.25 Param=3 I2S \\
\texttt{I2sSlave48k32} & \texttt{AT+I2SCFG=67} & §5.1.4 slave 48\,kHz 32-bit \\
\texttt{I2sSlave48k24} & \texttt{AT+I2SCFG=35} & §5.1.4 slave 48\,kHz 24-bit \\
\texttt{PairDiscoverable} & \texttt{AT+PAIR=1} & §5.1.20 enter discoverable \\
\texttt{PairHidden} & \texttt{AT+PAIR=0} & §5.1.20 leave discoverable \\
\texttt{A2dpStat} & \texttt{AT+A2DPSTAT} & §5.3.1; states 1--5 \\
@@ -171,7 +199,9 @@ The firmware enumerates every command it sends. Wire formats follow
\bottomrule
\end{tabular}
\caption{Enumerated AT commands (\texttt{core::Bt1035AtCommand}). Boot
uses Ping + I2sMode + I2sSlave48k32; pairing commands are runtime.}
sends Reset (best-effort) then the mandatory Ping + I2sMode +
I2sSlave48k24 (\texttt{core::bootInitSequence()}); pairing commands are
runtime.}
\label{tab:bt1035-at}
\end{table}
@@ -258,7 +288,7 @@ bt1035::Bt1035Driver& bt = ...;
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sMode); !r) {
// handle Bt1035Error
}
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sSlave48k32); !r) {
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sSlave48k24); !r) {
// handle Bt1035Error
}
\end{verbatim}