Commit Graph
17 Commits
Author SHA1 Message Date
micheleandClaude Sonnet 5 f612335632 Enable DAB_EVENT_INTERRUPT_SOURCE (0xB300) so SVRLISTINT can ever fire
Retesting DAB service-list retrieval later the same night (after the
service-list body-parsing fix and antenna calibration work) found it
far less reliable than the earlier confirmation: locked with
excellent FIC quality (97-100) but GET /api/tuner/services kept
returning service_list_empty for 30-65+ seconds.

readDabEventStatus()'s SVRLISTINT bit is gated by AN649 Property
0xB300 DAB_EVENT_INTERRUPT_SOURCE, bit0=SRVLIST_INTEN, default 0x0000
(disabled) at power-on — never written anywhere in this driver.
configureAfterBoot()'s DAB branch already writes a similarly-named
DIGITAL_SERVICE_INT_SOURCE (property 0x8100), but AN649's own text
for 0x8100 is internally inconsistent between its prose and its bit
table (VHFCAPS/VHFSW, a front-end switch field) — almost certainly a
pdftotext -raw extraction artifact merging two adjacent property
tables, the same failure mode already logged this session for
AN649/adau1701.pdf extraction. Only 0xB300's own section reads
internally consistent with its own prose, so it — not 0x8100 — is the
one that actually gates SVRLISTINT.

Verified live: the service list came back complete and correct (22
real station labels) on the next test after reflashing. Not proven
conclusively faster than before given DAB acquisition timing is
inherently variable and this was only tested once post-fix, but the
property write is unambiguously correct per its own AN649 section
regardless.

Also logged, NOT fixed: intermittent multi-second HTTP unresponsiveness
(connection timeouts even on /api/health) observed independent of this
change, both before and after — heartbeat kept logging on schedule
throughout so the system didn't crash, only the HTTP server (or
something it was blocking on, most likely a Si4684 SPI/CTS wait)
stalled and recovered on its own. Root cause not yet found; details
and next-session candidates in docs/si4684-rf-investigation-report.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-19 02:51:43 +02:00
micheleandClaude Sonnet 5 f610121fd3 Add FM ANTCAP antenna calibration: HTTP sweep control + EEPROM persistence
Confirmed live tonight that the Si4684's automatic front-end tuning
(FE_VARM/VARB properties) is measurably suboptimal on this board: a
129-value ANTCAP sweep (0-128, AN851 Appendix A) at four different FM
frequencies found antcap=102 beats auto-tune by +6 to +11 dB RSSI and
+3 to +11 dB SNR everywhere tested, consistent with the front-end
network component mismatch already logged in
docs/si4684-rf-investigation-report.md — the board's actual matching
network differs from the AN851 reference network those auto-tune
constants were derived from, so a fixed empirical override
compensates for a gap the chip's own algorithm can't see.

Wiring, bottom to top:
- Si4684Driver::tuneFm() already took an antCap byte; threaded it
  through core::ITuner::tuneFm() and si4684::Si4684Tuner::tuneFm() as
  a new parameter (default 0 = auto, unchanged behaviour for every
  existing caller).
- TunerService::tuneFm() takes an optional override instead: omitted,
  it falls back to a new defaultFmAntCap_ member so every ordinary FM
  tune (seek, scan, station recall, live UI) benefits automatically
  once calibrated, not just calls that pass antcap explicitly.
- POST /api/tuner/tune gained an optional "antcap" field for sweeping
  live without touching the saved calibration.
- POST /api/tuner/calibrate-antenna commits a sweep result: writes it
  to the 24AA025E48 EEPROM's user-writable region (word address 0x00,
  separate from the factory-locked EUI-48 at 0xFA-0xFF) via a new
  Eeprom24aa::writeFmAntCap()/readFmAntCap() pair, and immediately
  updates the live TunerService default — no reboot needed to take
  effect, though HardwareBootstrap::boot() also loads it at every
  boot so it survives power cycles. Same net::AntennaCalibration
  function-pointer bridge pattern as PhoneStreamSink/BleProvisioning,
  so components/net stays free of eeprom24aa headers.

