With DHCP on, the greyed-out IP/netmask/gateway/DNS fields show the
addresses in use. Unticking DHCP keeps them as the starting point for a
static setup. (Static addressing itself is not applied yet.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core: aes67_tx_pink_noise(), a ready-made TX source: xorshift32 white noise
through Paul Kellet's pink filter, -18 dBFS RMS (peaks about -6 dBFS), same
on L and R. The player uses it like the tone (straight to TX, no volume or
gain). Checked on the board: -18 dBFS RMS, octave bands 63 Hz-4 kHz flat
within +-0.4 dB, L = R.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pull callback's contract said "stream channels", but the player always
delivers stereo, so a 1-channel stream went out as L,R,L,R at half speed.
Sources now always deliver AES67_TX_SRC_CHANNELS (2); TX maps to the stream:
2 ch as is, 1 ch = (L+R)/2 in 64 bit with mono_sum (can't clip), else L.
Checked: mono packets 156 B, 1000/s, 48 samples each, SDP L24/48000/1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
play/pause/toggle/stop/next/prev. Spotify commands run on the session task
(same thread as cspot's frame handling) and the POST waits until they ran,
so it answers the new state. HLS pause stops fetching and resume rejoins at
the live edge; next/prev on HLS and anything on tone/off return 409.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- player: volume 0..100 %, amplitude = (v/100)^3 (about -18 dB at 50 %,
0 = mute), applied in the TX pull callback after the ring so a change
is heard at once (the ring holds 4 s); ramped over one packet to avoid
zipper noise. Spotify VOLUME events (0..65535) set it.
- Docs: pipeline order ring -> volume/gain, curve described.
- Verified from a Mac: RMS followed the slider immediately (100 % about
-10 dBFS; steps to about -24.5 and -28 dBFS match the cubic curve for
~57 % / ~50 %); user: "sounds about right". Mute at 0 % not yet
confirmed (check via /api/player/volume later).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Core config: cfg_mark_secret(group, key). GET /api/config returns ""
for secret keys; a POST with "" keeps the stored value, null clears it;
cfg_get() returns the real value. Documented in aes67-core-base.md.
- source.spotify_client_id / spotify_client_secret (secret write-only):
each user's own Spotify developer app, needed by the maintained cspot
fork (philippe44/cspot) since Spotify's 2025 API restrictions.
- UI: client ID field, password field for the secret ("leave empty to
keep"), link to developer.spotify.com; enabled for spotify/auto modes.
- status.spotify_state: "no client credentials" while either is missing.
- Verified: secret never appears in GET; UI-style re-save keeps it;
survives reboot; null clears it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- main/audio_ring: SPSC ring of interleaved int32 frames in PSRAM
(4 s at 48 kHz stereo), lock-free with acquire/release counters; the
TX pull callback never blocks.
- main/player: source selection from source.mode and the pull callback
for aes67_tx. tone -> the core's PTP-phased 1 kHz tone; off -> silence;
hls/spotify -> ring with 1 s prefill (silence while buffering is not an
underrun; running dry is, and prefills again). Status fields
active_source, source_state, spotify_state, buffer_ms. source config
applies live.
- New source.mode "tone" (validation, UI dropdown, doc) for commissioning.
- Temporary 440 Hz producer in hls mode (until the HLS player exists).
- Verified: tone 999.7 Hz -18 dBFS; off silent; hls 440.0 Hz with max
sample step 60816 (ideal sine 60825, i.e. no discontinuities), buffer
3983 ms, 0 underruns; spotify silent/not implemented.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Planned SNTP client: servers as names or IPs, several allowed, resolved
via DNS. Seeds the PTP clock with real time before becoming GM (today it
starts at 1970) and gives syslog timestamps. Added to the phase 2 list
in CLAUDE.md and as a section in aes67-core-base.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- As TimeReceiver in ptp.mode hybrid: Delay_Req unicast to the GM (IP
from its Announce, MAC recorded by the RX hook from its Sync frames),
unicastFlag set.
- As TimeTransmitter: a Delay_Req with the unicastFlag gets a unicast
Delay_Resp; multicast requests get multicast replies.
- EMAC timestamps every received frame (en_ts4all): its PTP filter left
unicast Delay_Req unstamped ("no HW RX timestamp").
- A unicast Delay_Resp's logMessageInterval 0x7F is ignored (use
ptp.log_delay_req); log_us() clamps to -7..6 (the shift was undefined).
- Verified vs ptp4l --hybrid_e2e 1: board hybrid TimeReceiver 18
Delay_Req in 20 s, all answered, locked; board TimeTransmitter
(p1 100): tcpdump shows Sync/Follow_Up/Announce to 224.0.1.129,
Delay_Req 192.168.192.233 -> .244 [unicast] and Delay_Resp .244 ->
.233 [unicast] within 0.4 ms; ptp4l s2.
- Docs: TimeTransmitter/hybrid notes; step 6 ticked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_cfg: cfg_set_number(group, key, value, apply) for firmware-side
changes, stored in NVS like a POST; apply can be skipped.
- aes67_sdp_sap: once a second, a change of the PTP GM (ts-refclk) bumps
aes67.session_ver without re-applying the aes67 group (no stream
restart); SAP then re-announces. Runs whether or not SAP is on.
- Verified with ptp4l as a normal clock (p1 128): board auto (p1 250)
steps down to SLAVE (session_ver 1->2); role master p1 100 via API ->
board MASTER at once (->3); back to auto p1 250 -> ptp4l takes over
within ~1 s, board SLAVE again (->4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SAP, syslog and health/temperatures are done and verified. The VLAN
split needs a tagged VLAN with DHCP on the switch and is only needed
for the internet sources, so it moves to a phase 2 section. Step 4
notes what is still open (Riedel import, Wireshark).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_health: status.temps = [{name, c, min_c, max_c, warn_c}]
(0.1 °C), sampled every 2 s by a health task. On-die "SoC" sensor via
driver/temperature_sensor.h, range 20..100 °C, warn at 85 °C.
health_temp_register(name, read_cb, warn_c) for board sensors.
Warning logged when crossing warn_c, cleared below warn_c - 5 °C.
- Verified: SoC 26-28 °C at idle with min/max since boot; with a
temporary 27 °C threshold the warning was logged and arrived via
syslog as <132>, status carried warn_c 27.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_sdp_sap: SAP (RFC 2974) announcer task. SAPv1 to
239.255.255.255:9875 every 30 s and within 1 s of any SDP change
(config save, GM change), with the old hash deleted first. Active while
aes67.discovery = sap, the stream is enabled, the AES67 interface has
an IP and a PTP GM is known. Deletion when that stops and at shutdown
(reboot/OTA) via a shutdown handler. TTL = aes67.ttl.
- Docs: SAP implementation notes; session_ver bump on GM change still open.
- Verified with a SAP listener: 30 s repeats with a stable hash; rename
-> delete old + announce new (o= version follows); manual -> delete;
sap -> announce; reboot and OTA -> delete at shutdown, first announce
after boot only once the GM is known.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Web UI PTP panel checked in the browser.
- GM loss test (ptp4l stopped): SLAVE -> LISTENING after the announce
timeout, frequency held; ptp4l restarted: UNCALIBRATED -> SLAVE in 22 s.
- Docs: P4 PTP implementation notes (UDP timestamp bit, RX hook, raw
Delay_Req, servo, measured results, link asymmetry).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test on board (ESP32-P4 rev v1.3):
- Rejected with 400 and nothing booted differently: garbage, wrong
project, min chip rev v3.0, truncated image.
- A (USB, ota_0) -> B via /api/ota: self-test passed, confirmed.
- Manual rollback B -> A; A -> B again.
- B -> C (forced self-test failure): C boots on trial in ota_0, fails,
rolls back to B in ota_1.
- D installed over the network; a later truncated upload hides
'previous' and clears can_rollback.
Docs: network flash command, serial-reset caveat, rollback test build,
OTA details in aes67-core-base.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_net: esp_netif_set_hostname() before the interface starts, and
in the net apply callback (takes effect at the next lease renewal).
- Verified: the router's DNS (lan.apointless.space) resolves p4-aes67
to 192.168.192.244.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_net/lldp.c: IEEE 802.1AB LLDPDU sent as a raw frame with
esp_eth_transmit every 30 s (TTL 120 s) and immediately on a new IP
or hostname change. TLVs: chassis/port ID (MAC), port description,
system name (net.hostname), system description (project + version),
capabilities station-only, IPv4 management address.
- Docs: LLDP in the core Network section; step 2a ticked.
- Verified on an HP 2530-8G-PoE+ (show lldp info remote-device 2): all
fields shown, management address 192.168.192.244.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_net: espressif/mdns 1.13.1 (managed component, pinned in
dependencies.lock). Advertises <net.hostname>.local and the web UI as
_http._tcp port 80. net apply callback renames live.
- First config value actually applied: net.hostname.
- Docs: mDNS in the core Network section; build order gets step 2a
(mDNS, then LLDP).
- Verified on board: p4-aes67.local resolves via raw mDNS and getent,
http://p4-aes67.local/ answers, service shows in _http._tcp browse;
live rename to p4-rename and back works (after ~1 s probing).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Board checked with esptool: ESP32-P4 rev v1.3 (engineering silicon),
32 MB GigaDevice flash. Rev <3 support and 32 MB set in sdkconfig.defaults.
- OTA partition layout: nvs, otadata, phy_init, ota_0/ota_1 (6 MB each,
below 16 MB), coredump; no factory app. Partition table at 0x10000 so
the bootloader can grow.
- App rollback enabled in the bootloader from the start.
- PROJECT_VER from git describe --tags --always --dirty.
- Empty stubs for the nine aes67_* core components; main logs version
and chip revision.
- Built with ESP-IDF v5.5.5; not yet flashed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>