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>
This commit is contained in:
parent
0b474b172b
commit
2a34ed5abe
@ -31,9 +31,9 @@ build_src_filter = +<board_sink.cpp>
|
||||
; --- Board B: I2S slave -> A2DP source -> JBL Charge 5 ------------------------
|
||||
[env:source_jbl]
|
||||
build_src_filter = +<board_source.cpp>
|
||||
build_flags = '-DTARGET_SPEAKER="JBL Charge 5"' -DSPEAKER_ID=0
|
||||
build_flags = '-DTARGET_SPEAKER="JBL Charge 5"'
|
||||
|
||||
; --- Board C: I2S slave -> A2DP source -> Cardo (Tangerine EDGE) --------------
|
||||
[env:source_cardo]
|
||||
build_src_filter = +<board_source.cpp>
|
||||
build_flags = '-DTARGET_SPEAKER="Tangerine EDGE"' -DSPEAKER_ID=1
|
||||
build_flags = '-DTARGET_SPEAKER="Tangerine EDGE"'
|
||||
|
||||
@ -1,30 +1,28 @@
|
||||
/**
|
||||
* BikeAudio — Board A : A2DP SINK -> I2S MASTER + setup UI
|
||||
* BikeAudio — Board A : A2DP SINK -> I2S MASTER
|
||||
*
|
||||
* Receives audio from the iPhone (A2DP "BikeAudio"), clocks decoded PCM onto the
|
||||
* shared I2S bus as master, AND hosts a Wi-Fi access point with a browser-based
|
||||
* setup UI to adjust each speaker's delay (for syncing JBL vs Cardo). Delay
|
||||
* values are pushed to the source boards over ESP-NOW.
|
||||
* Part of the 3-board relay. The iPhone connects to this board over Bluetooth
|
||||
* (A2DP name "BikeAudio"). This board decodes the audio to PCM and clocks it
|
||||
* out on a shared I2S bus as the MASTER. Boards B and C (A2DP sources) listen
|
||||
* to this same bus as slaves and stream it to the JBL / Cardo speakers.
|
||||
*
|
||||
* I2S OUTPUT (drives the bus): BCK=GPIO5 WS=GPIO25 DATA=GPIO23 + GND
|
||||
* Setup UI: join Wi-Fi "BikeAudio-Setup" (pass "bikeaudio"), open http://192.168.4.1
|
||||
* iPhone ))BT)) [Board A: sink] ==I2S==> [Board B: src] ))BT)) JBL
|
||||
* \====> [Board C: src] ))BT)) Cardo
|
||||
*
|
||||
* NOTE: Wi-Fi + Bluetooth share the radio — tune the delays while parked; the
|
||||
* source boards persist the values, so Wi-Fi need not be used while riding.
|
||||
* Why a separate board: one ESP32 cannot be an A2DP sink and source at once
|
||||
* (single Bluedroid A2DP role), and an A2DP source can hold only one outgoing
|
||||
* link — so the sink and each speaker need their own chip. See README.
|
||||
*
|
||||
* Build: pio run -e sink
|
||||
* I2S OUTPUT pins (this board DRIVES the bus — wire these to B and C):
|
||||
* BCK = GPIO5 WS/LRCK = GPIO25 DATA(out) = GPIO23 + common GND
|
||||
*
|
||||
* Build: pio run -e sink (compiled via build_src_filter in platformio.ini)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "AudioTools.h"
|
||||
#include "BluetoothA2DPSink.h"
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_wifi.h>
|
||||
|
||||
#include "relay_config.h"
|
||||
|
||||
#define I2S_BCK_PIN 5
|
||||
#define I2S_WS_PIN 25
|
||||
@ -32,130 +30,67 @@
|
||||
|
||||
I2SStream i2s;
|
||||
BluetoothA2DPSink sink;
|
||||
WebServer server(80);
|
||||
|
||||
static uint16_t current_sample_rate = 0;
|
||||
static uint8_t bcast_addr[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
||||
static RelayDelayMsg delays = { RELAY_MAGIC, {0, 0} };
|
||||
static uint16_t current_sample_rate = 0;
|
||||
|
||||
// Configure / reconfigure the I2S bus as master TX at the given rate.
|
||||
static void start_i2s(uint16_t rate) {
|
||||
if (rate == 0) rate = 44100;
|
||||
if (rate == 0) rate = 44100; // SBC default before negotiation
|
||||
auto cfg = i2s.defaultConfig(TX_MODE);
|
||||
cfg.pin_bck = I2S_BCK_PIN; cfg.pin_ws = I2S_WS_PIN; cfg.pin_data = I2S_DATA_PIN;
|
||||
cfg.sample_rate = rate; cfg.channels = 2; cfg.bits_per_sample = 16;
|
||||
cfg.is_master = true; cfg.buffer_count = 8; cfg.buffer_size = 512;
|
||||
cfg.pin_bck = I2S_BCK_PIN;
|
||||
cfg.pin_ws = I2S_WS_PIN;
|
||||
cfg.pin_data = I2S_DATA_PIN;
|
||||
cfg.sample_rate = rate;
|
||||
cfg.channels = 2;
|
||||
cfg.bits_per_sample = 16;
|
||||
cfg.is_master = true; // Board A clocks the whole bus
|
||||
cfg.buffer_count = 8;
|
||||
cfg.buffer_size = 512;
|
||||
i2s.begin(cfg);
|
||||
current_sample_rate = rate;
|
||||
Serial.printf("[SINK] I2S master @ %u Hz / 16-bit / stereo\n", rate);
|
||||
}
|
||||
|
||||
void write_pcm_to_i2s(const uint8_t *data, uint32_t len) { i2s.write(data, len); }
|
||||
// Called from the BT task with decoded PCM. Keep it cheap — just push to I2S.
|
||||
void write_pcm_to_i2s(const uint8_t *data, uint32_t len) {
|
||||
i2s.write(data, len);
|
||||
}
|
||||
|
||||
void on_conn_state(esp_a2d_connection_state_t state, void *obj) {
|
||||
if (state == ESP_A2D_CONNECTION_STATE_CONNECTED) Serial.println("[SINK] iPhone CONNECTED");
|
||||
else if (state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) Serial.println("[SINK] iPhone disconnected");
|
||||
}
|
||||
|
||||
static void send_delays() {
|
||||
esp_now_send(bcast_addr, (uint8_t *)&delays, sizeof(delays));
|
||||
}
|
||||
|
||||
static const char PAGE[] PROGMEM = R"HTML(<!doctype html><html><head>
|
||||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<title>BikeAudio Sync</title><style>
|
||||
body{font-family:sans-serif;background:#111;color:#eee;margin:0;padding:18px}
|
||||
h2{margin:6px 0 18px}.row{margin:22px 0}label{display:block;font-size:18px;margin-bottom:6px}
|
||||
input[type=range]{width:100%}.v{font-weight:bold;color:#4ad}.hint{color:#888;font-size:13px;margin-top:24px}
|
||||
</style></head><body><h2>BikeAudio — speaker sync</h2>
|
||||
<div class=row><label>JBL delay: <span class=v id=jv>0</span> ms</label>
|
||||
<input type=range min=0 max=250 value=0 id=js oninput="set(0,this.value)"></div>
|
||||
<div class=row><label>Cardo delay: <span class=v id=cv>0</span> ms</label>
|
||||
<input type=range min=0 max=250 value=0 id=cs oninput="set(1,this.value)"></div>
|
||||
<div class=hint>Raise the delay on whichever speaker is <i>early</i> until they line up.
|
||||
Values are saved on each speaker.</div>
|
||||
<script>
|
||||
function set(i,v){document.getElementById(i?'cv':'jv').innerText=v;
|
||||
fetch('/set?i='+i+'&v='+v)}
|
||||
fetch('/get').then(r=>r.json()).then(d=>{js.value=d.jbl;jv.innerText=d.jbl;
|
||||
cs.value=d.cardo;cv.innerText=d.cardo})
|
||||
</script></body></html>)HTML";
|
||||
|
||||
static void handleRoot() { server.send_P(200, "text/html", PAGE); }
|
||||
|
||||
static void handleSet() {
|
||||
if (server.hasArg("i") && server.hasArg("v")) {
|
||||
int i = server.arg("i").toInt();
|
||||
int v = server.arg("v").toInt();
|
||||
if (v < 0) v = 0;
|
||||
if (v > RELAY_MAX_DELAY_MS) v = RELAY_MAX_DELAY_MS;
|
||||
if (i == 0 || i == 1) {
|
||||
delays.delay_ms[i] = (uint16_t)v;
|
||||
send_delays();
|
||||
Serial.printf("[SINK] set %s delay = %d ms\n", i ? "Cardo" : "JBL", v);
|
||||
}
|
||||
}
|
||||
server.send(200, "text/plain", "ok");
|
||||
}
|
||||
|
||||
static void handleGet() {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "{\"jbl\":%u,\"cardo\":%u}",
|
||||
delays.delay_ms[SPEAKER_JBL], delays.delay_ms[SPEAKER_CARDO]);
|
||||
server.send(200, "application/json", buf);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
Serial.println("=== BikeAudio Board A — SINK -> I2S master + setup UI ===");
|
||||
Serial.println("=== BikeAudio Board A — A2DP SINK -> I2S master ===");
|
||||
|
||||
start_i2s(44100);
|
||||
|
||||
// false => the sink does NOT run its own I2S; we forward PCM ourselves.
|
||||
sink.set_stream_reader(write_pcm_to_i2s, false);
|
||||
sink.set_on_connection_state_changed(on_conn_state);
|
||||
sink.set_auto_reconnect(true);
|
||||
sink.start("BikeAudio");
|
||||
|
||||
// Wi-Fi AP (setup UI) + ESP-NOW (push delays to source boards), shared channel.
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(RELAY_AP_SSID, RELAY_AP_PASS, RELAY_WIFI_CHANNEL);
|
||||
esp_wifi_set_channel(RELAY_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);
|
||||
if (esp_now_init() == ESP_OK) {
|
||||
esp_now_peer_info_t peer = {};
|
||||
memcpy(peer.peer_addr, bcast_addr, 6);
|
||||
peer.channel = RELAY_WIFI_CHANNEL;
|
||||
peer.encrypt = false;
|
||||
esp_now_add_peer(&peer);
|
||||
Serial.println("[SINK] ESP-NOW ready (broadcasting delays)");
|
||||
} else {
|
||||
Serial.println("[SINK] ESP-NOW init FAILED");
|
||||
}
|
||||
|
||||
server.on("/", handleRoot);
|
||||
server.on("/set", handleSet);
|
||||
server.on("/get", handleGet);
|
||||
server.begin();
|
||||
Serial.printf("[SINK] Setup UI: join Wi-Fi '%s' -> http://192.168.4.1\n", RELAY_AP_SSID);
|
||||
Serial.println("[SINK] Advertising 'BikeAudio' — connect from iPhone");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
server.handleClient();
|
||||
|
||||
// Follow the negotiated sample rate (iPhone usually 44100; reconfigure if not).
|
||||
uint16_t sr = sink.sample_rate();
|
||||
if (sr != 0 && sr != current_sample_rate) {
|
||||
Serial.printf("[SINK] sample rate %u -> %u\n", current_sample_rate, sr);
|
||||
Serial.printf("[SINK] sample rate changed %u -> %u, reconfiguring I2S\n",
|
||||
current_sample_rate, sr);
|
||||
start_i2s(sr);
|
||||
}
|
||||
|
||||
static unsigned long lastb = 0;
|
||||
if (millis() - lastb > 2000) { send_delays(); lastb = millis(); } // keep source boards in sync
|
||||
|
||||
static unsigned long lasts = 0;
|
||||
if (millis() - lasts > 5000) {
|
||||
Serial.printf("[SINK] iPhone=%s heap=%u JBL=%ums Cardo=%ums\n",
|
||||
sink.is_connected() ? "YES" : "no", ESP.getFreeHeap(),
|
||||
delays.delay_ms[SPEAKER_JBL], delays.delay_ms[SPEAKER_CARDO]);
|
||||
lasts = millis();
|
||||
static unsigned long last = 0;
|
||||
if (millis() - last > 5000) {
|
||||
Serial.printf("[SINK] iPhone=%s heap=%u\n",
|
||||
sink.is_connected() ? "YES" : "no", ESP.getFreeHeap());
|
||||
last = millis();
|
||||
}
|
||||
delay(2);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
@ -1,54 +1,57 @@
|
||||
/**
|
||||
* BikeAudio — Boards B & C : I2S SLAVE -> [delay buffer] -> A2DP SOURCE
|
||||
* BikeAudio — Boards B & C : I2S SLAVE -> [touch-adjustable delay] -> A2DP SOURCE
|
||||
*
|
||||
* Reads PCM from the shared I2S bus (clocked by Board A), passes it through an
|
||||
* adjustable delay line, and streams it to ONE Bluetooth speaker. The delay lets
|
||||
* you sync this speaker against the other one; Board A's web UI pushes the value
|
||||
* over ESP-NOW and it is saved to flash (survives power cycles).
|
||||
* you sync this speaker against the other one, tuned live BY EAR with two
|
||||
* capacitive-touch pads (+ / -). The value is saved to flash (survives reboots).
|
||||
*
|
||||
* Per-env build flags (platformio.ini):
|
||||
* source_jbl -> TARGET_SPEAKER="JBL Charge 5" SPEAKER_ID=0
|
||||
* source_cardo -> TARGET_SPEAKER="Tangerine EDGE" SPEAKER_ID=1
|
||||
* No Wi-Fi: Wi-Fi + Bluetooth-A2DP + the audio buffer don't fit in RAM on this
|
||||
* chip (the BT stack gets starved and won't connect), so the control is local
|
||||
* touch rather than a phone UI. With Wi-Fi gone there's ~120 KB of heap free.
|
||||
*
|
||||
* I2S INPUT (listens to Board A): BCK=GPIO19 WS=GPIO18 DATA=GPIO22 + GND
|
||||
* Per-env build flag: TARGET_SPEAKER ("JBL Charge 5" / "Tangerine EDGE").
|
||||
*
|
||||
* Wiring:
|
||||
* I2S in (from Board A): BCK=GPIO19 WS=GPIO18 DATA=GPIO22 + GND
|
||||
* Touch "+" : GPIO4 (T0) Touch "-" : GPIO27 (T7)
|
||||
* (attach a short wire or a bit of foil to each; tap = one step, hold = ramp)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "AudioTools.h"
|
||||
#include "BluetoothA2DPSource.h"
|
||||
#include <WiFi.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
#include "relay_config.h"
|
||||
|
||||
#ifndef SPEAKER_ID
|
||||
#define SPEAKER_ID 0
|
||||
#endif
|
||||
#ifndef TARGET_SPEAKER
|
||||
#define TARGET_SPEAKER "BikeAudio-Speaker"
|
||||
#endif
|
||||
|
||||
#define I2S_BCK_PIN 19
|
||||
#define I2S_WS_PIN 18
|
||||
#define I2S_DATA_PIN 22
|
||||
#define I2S_BCK_PIN 19
|
||||
#define I2S_WS_PIN 18
|
||||
#define I2S_DATA_PIN 22
|
||||
|
||||
#define SR_HZ 44100
|
||||
// Ring buffer holds max delay + headroom, in frames (1 frame = L+R int16 = 4 bytes).
|
||||
#define RING_FRAMES (((uint32_t)SR_HZ * (RELAY_MAX_DELAY_MS + 50)) / 1000)
|
||||
#define TOUCH_PLUS T0 // GPIO4
|
||||
#define TOUCH_MINUS T7 // GPIO27
|
||||
#define TOUCH_THRESH 40 // touchRead below this = touched (calibrate via serial)
|
||||
|
||||
#define SR_HZ 44100
|
||||
#define MAX_DELAY_MS 200
|
||||
#define DELAY_STEP_MS 5
|
||||
#define TOUCH_REPEAT_MS 150 // tap = one step; hold = a step every 150 ms
|
||||
#define RING_FRAMES (((uint32_t)SR_HZ * (MAX_DELAY_MS + 20)) / 1000)
|
||||
|
||||
I2SStream i2s;
|
||||
BluetoothA2DPSource source;
|
||||
Preferences prefs;
|
||||
|
||||
// Delay ring buffer (interleaved L,R int16). Written by i2s_task, read by the A2DP callback.
|
||||
static int16_t ring[RING_FRAMES * 2];
|
||||
static volatile uint32_t write_frames = 0; // monotonic frame counter (producer)
|
||||
static volatile uint32_t delay_frames = 0; // current delay, in frames
|
||||
static volatile uint16_t delay_ms_current = 0; // for logging / persistence
|
||||
static volatile bool save_pending = false;
|
||||
static volatile uint32_t write_frames = 0;
|
||||
static volatile uint32_t delay_frames = 0;
|
||||
static volatile uint16_t delay_ms_current = 0;
|
||||
static bool save_pending = false;
|
||||
static unsigned long last_change_ms = 0;
|
||||
|
||||
// Continuously pull I2S into the ring (paced by Board A's clock).
|
||||
static void i2s_task(void *arg) {
|
||||
@ -71,7 +74,7 @@ int32_t read_delayed(Frame *data, int32_t frame_count) {
|
||||
uint32_t d = delay_frames;
|
||||
if (d < (uint32_t)frame_count) d = frame_count; // never read past the write head
|
||||
uint32_t w = write_frames;
|
||||
if (w < d) { // buffer not filled yet -> silence
|
||||
if (w < d) {
|
||||
for (int32_t i = 0; i < frame_count; i++) { data[i].channel1 = 0; data[i].channel2 = 0; }
|
||||
return frame_count;
|
||||
}
|
||||
@ -84,21 +87,15 @@ int32_t read_delayed(Frame *data, int32_t frame_count) {
|
||||
return frame_count;
|
||||
}
|
||||
|
||||
static void apply_delay(uint16_t ms) {
|
||||
if (ms > RELAY_MAX_DELAY_MS) ms = RELAY_MAX_DELAY_MS;
|
||||
if (ms == delay_ms_current) return;
|
||||
delay_ms_current = ms;
|
||||
static void set_delay(int ms) {
|
||||
if (ms < 0) ms = 0;
|
||||
if (ms > MAX_DELAY_MS) ms = MAX_DELAY_MS;
|
||||
if ((uint16_t)ms == delay_ms_current) return;
|
||||
delay_ms_current = (uint16_t)ms;
|
||||
delay_frames = ((uint32_t)ms * SR_HZ) / 1000;
|
||||
save_pending = true;
|
||||
Serial.printf("[SRC %s] delay -> %u ms\n", TARGET_SPEAKER, ms);
|
||||
}
|
||||
|
||||
void on_recv(const uint8_t *mac, const uint8_t *data, int len) {
|
||||
if (len < (int)sizeof(RelayDelayMsg)) return;
|
||||
RelayDelayMsg m;
|
||||
memcpy(&m, data, sizeof(m));
|
||||
if (m.magic != RELAY_MAGIC) return;
|
||||
apply_delay(m.delay_ms[SPEAKER_ID]);
|
||||
last_change_ms = millis();
|
||||
Serial.printf("[SRC %s] delay = %d ms\n", TARGET_SPEAKER, ms);
|
||||
}
|
||||
|
||||
void on_conn_state(esp_a2d_connection_state_t state, void *obj) {
|
||||
@ -111,23 +108,12 @@ void on_conn_state(esp_a2d_connection_state_t state, void *obj) {
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
Serial.printf("=== BikeAudio Source -> '%s' (delay buffer, id %d) ===\n", TARGET_SPEAKER, SPEAKER_ID);
|
||||
Serial.printf("=== BikeAudio Source -> '%s' (touch-adjustable delay) ===\n", TARGET_SPEAKER);
|
||||
|
||||
// Restore saved delay.
|
||||
prefs.begin("bikeaudio", false);
|
||||
apply_delay(prefs.getUShort("delay_ms", 0));
|
||||
set_delay(prefs.getUShort("delay_ms", 0));
|
||||
save_pending = false; // loading isn't a change to persist
|
||||
|
||||
// Wi-Fi (STA, not connected) + ESP-NOW receive on the shared channel.
|
||||
WiFi.mode(WIFI_STA);
|
||||
esp_wifi_set_channel(RELAY_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);
|
||||
if (esp_now_init() == ESP_OK) {
|
||||
esp_now_register_recv_cb(on_recv);
|
||||
Serial.println("[SRC] ESP-NOW ready (listening for delay updates)");
|
||||
} else {
|
||||
Serial.println("[SRC] ESP-NOW init FAILED");
|
||||
}
|
||||
|
||||
// I2S slave RX — follows Board A's clock.
|
||||
auto cfg = i2s.defaultConfig(RX_MODE);
|
||||
cfg.pin_bck = I2S_BCK_PIN; cfg.pin_ws = I2S_WS_PIN; cfg.pin_data = I2S_DATA_PIN;
|
||||
@ -142,22 +128,35 @@ void setup() {
|
||||
source.set_auto_reconnect(true, 5);
|
||||
source.set_volume(100);
|
||||
source.start(TARGET_SPEAKER);
|
||||
Serial.printf("[SRC] Connecting to '%s' — I2S slave @44.1k, delay %u ms\n",
|
||||
Serial.printf("[SRC] Connecting to '%s' — delay %u ms; touch + on GPIO4, - on GPIO27\n",
|
||||
TARGET_SPEAKER, delay_ms_current);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (save_pending) {
|
||||
prefs.putUShort("delay_ms", delay_ms_current);
|
||||
save_pending = false;
|
||||
Serial.printf("[SRC %s] saved delay %u ms to flash\n", TARGET_SPEAKER, delay_ms_current);
|
||||
unsigned long now = millis();
|
||||
|
||||
// Touch +/- (tap = one step, hold = ramp every TOUCH_REPEAT_MS).
|
||||
static unsigned long last_touch = 0;
|
||||
if (now - last_touch >= TOUCH_REPEAT_MS) {
|
||||
bool plus = touchRead(TOUCH_PLUS) < TOUCH_THRESH;
|
||||
bool minus = touchRead(TOUCH_MINUS) < TOUCH_THRESH;
|
||||
if (plus && !minus) { set_delay(delay_ms_current + DELAY_STEP_MS); last_touch = now; }
|
||||
else if (minus && !plus) { set_delay(delay_ms_current - DELAY_STEP_MS); last_touch = now; }
|
||||
}
|
||||
|
||||
static unsigned long last = 0;
|
||||
if (millis() - last > 5000) {
|
||||
Serial.printf("[SRC %s] connected=%s delay=%ums heap=%u\n", TARGET_SPEAKER,
|
||||
source.is_connected() ? "YES" : "no", delay_ms_current, ESP.getFreeHeap());
|
||||
last = millis();
|
||||
// Persist to flash 1.5 s after the last change (avoids wear while ramping).
|
||||
if (save_pending && now - last_change_ms > 1500) {
|
||||
prefs.putUShort("delay_ms", delay_ms_current);
|
||||
save_pending = false;
|
||||
Serial.printf("[SRC %s] saved %u ms to flash\n", TARGET_SPEAKER, delay_ms_current);
|
||||
}
|
||||
delay(50);
|
||||
|
||||
static unsigned long last_st = 0;
|
||||
if (now - last_st > 5000) {
|
||||
Serial.printf("[SRC %s] connected=%s delay=%ums heap=%u touch+=%u touch-=%u\n",
|
||||
TARGET_SPEAKER, source.is_connected() ? "YES" : "no", delay_ms_current,
|
||||
ESP.getFreeHeap(), touchRead(TOUCH_PLUS), touchRead(TOUCH_MINUS));
|
||||
last_st = now;
|
||||
}
|
||||
delay(20);
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Shared config for the BikeAudio relay — included by Board A (sink) and the
|
||||
* source boards. Defines the Wi-Fi AP for the setup UI and the ESP-NOW message
|
||||
* Board A broadcasts to push per-speaker delay values to the source boards.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// Wi-Fi AP that Board A hosts for the browser-based setup UI.
|
||||
#define RELAY_AP_SSID "BikeAudio-Setup"
|
||||
#define RELAY_AP_PASS "bikeaudio" // WPA2 needs >= 8 chars
|
||||
#define RELAY_WIFI_CHANNEL 1 // all boards share this channel for ESP-NOW
|
||||
|
||||
// Per-speaker delay range (ms). 250 ms costs ~44 KB of ring buffer per source board.
|
||||
#define RELAY_MAX_DELAY_MS 250
|
||||
|
||||
// Speaker indices into RelayDelayMsg.delay_ms[].
|
||||
#define SPEAKER_JBL 0
|
||||
#define SPEAKER_CARDO 1
|
||||
|
||||
#define RELAY_MAGIC 0xB10E
|
||||
|
||||
// Broadcast Board A -> source boards. delay_ms[SPEAKER_JBL] / [SPEAKER_CARDO].
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t magic; // RELAY_MAGIC sentinel
|
||||
uint16_t delay_ms[2]; // [0]=JBL, [1]=Cardo
|
||||
} RelayDelayMsg;
|
||||
Loading…
x
Reference in New Issue
Block a user