0aac7bc339
- spotify_init() sets up the logger, zeroconf routes and session task once; spotify_apply() (at boot and on every source save) enables or disables: enabled = mode spotify/auto + credentials. Disable removes the mDNS entry, zeroconf answers 404 and a running session ends (loop exits within 200 ms, SpircHandler::disconnect stops the queue and player tasks). A new device name ends the session and re-advertises. The login blob is swapped under a mutex. - Verified: hls/spotify/rename switch the mDNS entry and /spotify_info (404/200) live; switching to hls during Spotify playback ended the session in < 2 s, HLS played after ~5 s, internal heap 221 -> 365 KB (cspot frees everything). - CLAUDE.md: OTA-with-session findings (intermittent, flash-write stalls seen as TX resyncs during uploads), serial-port reset caveat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
8.4 KiB
Markdown
80 lines
8.4 KiB
Markdown
# aes67-ESP32-P4
|
|
|
|
AES67 sender on a Waveshare ESP32-P4-ETH (PoE). Sources: Spotify Connect (cspot) and HLS/m3u8, with failover. Riedel-style web UI and a reusable AES67 core (PTP, RTP TX, SDP/SAP, VLAN, syslog, health) meant for later AES67 projects.
|
|
|
|
Repo: https://gitea.apointless.space/bsncubed/aes67-ESP32-P4
|
|
|
|
## Read first
|
|
- `docs/aes67-core-base.md`: reusable core. Config schema, API, PTP (roles, Riedel defaults, status fields), AES67 TX/SDP/SAP, VLAN, syslog, temperatures, component layout.
|
|
- `docs/hardware-and-design-notes.md`: board pinout, chip revision caveat, project pipeline (cspot/HLS), failover, player API.
|
|
- `web/index.html`: finished web UI (single file). It is the API contract; firmware must match the JSON it reads and writes.
|
|
|
|
## Working rules
|
|
- **One change at a time** ("one fuckup at a time"). Change or test one variable per step; never stack several fixes or hypotheses. If something breaks, go back to the last known-good state.
|
|
- Follow the build order below. Finish and verify a step on hardware before starting the next.
|
|
- After every change: `idf.py build`, flash, and check the serial monitor output. Show the relevant log lines.
|
|
- Don't mark a step done until it has been verified on the board.
|
|
- Keep core and project separate: `components/aes67_*` must not depend on `main/` (sources/player). Project code plugs in through the config, status and route registries.
|
|
- `web/index.html`: keep the CORE / PROJECT markers. If the firmware needs an API change, change the doc and the page together.
|
|
- Small, focused commits per step.
|
|
|
|
## Toolchain
|
|
- ESP-IDF 5.5 or later (needed for the P4 and for `examples/network/vlan_support`). Target `esp32p4`.
|
|
- `idf.py set-target esp32p4`, `idf.py build`, `idf.py -p <PORT> flash monitor`
|
|
- Before first build: run `esptool.py chip_id` and `esptool.py flash_id`.
|
|
- Chip revision < v3.0 (engineering silicon, seen on some of these boards) needs `CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y`.
|
|
Our board: **v1.3** (set in sdkconfig.defaults, min rev v1.0).
|
|
- Our board: **32 MB** flash (GigaDevice c8/4019). App slots must stay below 16 MB (cache mapping above 16 MB is experimental in IDF).
|
|
- Rev < 3 also limits Espressif's prebuilt audio libraries: `esp_audio_codec` must stay < 2.6 and `esp_audio_effects` < 1.4 (newer versions use P4 assembly that needs rev >= 3; the build fails with a message saying so). Check this for any new Espressif binary component.
|
|
- Embed `web/index.html` via `EMBED_TXTFILES` in `aes67_web`.
|
|
- cspot (Spotify, `external/cspot` git submodule, philippe44 fork, pinned): after cloning run `git submodule update --init external/cspot && git -C external/cspot submodule update --init cspot/bell`. Its nanopb code generator needs, in the IDF Python env: `python -m pip install protobuf grpcio-tools 'setuptools<81'` (after `. export.sh`). Our fixes to cspot/bell live in `components/spotify/patches/{cspot,bell}/*.patch` and are applied automatically at configure time; don't edit the submodule directly, add a patch.
|
|
- Flash over the network (normal way since step 2b; keep USB for recovery):
|
|
`curl -f --data-binary @build/aes67_p4.bin -H 'Content-Type: application/octet-stream' http://p4-aes67/api/ota`
|
|
The board reboots into the new image on trial; check `GET /api/ota` shows the new version with `pending_verify: false`.
|
|
- Serial: opening /dev/ttyACM0 can reset the board. Don't open it while an OTA image is on trial (a reset then counts as a failed boot and rolls back).
|
|
- Rollback test build (self-test always fails), in its own build dir:
|
|
`idf.py -B build-selftest-fail -DSDKCONFIG=build-selftest-fail/sdkconfig -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.selftest_fail" build`
|
|
|
|
## Board quick reference (full details in docs)
|
|
- Ethernet: IP101GRI, RMII, PHY addr 1, ref clock from PHY into GPIO50 (EMAC_CLK_EXT_IN).
|
|
MDC 31, MDIO 52, PHY reset/power 51, TX_EN 49, TXD0 34, TXD1 35, CRS_DV 28, RXD0 29, RXD1 30.
|
|
- No Wi-Fi/BT on this board.
|
|
- On-die temperature sensor only (driver/temperature_sensor.h).
|
|
|
|
## Build order / status
|
|
- [x] 0. Check chip revision and flash size; create IDF project and empty component stubs (layout in aes67-core-base.md). Set up the OTA partition table (two app slots, no factory) and PROJECT_VER from git now, so the layout never changes later.
|
|
- [x] 1. Ethernet: IP101 up, DHCP, IP logged. Ping works.
|
|
- [x] 2. Web server + config store (cJSON in NVS) + embedded index.html; /api/config, /api/status (stub values), /api/reboot.
|
|
- [x] 2a. Finding the device: mDNS (hostname.local + _http._tcp), then LLDP (switch shows name + IP).
|
|
- [x] 2b. Firmware update: /api/ota upload + rollback self-test. Test: update to a new build, then deliberately flash a build that fails its self-test and confirm it rolls back. After this, flash over the network; keep USB for recovery.
|
|
- [x] 3. PTP TimeReceiver: lock to an existing GM (Riedel), fill `status.ptp`. Confirm EMAC hardware timestamps work. First check whether the installed ESP-IDF has a PTP example/component for the P4 before writing one.
|
|
- [ ] 4. AES67 TX with a 1 kHz test tone, PTP-paced; /stream.sdp. Verify: import SDP on a Riedel Artist 4-wire AES67 port, and check packets/timestamps in Wireshark.
|
|
Done and checked with a receiver script (all ptimes, L16/L24, tone phase-locked to PTP); still open: the Riedel import and a Wireshark capture.
|
|
- [x] 5. SAP discovery, then syslog, then health/temperatures (one at a time). VLAN split moved to phase 2.
|
|
- [x] 6. PTP TimeTransmitter: BMCA roles (auto/master), hybrid mode.
|
|
- [ ] 7. Sources: HLS player, then cspot (Spotify Connect), then failover + /api/player.
|
|
- [x] 7.1-7.4 HLS plays on AES67 (PSRAM ring, TS demux + AAC, 44.1 -> 48 kHz). Tested with Triple J Hottest (TS, AAC-LC 44.1k).
|
|
- [ ] Come back to HLS (open items):
|
|
- Clock drift: the station's encoder clock vs our PTP clock is not corrected. Starts 3 segments (~30 s) behind live, so it shows after hours/days (skip when falling out of the live window, or buffering). Fix: steer the 44.1 -> 48 kHz ratio by a few ppm from the distance to the live edge / ring level.
|
|
- Download speed ~1.4 Mbit/s over TLS (fine for ~250 kbit/s; tune buffer sizes / per-chunk overhead).
|
|
- Not yet tested: HE-AAC variant (140k), other stations, fMP4/ADTS-only playlists, discontinuities (#EXT-X-DISCONTINUITY), network loss and recovery, long runs.
|
|
- Audio starts only after PTP lock (~20 s after boot): intended, TX needs PTP.
|
|
- [ ] cspot (Spotify Connect): login, audio, pause/skip/seek and app volume work (7.5b). Open:
|
|
- OTA during an active Spotify session made the upload crawl (~10 kB/s) and ended in a reset. With Spotify paused for the upload (aes67_ota_on_update) 2 of 3 later tries worked, 1 still failed: intermittent, root cause not found. Serial log of a good upload: flash writes stall the TX task > 20 ms about 20x/s (509 'aes67_tx: resync' in 26 s, stream stutters during OTA; none during normal playback). Likely the same stalls hit cspot's network/TLS tasks. Ideas: end the Spotify session (not just pause) for OTA; rate-limit the resync warning.
|
|
- Opening /dev/ttyACM0 resets the board even with DTR/RTS held low: start serial captures before setting up a test.
|
|
- First connects sometimes fail ("Can't connect to spotify servers"), a retry works.
|
|
- Internal heap drops from ~408 KB to ~232 KB with a session; check what can move to PSRAM.
|
|
- Mute at 0 % volume not yet confirmed.
|
|
- [ ] failover (auto mode) + /api/player
|
|
- [ ] 8. Mono sum, gain, polish.
|
|
|
|
## Phase 2 (parked)
|
|
- [ ] VLAN split: AES67 untagged + internet tagged (inet.vlan_id/pcp), per aes67-core-base.md "Network". Needs a tagged VLAN with DHCP on the switch port. Config group `inet` and the UI fields exist already; nothing is applied yet.
|
|
- [ ] NTP (SNTP): servers as hostnames or IPs (e.g. `pool.ntp.org`, several allowed; names resolved via DNS, re-resolved on failure). Uses: seed the PTP clock with real time before becoming GM (today it starts at 1970), syslog timestamps. Needs a `time` config group + UI fields (change doc and page together).
|
|
|
|
## Phase 3 (parked)
|
|
- [ ] Remote access via VPN / Tailscale. There is no official Tailscale client for ESP32; options to evaluate first:
|
|
(a) no firmware change: a Tailscale subnet router on the LAN (e.g. the dev PC or a Pi) advertising the device's subnet;
|
|
(b) WireGuard on the device (e.g. the `esp_wireguard` component) to a WireGuard server or a Tailscale/Headscale-compatible peer.
|
|
Before exposing the web/API remotely: add authentication (bearer token) as noted in the OTA/security section of aes67-core-base.md. Keep AES67/PTP traffic off the tunnel.
|