feat(c6): ESP-NOW cross-node sync — D1 workaround for broken 15.4 RX

After 5 systematic experiments confirmed the 802.15.4 RX path is
unfixable from user code in this IDF v5.4 + C6 combination (D1), add a
parallel sync transport over ESP-NOW. Same TS_BEACON protocol, same
public API (c6_sync_espnow_get_epoch_us / is_valid / is_leader), but
runs on the WiFi MAC layer that ESP-IDF fully supports across every
ESP32 family.

The 802.15.4 code stays in source for when the IDF driver is fixed.
ESP-NOW is the working primary today.

Empirical (single-board COM9 — other 3 boards dropped off USB during
the experiment):
- c6_sync_espnow_init() succeeds: "init done local_id=… leader=
  yes(candidate) period=100ms"
- TX path 100% reliable: tx#101 fail=0 over ~15s at 100ms cadence
- RX awaiting cross-board test once USB-enumeration is restored

Trade vs. 802.15.4 design:
- Loses: "frees WiFi airtime for CSI" property
- Gains: known-working RX path, cross-target (S3 and C6 both)
- Same API surface — consumers swap transports without code change

Files:
- main/c6_sync_espnow.{h,c} — new module, ~210 lines
- main/CMakeLists.txt        — add to SRCS (always built, used on any target)
- main/main.c                — init after WiFi STA up, skip on QEMU mock
- test/capture-3board-experiment.py — surface c6_espnow log lines
- docs/WITNESS-LOG-110.md    — new §D-workaround documenting the pivot

Ref: ruvnet/RuView#762 / D1 known-issue / draft PR #764

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-05-22 22:37:12 -04:00
parent f8a2e36958
commit 88be283ab0
6 changed files with 306 additions and 1 deletions
@@ -76,10 +76,14 @@ for port in PORTS:
for L in grep_pattern(text, r'main: ESP32-C6.*Node ID', 2):
print(f' banner : {L}')
# Time-sync init
# Time-sync init (802.15.4 path — known broken D1)
for L in grep_pattern(text, r'c6_ts:.*(init done|promot|stepping down|tx fail)', 4):
print(f' c6_ts : {L}')
# ESP-NOW sync (D1 workaround, working path)
for L in grep_pattern(text, r'c6_espnow:.*(init done|promot|stepping down|tx#\d)', 6):
print(f' c6_espnow: {L}')
# WiFi mode + connect status
for L in grep_pattern(text, r'(wifi:mode|wifi:state|Retrying WiFi|got ip|Connected to WiFi)', 6):
print(f' wifi : {L}')