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>
- 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>
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>