Verified end to end on hardware: swept and found 102, saved it via
the new endpoint, confirmed the live default changed immediately,
then power-cycled and confirmed the boot log reports "FM ANTCAP
calibration loaded: 102" and a subsequent default tune reflects it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-19 02:01:09 +02:00
micheleandClaude Sonnet 5 f9f3e58d64 Fix empty DAB service list and silently-dropped last HTTP route
Two independent live-hardware bugs found testing on real DAB signal
tonight (board reconnected after this session's feature work):

1. Si4684Driver::fetchDabServiceList() double-counted the already-
   consumed SIZE field when computing the body offset: it added a
   phantom "List Size(2)" on top of the 7-byte STATUS/SIZE header
   already stripped out, shifting the service-count byte and every
   service entry by exactly 2 bytes. AN649 documents SIZE/DATA_0/
   DATA_N generically for GET_DIGITAL_SERVICE_LIST and defers the
   actual DAB payload layout to a supplemental "Digital Services
   User's Guide" we don't have, so the previous "AN649 Table 14"
   citation for that layout was never actually sourced from AN649 —
   it was guessed. Re-derived the real layout by cross-checking
   hitech95/si468x_dab_receiver's si468x_core_cmd_dab_get_service_list()
   (a working Linux driver for the same command), which also shows
   the payload is SIZE-2 bytes, not SIZE bytes — fixed the read-length
   sizing (payloadSize+5, was +7) to match. This is what made
   GET /api/tuner/services always come back empty even with a locked
   ensemble.

2. SetupWebServer registers 41 HTTP routes but httpd_config_t::
   max_uri_handlers was still 40 (set before several endpoints landed
   this session). esp_http_server's httpd_register_uri_handler()
   fails silently past the limit, logging only a generic
   "no slots left" warning with no indication of which handler was
   dropped — the 41st and therefore last-registered route,
   POST /api/stations/tune, was silently unroutable (404) on every
   boot since whichever commit pushed the count past 40. Bumped to 56
   for headroom.

Both confirmed on hardware: fresh flash boots with zero httpd
warnings; DAB service list fix not yet re-verified against a live
ensemble pending user retest (board was between test sessions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-19 01:17:52 +02:00
micheleandClaude Sonnet 5 8a69cbed08 Fix Si4684 DAB response-parsing bugs found in firmware review
readDabServiceData(): 6 of 7 response fields (dataSrc, serviceId,
componentId, byteCount, segmentIndex, segmentCount) read one byte too
early, using the old raw[4]=RESP4 convention instead of the correct
raw[5]=RESP4 (established elsewhere in this driver by getPartInfo() and
readDabDigRadStatus()'s own ficQuality/cnrDb fields). dataSrc landing on
the wrong byte meant the DAB dynamic label (PAD/now-playing text) check
(dataSrc == 2) could essentially never match — it has likely never worked.
Header buffer grown 24->25 bytes to fit the correctly-positioned last field.

fetchDabServiceList(): didn't match AN649 Table 14's "DAB/DMB Digital
Service List" layout at all — serviceCount read from the wrong byte, every
per-service field misaligned, componentId assumed 4 bytes wide (actually
2 per the spec), and only the first of a service's possibly-several
components was ever skipped past (desyncing every later entry). Rewrote
against the actual Table 14 field layout. Confirmed live yesterday this
was producing garbled service_id/component_id/label output
(component_id values decoding as literal ASCII spaces).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-18 07:50:09 +02:00
micheleandClaude Sonnet 5 9b337e7bca Fix Si4684 response-parsing offset bug: RDS, DAB acquired/event status
readFmRds(), readDabDigRadStatus()'s acquired field, and
readDabEventStatus() all read raw[4] expecting AN649's RESP4 field, but
this driver's own established convention (getPartInfo(), and the
already-correct ficQuality/cnrDb fields in readDabDigRadStatus() itself)
is raw[5]=RESP4 (raw[0]=SPI lead-in, raw[1..4]=STATUS0-3). This is why
DAB_GET_EVENT_STATUS's serviceListReady never set — it was reading
STATUS3's ERRNR bit instead of RESP4's SVRLISTINT bit, so
/api/tuner/services returned service_list_empty forever regardless of lock
quality. Fixed all four sites; readFmRds()'s fifoUsed/blockA-D were
consequently also off by one and fixed together with it.

Confirmed live: first DAB ensemble locks in this project's history (3 found
sweeping freq_index 0-35, fic_quality=100, best CNR 20 dB on index 23), and
/api/tuner/services now returns real entries instead of service_list_empty.
The service-list entry contents themselves are still garbled (a third,
separate bug in fetchDabServiceList()'s body parsing, documented but not
fixed this session — see docs/si4684-rf-investigation-report.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-16 01:24:54 +02:00
micheleandClaude Sonnet 5 6974095f35 Fix Si4684 I2S audio: PIN_CONFIG_ENABLE conflict + ADAU1701 BCLK polarity
Two independent bugs silenced the digital audio path from Si4684 into the
ADAU1701 even after the FM/DAB tune fix produced a real RF lock:

1. Si4684Driver::configureAfterBoot() wrote PIN_CONFIG_ENABLE (0x0800) as
   0x0003, enabling both I2SOUTEN and DACOUTEN. AN649: "only I2SOUTEN or
   DACOUTEN can be enabled at a time. If both enabled, only analog audio
   output is enabled" — the chip silently fell back to its unused analog
   DAC output on every boot. Fixed to 0x8002 (I2SOUTEN + INTBOUTEN),
   matching the value hitech95/si468x_dab_receiver's working ALSA codec
   driver uses (SI468X_PROP_I2S_ENABLED); I2SOUTEN alone (0x0002) was not
   sufficient on this hardware. Also fixed AUDIO_OUTPUT_CONFIG (0x0302),
   which was being written with a stray I2S-enable bit that property does
   not have (its only real field is bit0 MONO).

2. Even with the chip correctly outputting I2S, the ADAU1701 received only
   static. Traced with SIGMA_WRITE_REGISTER_BLOCK live overrides of
   SerialInputRegister (0x081F, baked into the compiled SigmaStudio export
   at ILP=0/IBP=0): IBP=1 (input data clocked on the opposite BCLK edge)
   produced real, recognizable music instead of static on a locked, strong
   FM signal — first confirmed end-to-end audio in this project's history.
   ILP=1 made it worse and was reverted; IBP=1 kept as a runtime override
   in Adau1701Driver::boot().

Remaining noise on top of the music is attributed to antenna quality
(RSSI/SNR fluctuated significantly between retunes of the same station on
the current improvised antenna) — not yet confirmed with a proper antenna.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-16 01:12:57 +02:00
micheleandClaude Sonnet 5 f5fe92f93e Fix remaining Si4684 ARG-offset bugs: seek, DAB services, RSQ/RDS/DIGRAD acks
writeCommand() had no way to send a non-zero ARG1, since it always
hardcoded ARG1=0x00 before the caller's payload. Every command whose real
ARG1 needed to carry a flag (STCACK, INTACK, SERTYPE, DIGRAD/EVENT ack) or
whose payload needed to start at ARG1 instead of ARG2 was silently broken:

- seekFm(): SEEKUP/WRAP never reached the chip (always ARG2=0x00), so
  hardware seek always searched down/no-wrap; masked by the existing
  100 kHz software-step fallback in Si4684Tuner.
- startDabService()/stopDabService(): SERVICE_ID/COMPONENT_ID shifted one
  byte right of their real ARG4-11 positions, with SERTYPE landing where
  the spec requires a fixed 0x00.
- readDabServiceData(): same shift, plus STATUS_ONLY was bit3 (0x08)
  instead of the correct bit4 (0x10).
- clearFmStc(), readFmRsq(), readFmRds(), fetchDabServiceList(),
  readDabDigRadStatus(), readDabEventStatus(): these AN649 commands have
  only ARG1 and no ARG2 at all, so the old two-argument writeCommand()
  could never carry their ack/status flags — clearFmStc()'s STCACK never
  fired in this driver's history (masked by FM_TUNE_FREQ/FM_SEEK_START
  auto-clearing STC per their own spec).

writeCommand() gains a fourth parameter, arg1 (default 0x00, preserving
every already-correct call site); each caller above now passes its flag
through arg1 instead of the payload array.

Confirmed live: first locked:true and first genuine hardware seek (not
software-fallback) in this driver's history — 87.5 -> 98.3 MHz, RSSI +12
dBuV, SNR +14 dB.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-16 00:33:46 +02:00
micheleandClaude Sonnet 5 ef15e9839c Fix Si4684 FM/DAB tune commands: ARG offset bug caused total RF blackout
writeCommand() always prepends a fixed ARG1=0x00 byte before the payload;
tuneFm()/tuneDab() built their argument arrays starting at what the author
believed was ARG1, so every byte landed one slot right of its real AN649
position and an extra unused byte was appended. The chip never received the
requested frequency. This is the root cause of the months-long total RF
blackout (RSQ frozen at all-zero on every frequency/ANTCAP value) previously
attributed to a QFN exposed-pad hardware defect — that hypothesis is now
overturned, confirmed live: RSSI/SNR now read real, frequency-dependent
values with zero STC timeouts after the fix.

Also make BT1035 boot failure non-fatal in HardwareBootstrap::boot() so a
companion-chip fault no longer halts the whole device (Si4684 tuning, web
UI, Wi-Fi already isolate BT1035 readiness via CompanionChipStatus).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-16 00:19:43 +02:00
micheleandClaude Sonnet 5 42dadea369 Fix GET_PART_INFO/GET_SYS_STATE/GET_FUNC_INFO byte offsets; confirm Si4684 blobs genuine
Si4684Driver::getPartInfo()/getSysState() existed but were never called, so
their byte-offset bugs never surfaced. Wired them into a boot-time diagnostic
log (part number, firmware revision, active image, streamed blob byte counts)
to check whether the loaded DAB/FM firmware images are genuine and intact, as
an alternative explanation to the QFN exposed-pad hardware hypothesis.

Fixed two rounds of off-by-one bugs found while doing this: the fields were
initially read one byte too far right (e.g. firmwareBuild was reading a flag
byte, not a version number); the first fix undershot in the other direction
by not accounting for readRaw()'s one-byte SPI lead-in before STATUS0 (already
documented and confirmed elsewhere in this file, in pollStc()) -- caught
because the "fixed" GET_SYS_STATE reported image=192 (0xC0), the exact
signature of STATUS3 with PUP_STATE=3 seen throughout this investigation.
All three response buffers were already sized for the lead-in byte, which
confirmed the correct fix.

