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
This commit is contained in:
2026-08-16 01:24:54 +02:00
co-authored by Claude Sonnet 5
parent 6974095f35
commit 9b337e7bca
2 changed files with 50 additions and 9 deletions
@@ -931,13 +931,16 @@ std::expected<Si4684FmRdsStatus, Si4684Error> Si4684Driver::readFmRds()
return std::unexpected(rd.error()); return std::unexpected(rd.error());
} }
// raw[0]=lead-in, raw[1..4]=STATUS0-3, raw[5]=RESP4 (established
// convention, see getPartInfo()/readDabDigRadStatus() comments) — every
// offset below is RESP-number relative to that, not raw[4].
Si4684FmRdsStatus rds = {}; Si4684FmRdsStatus rds = {};
rds.received = (raw[4] & 0x01U) != 0U; rds.received = (raw[5] & 0x01U) != 0U;
rds.fifoUsed = raw[10]; rds.fifoUsed = raw[11];
rds.blockA = readLe16(raw.data() + 12U); rds.blockA = readLe16(raw.data() + 13U);
rds.blockB = readLe16(raw.data() + 14U); rds.blockB = readLe16(raw.data() + 15U);
rds.blockC = readLe16(raw.data() + 16U); rds.blockC = readLe16(raw.data() + 17U);
rds.blockD = readLe16(raw.data() + 18U); rds.blockD = readLe16(raw.data() + 19U);
return rds; return rds;
} }
@@ -1063,7 +1066,8 @@ Si4684Driver::readDabDigRadStatus()
Si4684DabDigRadStatus status = {}; Si4684DabDigRadStatus status = {};
status.ficQuality = raw[9]; status.ficQuality = raw[9];
status.cnrDb = raw[10]; status.cnrDb = raw[10];
status.acquired = (raw[4] & 0x08U) != 0U; // raw[5]=RESP4 (see readFmRds()); ACQINT is RESP4 bit3.
status.acquired = (raw[5] & 0x08U) != 0U;
status.valid = status.ficQuality > 0U; status.valid = status.ficQuality > 0U;
return status; return status;
} }
@@ -1085,9 +1089,10 @@ Si4684Driver::readDabEventStatus()
return std::unexpected(rd.error()); return std::unexpected(rd.error());
} }
// raw[5]=RESP4 (see readFmRds()); SVRLISTINT is RESP4 bit0.
Si4684DabEventStatus events = {}; Si4684DabEventStatus events = {};
events.serviceListReady = (raw[4] & 0x01U) != 0U; events.serviceListReady = (raw[5] & 0x01U) != 0U;
events.reconfig = (raw[4] & 0x02U) != 0U; events.reconfig = (raw[5] & 0x02U) != 0U;
return events; return events;
} }
@@ -565,6 +565,42 @@ BT1035 → Bluetooth speaker) in this project's history. Remaining noise on
top of the music is attributed to antenna quality, not yet independently top of the music is attributed to antenna quality, not yet independently
confirmed with a proper antenna — flagged as follow-up, not closed. confirmed with a proper antenna — flagged as follow-up, not closed.
## 2026-08-16 update: DAB lock confirmed on 3 ensembles + response-offset bug
With the tune fix in place, a full sweep of freq_index 0-35 found three real
ensemble locks (fic_quality=100 on all three): index 5 (CNR 7 dB), index 22
(CNR 15 dB), index 23 (CNR 20 dB, strongest). First confirmed DAB lock in
this project's history.
Chasing why `/api/tuner/services` returned `service_list_empty` even after
30+ seconds on a solid lock found a second bug class, this time in
**response parsing, not command construction**: `readFmRds()`,
`readDabDigRadStatus()`'s `acquired` field, and `readDabEventStatus()` all
read `raw[4]` expecting AN649's "RESP4" field, but this driver's own
established convention elsewhere (`getPartInfo()`, and the already-correct
`ficQuality`/`cnrDb` fields in `readDabDigRadStatus()`) is `raw[5]=RESP4`
(`raw[0]`=SPI lead-in, `raw[1..4]`=STATUS0-3). Fixed all four call sites to
the correct offset; `readFmRds()`'s `fifoUsed`/`blockA-D` fields were
consequently also all off by one and fixed together with it.
After the fix, `serviceListReady` now correctly gates open and
`/api/tuner/services` returns real data instead of `service_list_empty`
but the entries themselves are still garbled (implausible `service_id`
values, `component_id` fields that decode as ASCII spaces, e.g.
`538976288 = 0x20202020`, mostly-empty labels). This points to a **third,
separate bug** in `fetchDabServiceList()`'s service-list *body* parsing
(the entry structure walked in the loop over `serviceCount`), not yet
investigated — the DAB service list binary format is documented in AN649
§7 "Digital Services User's Guide" (starts around page 418), not the
command tables checked so far. Confirmed live: `POST /api/tuner/play` with
one of these garbled IDs accepted (`{"status":"playing"}`) but produced no
audio, consistent with a wrong service/component ID rather than a new
audio-path regression.
**Follow-up, not done this session**: fix `fetchDabServiceList()` entry
parsing against AN649 §7; then confirm actual DAB audio playback end to
end the same way FM was confirmed.
**Unrelated finding from the same session, logged for completeness**: BT1035 **Unrelated finding from the same session, logged for completeness**: BT1035
began failing boot deterministically (`no spontaneous UART bytes after began failing boot deterministically (`no spontaneous UART bytes after
hardware reset`, then `AT init failed`) starting from this session, on both hardware reset`, then `AT init failed`) starting from this session, on both