Add BT1035 pairing and station presets (fw 0.7.0).
Expose discoverable mode and A2DP control over REST, add persisted DAB/FM preset list with web UI, and document gaps in docs/TODO.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# DigiRadio — consolidated TODO (audit 2026-07-06)
|
||||
|
||||
Firmware **0.7.0** after BT1035 pairing + Slice 4 presets. This list
|
||||
cross-checks code, manual, API, UI, and `instructions.md`.
|
||||
|
||||
## Done in this slice
|
||||
|
||||
| Area | Status |
|
||||
|------|--------|
|
||||
| BT1035 pairing AT (`AT+PAIR`, `AT+A2DPSTAT`, `AT+A2DPDISC`) | Done |
|
||||
| `BluetoothService` + `/api/bluetooth/*` + UI | Done |
|
||||
| `Station`, `StationList`, NVS persistence | Done |
|
||||
| `StationService` + `/api/stations/*` + UI | Done |
|
||||
| Host tests (10/10 green) | Done |
|
||||
| Manual: `ch-api`, `ch-bt1035`, `ch-classes` | Done |
|
||||
|
||||
## High priority (next)
|
||||
|
||||
1. **Device flash / HIL** — verify Si4684, ADAU1701, BT1035 on hardware; pairing with real headphones; preset recall across reboot.
|
||||
2. **DAB preset save from UI** — “Save current tune target” stores ensemble index only; capture `service_id` / `component_id` from the last played DAB service (needs UI state or tuner cache).
|
||||
3. **FM band switch UX** — Si4684 boots DAB; FM tune may reload FM image; document/limit band changes in UI (auto-reload or explicit band selector).
|
||||
4. **NVS encryption** — enable `nvs_keys` partition for production (Wi-Fi, presets, future user creds).
|
||||
5. **Slice 8 integration** — unify tuner + audio + presets in a single “now playing” model; source selection (DAB / FM / BT Line-In).
|
||||
|
||||
## Medium priority
|
||||
|
||||
6. **IBtModule interface** — AGENTS mentions it; driver is used directly today. Add when a second BT module or host fake is needed.
|
||||
7. **BT1035 extended AT** — `AT+NAME`, `AT+PLIST`, `AT+A2DPCONN`, event-driven `+PAIRED` / `+A2DPDEV` (UART listener task).
|
||||
8. **Station list reorder API** — `StationList::move()` exists in core; no HTTP route yet.
|
||||
9. **EQ UI completeness** — web UI exposes master/mixer/enhance; per-band EQ editing not in UI (API supports full profile PUT).
|
||||
10. **Si4684 RSQ / scan UX** — driver + HTTP largely done; polish seek, service list refresh, signal display on UI.
|
||||
11. **User credentials** — `ISecureStore` extension for login (out of scope until product needs it).
|
||||
|
||||
## Documentation / tooling
|
||||
|
||||
12. **Overleaf sync** — GitHub is source of truth; root `docs/` symlink can break Overleaf push; compile from `Software/docs/manual/manual.tex`.
|
||||
13. **Firmware version single source** — `0.7.0` in `SetupWebServer.cpp`; align `FirmwareVersion` / health test constants if desired.
|
||||
14. **Doxygen pass** — run `doxygen Doxyfile` on CI host with ESP-IDF toolchain.
|
||||
|
||||
## Low priority / ideas
|
||||
|
||||
15. Physical preset buttons → map GPIO to `StationService::tuneToIndex` by `PresetSlot`.
|
||||
16. OTA updates, mDNS hostname (`digiradio.local`), HTTPS on LAN.
|
||||
17. aptX license note (Feasycom) — commercial firmware variant if needed.
|
||||
|
||||
## Test gaps
|
||||
|
||||
| Missing test | Layer |
|
||||
|--------------|-------|
|
||||
| `BluetoothService` with fake driver | Host (needs `IBtModule` or inject interface) |
|
||||
| `StationService` + fake `ISecureStore` | Host |
|
||||
| `NvsSecureStore` station round-trip | Target / integration |
|
||||
| BT1035 UART HIL | Hardware-only, marked separate |
|
||||
|
||||
## Roadmap alignment (`instructions.md`)
|
||||
|
||||
| Slice | Item | State |
|
||||
|-------|------|-------|
|
||||
| 4 | Station list + persistence + UI | **Done** (basic CRUD + tune) |
|
||||
| 5 | Si4684 tuning / RSQ / DAB properties | Mostly done; UI polish open |
|
||||
| 7 | BT1035 pairing | **Done** (discover + A2DP stat/disconnect) |
|
||||
| 8 | TunerService + AudioService E2E | Partial — health chips OK; unified UX open |
|
||||
@@ -36,7 +36,7 @@ Returns a health-check DTO serialised by
|
||||
|
||||
\begin{drnote}[Response schema]
|
||||
\begin{drcode}[JSON]
|
||||
{"status":"ok","fw":"0.6.0",
|
||||
{"status":"ok","fw":"0.7.0",
|
||||
"chips":{"si4684":true,"adau1701":true,"bt1035":true}}
|
||||
\end{drcode}
|
||||
\begin{itemize}
|
||||
@@ -252,11 +252,70 @@ Adjusts bass emphasis via a PEQ overlay on bands 1--2 (100\,Hz /
|
||||
400\,Hz). Request and response schema match
|
||||
\texttt{POST /api/audio/stereo-enhance} (Section~\ref{sec:api-audio-stereo-enhance}).
|
||||
|
||||
% ------------------------------------------------------------------
|
||||
% Bluetooth (Slice 7)
|
||||
% ------------------------------------------------------------------
|
||||
|
||||
\subsection{\texttt{GET /api/bluetooth/status}}
|
||||
\label{sec:api-bluetooth-status}
|
||||
|
||||
Returns BT1035 boot flag, whether discoverable mode was requested, and the
|
||||
last read A2DP state. Serialised by
|
||||
\texttt{core::serializeBluetoothStatusJson()}.
|
||||
|
||||
\begin{drnote}[Response schema]
|
||||
\begin{drcode}[JSON]
|
||||
{"booted":true,"pairing":false,"a2dp":"standby"}
|
||||
\end{drcode}
|
||||
\end{drnote}
|
||||
|
||||
\subsection{\texttt{POST /api/bluetooth/pair}}
|
||||
\label{sec:api-bluetooth-pair}
|
||||
|
||||
Enters discoverable mode (\texttt{AT+PAIR=1}). Success:
|
||||
\texttt{\{"status":"pairing"\}}.
|
||||
|
||||
\subsection{\texttt{POST /api/bluetooth/pair/stop}}
|
||||
\label{sec:api-bluetooth-pair-stop}
|
||||
|
||||
Leaves discoverable mode (\texttt{AT+PAIR=0}). Success:
|
||||
\texttt{\{"status":"idle"\}}.
|
||||
|
||||
\subsection{\texttt{POST /api/bluetooth/disconnect}}
|
||||
\label{sec:api-bluetooth-disconnect}
|
||||
|
||||
Releases the current A2DP session (\texttt{AT+A2DPDISC}).
|
||||
|
||||
% ------------------------------------------------------------------
|
||||
% Station presets (Slice 4)
|
||||
% ------------------------------------------------------------------
|
||||
|
||||
\subsection{\texttt{GET /api/stations}}
|
||||
\label{sec:api-stations-get}
|
||||
|
||||
Returns presets serialised by \texttt{core::serializeStationListJson()}.
|
||||
|
||||
\subsection{\texttt{POST /api/stations}}
|
||||
\label{sec:api-stations-post}
|
||||
|
||||
Adds one preset (\texttt{core::parseStationJson()}); persists via
|
||||
\texttt{ISecureStore::saveStationListJson()}.
|
||||
|
||||
\subsection{\texttt{POST /api/stations/remove}}
|
||||
\label{sec:api-stations-remove}
|
||||
|
||||
Removes a preset by list index (\texttt{\{"index":0\}}).
|
||||
|
||||
\subsection{\texttt{POST /api/stations/tune}}
|
||||
\label{sec:api-stations-tune}
|
||||
|
||||
Recalls a preset via \texttt{station::StationService::tuneToIndex()}.
|
||||
|
||||
\section{Boot and network state machine}
|
||||
\label{sec:api-boot-flow}
|
||||
|
||||
At boot, \texttt{net::NetBootstrap::start(store, tuner, audio)} consults
|
||||
\texttt{ISecureStore::hasWifiCredentials()}:
|
||||
At boot, \texttt{net::NetBootstrap::start(store, tuner, audio, bluetooth,
|
||||
stations)} consults \texttt{ISecureStore::hasWifiCredentials()}:
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Credentials present} --- create STA netif, connect via
|
||||
@@ -275,7 +334,8 @@ This explicit \texttt{enum class NetState} replaces ad-hoc flags; see
|
||||
Wi-Fi credentials are stored in NVS namespace \texttt{digiradio}, keys
|
||||
\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{secure\_store::NvsAudioProfileStore}. Station presets use key
|
||||
\texttt{station\_list} (JSON blob). Passwords are wrapped in
|
||||
\texttt{core::Secret} in RAM and are never logged or returned by the API.
|
||||
|
||||
\begin{drcaution}[Encryption at rest]
|
||||
|
||||
@@ -131,8 +131,8 @@ only the Line-In bring-up required for the wired audio path.
|
||||
Host-testable command strings and OK/ERROR classification \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{BT1035 control stack (Slice~6). Future HTTP/UI for pairing will
|
||||
sit above the driver without changing the init contract.}
|
||||
\caption{BT1035 control stack (Slice~6--7). Pairing and A2DP status are
|
||||
exposed on \texttt{/api/bluetooth/*} via \texttt{BluetoothService}.}
|
||||
\label{tab:bt1035-stack}
|
||||
\end{table}
|
||||
|
||||
@@ -149,9 +149,14 @@ updating \texttt{core::Bt1035AtCommand}, the manual, and a host test.
|
||||
\midrule
|
||||
\texttt{Ping} & \texttt{AT} & Verify UART link \\
|
||||
\texttt{AuxLineIn} & \texttt{AT+AUXCFG=1} & Enable Line-In from ADAU \\
|
||||
\texttt{PairDiscoverable} & \texttt{AT+PAIR=1} & Enter discoverable mode \\
|
||||
\texttt{PairHidden} & \texttt{AT+PAIR=0} & Leave discoverable mode \\
|
||||
\texttt{A2dpStat} & \texttt{AT+A2DPSTAT} & Read link state \\
|
||||
\texttt{A2dpDisconnect} & \texttt{AT+A2DPDISC} & Release A2DP session \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{AT commands used at boot (\texttt{core::bootInitSequence()}).}
|
||||
\caption{Enumerated AT commands (\texttt{core::Bt1035AtCommand}). Boot
|
||||
uses Ping + AuxLineIn only; pairing commands are runtime.}
|
||||
\label{tab:bt1035-at}
|
||||
\end{table}
|
||||
|
||||
@@ -170,6 +175,10 @@ updating \texttt{core::Bt1035AtCommand}, the manual, and a host test.
|
||||
\texttt{boot()} & Reset, UART init, run \texttt{bootInitSequence()} \\
|
||||
\texttt{isBooted()} & \texttt{true} after Line-In init succeeded \\
|
||||
\texttt{sendCommand(cmd)} & Send one typed command, expect OK \\
|
||||
\texttt{enterPairingMode()} & \texttt{AT+PAIR=1} \\
|
||||
\texttt{leavePairingMode()} & \texttt{AT+PAIR=0} \\
|
||||
\texttt{queryA2dpState()} & \texttt{AT+A2DPSTAT}, parse \texttt{+A2DPSTAT=} \\
|
||||
\texttt{disconnectA2dp()} & \texttt{AT+A2DPDISC} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Public driver API.}
|
||||
@@ -232,7 +241,7 @@ if (auto r = bt.sendCommand(core::Bt1035AtCommand::AuxLineIn); !r) {
|
||||
|
||||
\begin{itemize}
|
||||
\item Feasycom FSC-BT1035 AT command manual (vendor) --- full command set
|
||||
for pairing, name, and codec options not yet wrapped by firmware.
|
||||
for name, paired-device list, and reconnect not yet wrapped by firmware.
|
||||
\item Chapter~\ref{ch:hardware} --- pin map and I\textsuperscript{2}S routing.
|
||||
\item Chapter~\ref{ch:adau1701} --- DSP output that feeds the module.
|
||||
\item \texttt{components/core/test/bt1035\_at\_test.cpp} --- init sequence test.
|
||||
|
||||
@@ -56,10 +56,13 @@ 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}, 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}.
|
||||
\texttt{POST /api/wifi}, tuner routes (\texttt{/api/tuner/*}), audio
|
||||
routes (\texttt{/api/audio/*}), Bluetooth (\texttt{/api/bluetooth/*}), and
|
||||
station presets (\texttt{/api/stations/*}). JSON parsing and serialisation
|
||||
delegate to the pure core; credentials persist via \texttt{ISecureStore};
|
||||
tuner via \texttt{tuner::TunerService}; audio via
|
||||
\texttt{audio::AudioService}; Bluetooth via \texttt{bluetooth::BluetoothService};
|
||||
presets via \texttt{station::StationService}.
|
||||
|
||||
\section{NetBootstrap}\label{cls:NetBootstrap}
|
||||
Owns network resources for setup or STA mode.
|
||||
@@ -222,3 +225,32 @@ Validated at the HTTP boundary by \texttt{core::parseEnhanceLevelJson()}.
|
||||
\section{NvsAudioProfileStore}\label{cls:NvsAudioProfileStore}
|
||||
\texttt{IAudioProfileStore} implementation storing serialised
|
||||
\texttt{AudioProfile} JSON in NVS namespace \texttt{digiradio}.
|
||||
|
||||
\section{StationName}\label{cls:StationName}
|
||||
Strong type for a preset label (1--32 bytes). Parsed at the HTTP boundary
|
||||
via \texttt{StationName::tryFrom()}.
|
||||
|
||||
\section{PresetSlot}\label{cls:PresetSlot}
|
||||
Optional hardware preset button index (1--20). Validated by
|
||||
\texttt{PresetSlot::tryFrom()}.
|
||||
|
||||
\section{Station}\label{cls:Station}
|
||||
Immutable preset value: name, band (DAB/FM), tune coordinates, optional
|
||||
preset slot. FM presets carry \texttt{FrequencyKHz}; DAB presets carry
|
||||
ensemble index and optional service/component ids for playback.
|
||||
|
||||
\section{StationList}\label{cls:StationList}
|
||||
Pure-domain ordered collection (max 20 entries) with add/remove/move and
|
||||
duplicate tune-target rejection. Persisted as JSON through
|
||||
\texttt{ISecureStore}.
|
||||
|
||||
\section{StationService}\label{cls:StationService}
|
||||
Application service loading/saving presets from NVS and recalling them via
|
||||
\texttt{TunerService}. Exposed on \texttt{/api/stations/*} and the Presets
|
||||
web UI section.
|
||||
|
||||
\section{BluetoothService}\label{cls:BluetoothService}
|
||||
Application service for BT1035 pairing and A2DP status
|
||||
(Chapter~\ref{ch:bt1035}). Delegates to \texttt{Bt1035Driver}; tracks
|
||||
whether discoverable mode was requested. Exposed on
|
||||
\texttt{/api/bluetooth/*}.
|
||||
|
||||
Reference in New Issue
Block a user