Verdict, captured live: blob streamed bytes match local file sizes exactly (no
truncation), GET_SYS_STATE reports image=2 (DAB active, correct), GET_PART_INFO
reports part=4684 (matches expected Si4684 part number) with a plausible
firmware revision -- the loaded DAB firmware is genuine and intact. This closes
the last plausible firmware-side explanation for the FM/DAB no-lock symptom;
docs/si4684-rf-investigation-report.md and docs/TODO.md (P4/H5) updated with
the full record and verdict.

Also noted, not yet fixed: BT1035 AT-init now fails deterministically on every
boot (was a one-off earlier this session) -- see report's Open Items.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 22:53:27 +02:00
micheleandClaude Sonnet 5 bee8cfa86f Si4684 RF investigation report; fix ADAU1701 I2C reliability under long safeload bursts
- Add docs/si4684-rf-investigation-report.md: full record of the FM/DAB no-lock
  investigation (crystal, front-end matching, ANTCAP sweep, continuity, leading
  EP solder-defect hypothesis, PCBWay report sent) plus the separate audio
  profile NVS bug found and partially fixed this session.
- Fix sigma_i2c_write() (SigmaStudioFW.c): no retry on I2C failure meant a single
  transient NACK anywhere in a long safeload burst (EQ apply = ~55 sequential
  transactions) aborted the whole sequence. Added a 3-attempt retry.
