From 9d70d621daf781c9b8621df396d243d8c8b3ae18 Mon Sep 17 00:00:00 2001 From: ruv Date: Fri, 3 Apr 2026 00:26:22 -0400 Subject: [PATCH] feat: ADR-073 enable multi-frequency channel hopping from NVS - main.c: call csi_collector_set_hop_table() at boot when hop_count > 1 - provision.py: add --hop-channels and --hop-dwell flags, write chan_list blob and dwell_ms to NVS matching firmware's expected format - Validated: Node 1 hopping ch 1/6/11, Node 2 hopping ch 3/5/9, 200ms dwell, null subcarriers reduced from 19% to 16% Co-Authored-By: claude-flow --- firmware/esp32-csi-node/main/main.c | 11 +++++++++++ firmware/esp32-csi-node/provision.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/firmware/esp32-csi-node/main/main.c b/firmware/esp32-csi-node/main/main.c index df267bce..fd1abe4f 100644 --- a/firmware/esp32-csi-node/main/main.c +++ b/firmware/esp32-csi-node/main/main.c @@ -167,6 +167,17 @@ void app_main(void) } #else csi_collector_init(); + + /* ADR-073: Start multi-frequency channel hopping if configured in NVS. */ + if (g_nvs_config.channel_hop_count > 1) { + ESP_LOGI(TAG, "Starting channel hopping: %u channels, dwell=%lu ms", + (unsigned)g_nvs_config.channel_hop_count, + (unsigned long)g_nvs_config.dwell_ms); + csi_collector_set_hop_table( + g_nvs_config.channel_list, + g_nvs_config.channel_hop_count, + g_nvs_config.dwell_ms); + } #endif /* ADR-039: Initialize edge processing pipeline. */ diff --git a/firmware/esp32-csi-node/provision.py b/firmware/esp32-csi-node/provision.py index f13c27a8..5ed82a9a 100644 --- a/firmware/esp32-csi-node/provision.py +++ b/firmware/esp32-csi-node/provision.py @@ -71,6 +71,14 @@ def build_nvs_csv(args): mac_bytes = bytes(int(b, 16) for b in args.filter_mac.split(":")) # NVS blob: write as hex-encoded string for CSV compatibility writer.writerow(["filter_mac", "data", "hex2bin", mac_bytes.hex()]) + # ADR-073: Multi-frequency channel hopping + if args.hop_channels is not None: + channels = [int(c.strip()) for c in args.hop_channels.split(",")] + writer.writerow(["hop_count", "data", "u8", str(len(channels))]) + # Store as NVS blob (firmware reads "chan_list" as uint8 blob) + chan_bytes = bytes(channels) + writer.writerow(["chan_list", "data", "hex2bin", chan_bytes.hex()]) + writer.writerow(["dwell_ms", "data", "u32", str(args.hop_dwell)]) # ADR-066: Swarm bridge configuration if args.seed_url is not None: writer.writerow(["seed_url", "data", "string", args.seed_url]) @@ -181,6 +189,9 @@ def main(): parser.add_argument("--channel", type=int, help="CSI channel (1-14 for 2.4GHz, 36-177 for 5GHz). " "Overrides auto-detection from connected AP.") parser.add_argument("--filter-mac", type=str, help="MAC address to filter CSI frames (AA:BB:CC:DD:EE:FF)") + # ADR-073: Multi-frequency channel hopping + parser.add_argument("--hop-channels", type=str, help="Comma-separated channel list for hopping (e.g. '1,6,11')") + parser.add_argument("--hop-dwell", type=int, default=200, help="Dwell time per channel in ms (default: 200)") # ADR-066: Swarm bridge parser.add_argument("--seed-url", type=str, help="Cognitum Seed base URL (e.g. http://10.1.10.236)") parser.add_argument("--seed-token", type=str, help="Seed Bearer token (from pairing)")