e.g. 0.0.1-1d613b1: the first part is set by hand, the hash says which
commit was built. No dirty flag (the build always patches cspot).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The version is now set by hand in version.txt, so it's readable and only
changes on purpose. git describe gave commit hashes, and always "-dirty"
because the build applies patches to the cspot submodule. Editing the file
is enough; the next build picks it up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All sources (player, test signals) are 48 kHz; at 96 kHz Spotify and HLS
played at the wrong speed. Validation, the page and the docs allow 48000
only, and a 96000 stored before is reset to 48000 at boot (stored values
aren't re-validated, and the SDP would still have said 96 kHz).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page starts dark regardless of the system setting; the toggle at the top
right switches to light and is remembered in the browser. [hidden] now
always wins: label{display:block} had overridden it, so the hidden VLAN
split checkbox still showed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- 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>