- Add granular failure logging (AudioService::applyProfileToDsp/persistProfile,
  Adau1701Driver::applyMixer/applyEq, NvsAudioProfileStore::saveProfile error
  codes) to isolate the remaining NVS-side audio profile save failure.
- Si4684Driver::tuneFm gains an optional ANTCAP argument (default 0 = unchanged
  auto-tune behavior) used during this session's front-end matching sweep.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 22:21:04 +02:00
micheleandClaude Sonnet 5 6f7b6dd12c 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>
2026-08-08 21:15:22 +02:00
michele 34f247019a Si4684 boot fix: flash encryption off, 16MB flash, NVS enc off, main task stack 8KB, CTS timeout 10s (fixes BootCmd error 6), DMA-safe HOST_LOAD buffer 2026-08-05 08:57:43 +02:00
micheleandCursor 176cdd9319 Release fw 0.8.4: doc sync, System UI, BT/FM polish.
Align README, manual, and backlog to 0.8.4; add Web UI System tab for OTA/DSP uploads, serial in health header, FM seek down, and BT1035 paired list plus auto-reconnect API.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 09:56:44 +02:00
micheleandCursor 2e44a166e1 Harden Si4684 RSTB config and document power sequencing.
Enable GPIO_PULLDOWN_ENABLE during gpio_config so RSTB cannot glitch high before gpio_set_level(0), and add manual validation notes explaining why the internal pull-down complements the external one.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 08:34:52 +02:00
micheleandCursor 0fc714e431 Add preset polish and broadcast metadata (fw 0.8.0).
Ship station reorder, DAB playing ids in presets, RDS PS/RT and DAB DLS in tuner status, Si4684 data-service read, host tests, and UI now-playing lines.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 00:01:49 +02:00
micheleandCursor 81d404a1df Add Slice 4 tuner stack with AGENTS-compliant core types and HTTP API.
Introduces Si4684/ADAU1701 drivers, TunerService, FrequencyKHz,
SeekDirection, /api/tuner routes without file-scope globals, host tests,
and firmware blob tooling (binaries remain gitignored).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:17:56 +02:00
micheleandCursor ddbee70c23 Add firmware slices 1–2: skeleton and Wi-Fi provisioning
Implement ESP-IDF walking skeleton with SoftAP, health API, and host
tests, then Slice 2 ISecureStore/NvsSecureStore, STA join, POST
/api/wifi, and the provisioning web UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 09:10:37 +02:00