8 Commits

Author SHA1 Message Date
55f7f22a01 spike: BLE+A2DP coexistence test on Board A (throwaway)
Proves dual-mode BLE+A2DP fit in RAM (~110KB free) but BLE radio activity
shreds the A2DP audio (clicks, no music) even when only advertising. Conclusion:
move BLE control to a dedicated ESP32-S3 hub; keep Board A a pure sink.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:15:37 -04:00
0b1c34074f Fix robotic audio + crash: proper FIFO delay line
The previous delay buffer re-derived the read position from the write head on
every A2DP pull (read = write - delay), so it sampled the buffer at the
Bluetooth pull-rate while positioning by the I2S write-rate — skipping/
repeating samples (robotic) and, with no cushion at delay=0, constantly
under/overrunning until it destabilized and crashed.

Replace with a real FIFO: a sequential read pointer that advances by the
frames consumed, held BASE_DELAY_MS (40 ms jitter cushion) + the touch trim
behind the I2S write head, with underrun (pad silence) and overrun (resync)
handling. Also carry partial frames across I2S reads so L/R never slips.

Verified on hardware: clean audio to both speakers, stable for hours,
touch-pad sync aligns JBL and Cardo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:07:49 -04:00
66f56f1e09 docs: document touch-pad sync (GPIO4/27), no WiFi UI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:18:05 -04:00
2a34ed5abe Sync via capacitive touch (drop Wi-Fi) — fixes BT starvation
The Wi-Fi/web/ESP-NOW sync approach was unviable: Wi-Fi + Bluetooth-A2DP +
the delay buffer exhausted RAM on the classic ESP32 (~20 KB free), and the
Bluetooth stack was so starved the source boards couldn't even connect to a
speaker. Confirmed on hardware: with Wi-Fi up the JBL would not connect; with
Wi-Fi removed it connects instantly (~65 KB free).

- board_source.cpp: remove Wi-Fi/ESP-NOW. Keep the I2S-reader-task + ring
  delay line (now 200 ms; plenty of RAM without Wi-Fi). Adjust delay live by
  ear via two capacitive-touch pads — "+" on GPIO4 (T0), "-" on GPIO27 (T7);
  tap = 5 ms step, hold = ramp. Persisted to flash (debounced).
- board_sink.cpp: reverted to the simple A2DP sink + I2S master (no Wi-Fi).
- platformio.ini: drop SPEAKER_ID. Remove relay_config.h.

All three boards connect reliably with healthy heap. Touch pins read ~120
untouched; threshold 40.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:07:43 -04:00
0b474b172b Add per-speaker delay sync with Wi-Fi/web UI + ESP-NOW
Adds runtime speaker-sync control to the relay:

- src/relay_config.h: shared Wi-Fi AP creds + ESP-NOW RelayDelayMsg
  (delay_ms[JBL], delay_ms[Cardo]) on a fixed channel.
- board_sink.cpp (Board A): hosts a Wi-Fi AP "BikeAudio-Setup" + a small
  web UI (two sliders, no app) at 192.168.4.1, and broadcasts the chosen
  per-speaker delays to the source boards over ESP-NOW. Still A2DP sink +
  I2S master. (Tune while parked — Wi-Fi/BT coexist on one radio.)
- board_source.cpp (Boards B/C): inserts an adjustable delay line between
  I2S in and A2DP out — a dedicated reader task fills a ring buffer (up to
  ~250 ms) and the A2DP callback reads delay_frames behind the write head.
  Delay arrives via ESP-NOW (per SPEAKER_ID) and is persisted to flash
  (Preferences), so it survives power cycles.
- platformio.ini: source envs get -DSPEAKER_ID (0=JBL, 1=Cardo).

Lets the rider trim JBL vs Cardo timing to sync the two speakers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:11:41 -04:00
baa3ef7690 3-board relay firmware: sink + I2S + dual source
Implements the only architecture that can relay iPhone audio to two BT
speakers at once (one ESP32 cannot be A2DP sink+source, and a source holds
only one link):

  iPhone ))BT)) [Board A: A2DP sink -> I2S master]
                      ==I2S bus==> [Board B: I2S slave -> A2DP source] ))BT)) JBL
                      ==I2S bus==> [Board C: I2S slave -> A2DP source] ))BT)) Cardo

- src/board_sink.cpp   : A2DP sink "BikeAudio", forwards decoded PCM to an
  I2S master bus (BCK=5, WS=25, DATA=23); follows negotiated sample rate.
- src/board_source.cpp : I2S slave (BCK=19, WS=18, DATA=22) -> A2DP source,
  target speaker via TARGET_SPEAKER build flag; pads silence on underrun.
- platformio.ini       : 3 envs (sink, source_jbl, source_cardo) sharing an
  [env] base; sources differ only by TARGET_SPEAKER. build_src_filter selects
  the per-board source file. Libs pinned as before.
- README_RELAY.md      : wiring table, I2S bus topology, flash order, pairing,
  and the speaker-sync limitation.

Replaces the single-board src/main.cpp (architecturally impossible). All
three envs build clean. Hardware flash + wiring next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:35:12 -04:00
04e7f20430 Convert Arduino sketch to PlatformIO project
Restructure BikeAudio for PlatformIO while preserving the original
build specs from the .ino header:
  - platform espressif32 @ ~6.6.0  -> ESP32 Arduino core 2.0.17
  - board esp32dev (ESP32 DevKitC v4)
  - board_build.partitions = huge_app.csv (Huge APP, required for BT stack)
  - monitor_speed = 115200

Changes:
  - BikeAudio.ino -> src/main.cpp (+ #include <Arduino.h>, print_status()
    forward declaration; PlatformIO compiles .cpp directly and does not
    auto-generate prototypes like the Arduino IDE).
  - Add platformio.ini with pschatzmann ESP32-A2DP + arduino-audio-tools
    pinned to exact commits (ESP32-A2DP 1.8.11, audio-tools 1.2.4) for
    reproducible builds.
  - Adapt three spots to the current library API (behavior preserved):
      * RingBuffer<uint8_t>::read() -> bool read(T&)
      * connection-state callbacks: drop esp_bd_addr_t arg
        -> (esp_a2d_connection_state_t, void*)
      * BluetoothA2DPSource::start(name, bool) -> start(name)
        (auto-reconnect already configured via set_auto_reconnect(true))

Verified: pio run -e esp32dev succeeds.
RAM 14.5% (47.6 KB), Flash 42.0% (1.32 MB / 3 MB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:44:09 -04:00
Sebastien Vayrette
276489cb17 feat: initial BikeAudio firmware
ESP32 DevKitC v4 Bluetooth audio relay.
iPhone -> ESP32 (A2DP sink) -> JBL Charge 5 + Tangerine EDGE (dual A2DP source).
Dual mirrored ring buffers for in-sync output, auto-reconnect on boot.
2026-06-09 12:51:08 -04:00