Files
ruvnet--RuView/firmware/esp32-csi-node/main/c6_sync_espnow.h
T
ruv f41f5fc85b feat(c6_sync_espnow): EMA-smooth cross-board offset, expose via get_epoch_us
SOTA iter 5 — converted the iter 4 ADR-110 §A0.8 closing recommendation
("host-side Kalman / linear fit on the offset trajectory") into a
firmware-side, fixed-point EMA so every downstream consumer of
c6_sync_espnow_get_epoch_us() gets bounded-jitter timestamps for free.

Implementation:
* α = 1/8 (Q3.3 shift = 3), ≈8-sample effective window at the 10 Hz
  beacon rate. Tracks the ≈1.4 ppm crystal drift §A0.8 measured while
  averaging out per-beacon WiFi-MAC jitter spikes.
* y[n] = y[n-1] + (raw - y[n-1]) >> 3  — integer arithmetic, two cycles
  on the RISC-V LP/HP cores, no float dependency.
* Seeded from the first follower-mode sample so we don't bias toward 0.
* New getter: int64_t c6_sync_espnow_get_offset_us_smoothed(void).
* c6_sync_espnow_get_offset_us() (raw) stays for diagnostics, unchanged.
* c6_sync_espnow_get_epoch_us() now prefers the smoothed offset once
  s_smoothed_seeded — meaning every CSI frame timestamp ADR-029/030
  consumes is already filtered, no host-side rework required.

Diag log line now prints both:
  c6_espnow: tx#N ... offset_us=R smoothed=S

90 s bench verification (witness §A0.9 + iter5-COM9-ema-90s.log) shows
both values tracking. Methodology caveat in §A0.9: short windows don't
let the smoothing benefit emerge over the raw noise floor — the
suppression ratio measurement needs ≥5 min, deferred to a long-soak
iteration.

Binary size cost: ~32 bytes (one int64, one bool, one getter). C6 build
still 45% partition slack.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-23 12:04:22 -04:00

69 lines
2.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file c6_sync_espnow.h
* @brief ESP-NOW based cross-node time-sync — ADR-110 D1 workaround.
*
* After 4 systematic experiments confirmed the 802.15.4 RX path is broken
* in this user-code + IDF v5.4 combination (see WITNESS-LOG-110 §D1), the
* cross-node sync claim was unblocked by switching transport from IEEE
* 802.15.4 to ESP-NOW (WiFi-based peer-to-peer, runs on the same 2.4 GHz
* radio but uses the WiFi MAC layer that ESP-IDF's 802.11 driver fully
* supports).
*
* Trade vs. 802.15.4:
* - Loses the "frees WiFi airtime for CSI" property (uses WiFi for sync)
* - Gains a known-working RX path on every ESP32 family
* - Same API surface (epoch_us, is_valid, is_leader) so call sites that
* used to depend on c6_timesync drop in unchanged
*
* Works on both ESP32-S3 and ESP32-C6 — the cross-node sync becomes a
* cross-target feature, not C6-only.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
#include <stdint.h>
#include <stdbool.h>
/**
* Initialize the ESP-NOW sync module. Must be called AFTER WiFi STA is
* connected (ESP-NOW needs the WiFi driver active).
*
* @return ESP_OK on success.
*/
esp_err_t c6_sync_espnow_init(void);
/**
* Returns the synced wall-clock estimate in microseconds.
* If no leader heard within the timeout, returns the local
* esp_timer_get_time() value unchanged (offset = 0).
*/
uint64_t c6_sync_espnow_get_epoch_us(void);
bool c6_sync_espnow_is_leader(void);
bool c6_sync_espnow_is_valid(void);
int64_t c6_sync_espnow_get_offset_us(void);
/**
* EMA-smoothed offset (α=1/8, ~8-sample effective window at the 10 Hz
* beacon rate). Tracks the ≈1.4 ppm crystal drift between two C6 boards
* (measured in §A0.8) while suppressing the 540 µs per-beacon WiFi-MAC
* jitter. CSI frame timestamps should stamp from this value, not the raw
* offset — `c6_sync_espnow_get_epoch_us()` already does so internally.
*/
int64_t c6_sync_espnow_get_offset_us_smoothed(void);
/* Counters for the witness harness — exposed for tests/diagnostics. */
uint32_t c6_sync_espnow_tx_count(void);
uint32_t c6_sync_espnow_tx_fail(void);
uint32_t c6_sync_espnow_rx_count(void);
uint32_t c6_sync_espnow_rx_magic_match(void);
#ifdef __cplusplus
}
#endif