Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e0fe10b

Browse files
committed
feat: add provision.py to repo, fix user guide paths
- Move provision.py from release-only asset into firmware/esp32-csi-node/ - Fix user guide references from scripts/provision.py to correct path - Update release link to v0.2.0-esp32 Co-Authored-By: claude-flow <[email protected]>
1 parent 915943c commit e0fe10b

2 files changed

Lines changed: 188 additions & 7 deletions

File tree

docs/user-guide.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ A 3-6 node ESP32-S3 mesh provides full CSI at 20 Hz. Total cost: ~$54 for a 3-no
612612

613613
**Flashing firmware:**
614614

615-
Pre-built binaries are available at [Releases](https://github.com/ruvnet/wifi-densepose/releases/tag/v0.1.0-esp32).
615+
Pre-built binaries are available at [Releases](https://github.com/ruvnet/wifi-densepose/releases/tag/v0.2.0-esp32).
616616

617617
```bash
618618
# Flash an ESP32-S3 (requires esptool: pip install esptool)
@@ -624,7 +624,7 @@ python -m esptool --chip esp32s3 --port COM7 --baud 460800 \
624624
**Provisioning:**
625625

626626
```bash
627-
python scripts/provision.py --port COM7 \
627+
python firmware/esp32-csi-node/provision.py --port COM7 \
628628
--ssid "YourWiFi" --password "YourPassword" --target-ip 192.168.1.20
629629
```
630630

@@ -635,7 +635,7 @@ Replace `192.168.1.20` with the IP of the machine running the sensing server.
635635
For multistatic mesh deployments with authenticated beacons (ADR-032), provision a shared mesh key:
636636

637637
```bash
638-
python scripts/provision.py --port COM7 \
638+
python firmware/esp32-csi-node/provision.py --port COM7 \
639639
--ssid "YourWiFi" --password "YourPassword" --target-ip 192.168.1.20 \
640640
--mesh-key "$(openssl rand -hex 32)"
641641
```
@@ -648,13 +648,13 @@ Each node in a multistatic mesh needs a unique TDM slot ID (0-based):
648648

649649
```bash
650650
# Node 0 (slot 0) — first transmitter
651-
python scripts/provision.py --port COM7 --tdm-slot 0 --tdm-total 3
651+
python firmware/esp32-csi-node/provision.py --port COM7 --tdm-slot 0 --tdm-total 3
652652
653653
# Node 1 (slot 1)
654-
python scripts/provision.py --port COM8 --tdm-slot 1 --tdm-total 3
654+
python firmware/esp32-csi-node/provision.py --port COM8 --tdm-slot 1 --tdm-total 3
655655
656656
# Node 2 (slot 2)
657-
python scripts/provision.py --port COM9 --tdm-slot 2 --tdm-total 3
657+
python firmware/esp32-csi-node/provision.py --port COM9 --tdm-slot 2 --tdm-total 3
658658
```
659659

660660
**Start the aggregator:**
@@ -720,7 +720,7 @@ docker run -p 3000:3000 -p 3001:3001 ruvnet/wifi-densepose:latest
720720
### ESP32: No data arriving
721721
722722
1. Verify the ESP32 is connected to the same WiFi network
723-
2. Check the target IP matches the sensing server machine: `python scripts/provision.py --port COM7 --target-ip <YOUR_IP>`
723+
2. Check the target IP matches the sensing server machine: `python firmware/esp32-csi-node/provision.py --port COM7 --target-ip <YOUR_IP>`
724724
3. Verify UDP port 5005 is not blocked by firewall
725725
4. Test with: `nc -lu 5005` (Linux) or similar UDP listener
726726
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#!/usr/bin/env python3
2+
"""
3+
ESP32-S3 CSI Node Provisioning Script
4+
5+
Writes WiFi credentials and aggregator target to the ESP32's NVS partition
6+
so users can configure a pre-built firmware binary without recompiling.
7+
8+
Usage:
9+
python provision.py --port COM7 --ssid "MyWiFi" --password "secret" --target-ip 192.168.1.20
10+
11+
Requirements:
12+
pip install esptool nvs-partition-gen
13+
(or use the nvs_partition_gen.py bundled with ESP-IDF)
14+
"""
15+
16+
import argparse
17+
import csv
18+
import io
19+
import os
20+
import struct
21+
import subprocess
22+
import sys
23+
import tempfile
24+
25+
26+
# NVS partition table offset — default for ESP-IDF 4MB flash with standard
27+
# partition scheme. The "nvs" partition starts at 0x9000 (36864) and is
28+
# 0x6000 (24576) bytes.
29+
NVS_PARTITION_OFFSET = 0x9000
30+
NVS_PARTITION_SIZE = 0x6000 # 24 KiB
31+
32+
33+
def build_nvs_csv(ssid, password, target_ip, target_port, node_id):
34+
"""Build an NVS CSV string for the csi_cfg namespace."""
35+
buf = io.StringIO()
36+
writer = csv.writer(buf)
37+
writer.writerow(["key", "type", "encoding", "value"])
38+
writer.writerow(["csi_cfg", "namespace", "", ""])
39+
if ssid:
40+
writer.writerow(["ssid", "data", "string", ssid])
41+
if password is not None:
42+
writer.writerow(["password", "data", "string", password])
43+
if target_ip:
44+
writer.writerow(["target_ip", "data", "string", target_ip])
45+
if target_port is not None:
46+
writer.writerow(["target_port", "data", "u16", str(target_port)])
47+
if node_id is not None:
48+
writer.writerow(["node_id", "data", "u8", str(node_id)])
49+
return buf.getvalue()
50+
51+
52+
def generate_nvs_binary(csv_content, size):
53+
"""Generate an NVS partition binary from CSV using nvs_partition_gen.py."""
54+
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f_csv:
55+
f_csv.write(csv_content)
56+
csv_path = f_csv.name
57+
58+
bin_path = csv_path.replace(".csv", ".bin")
59+
60+
try:
61+
# Try the pip-installed version first
62+
try:
63+
import nvs_partition_gen
64+
nvs_partition_gen.generate(csv_path, bin_path, size)
65+
with open(bin_path, "rb") as f:
66+
return f.read()
67+
except ImportError:
68+
pass
69+
70+
# Fall back to calling the ESP-IDF script directly
71+
idf_path = os.environ.get("IDF_PATH", "")
72+
gen_script = os.path.join(idf_path, "components", "nvs_flash",
73+
"nvs_partition_generator", "nvs_partition_gen.py")
74+
if os.path.isfile(gen_script):
75+
subprocess.check_call([
76+
sys.executable, gen_script, "generate",
77+
csv_path, bin_path, hex(size)
78+
])
79+
with open(bin_path, "rb") as f:
80+
return f.read()
81+
82+
# Last resort: try as a module
83+
subprocess.check_call([
84+
sys.executable, "-m", "nvs_partition_gen", "generate",
85+
csv_path, bin_path, hex(size)
86+
])
87+
with open(bin_path, "rb") as f:
88+
return f.read()
89+
90+
finally:
91+
for p in (csv_path, bin_path):
92+
if os.path.isfile(p):
93+
os.unlink(p)
94+
95+
96+
def flash_nvs(port, baud, nvs_bin):
97+
"""Flash the NVS partition binary to the ESP32."""
98+
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
99+
f.write(nvs_bin)
100+
bin_path = f.name
101+
102+
try:
103+
cmd = [
104+
sys.executable, "-m", "esptool",
105+
"--chip", "esp32s3",
106+
"--port", port,
107+
"--baud", str(baud),
108+
"write_flash",
109+
hex(NVS_PARTITION_OFFSET), bin_path,
110+
]
111+
print(f"Flashing NVS partition ({len(nvs_bin)} bytes) to {port}...")
112+
subprocess.check_call(cmd)
113+
print("NVS provisioning complete!")
114+
finally:
115+
os.unlink(bin_path)
116+
117+
118+
def main():
119+
parser = argparse.ArgumentParser(
120+
description="Provision ESP32-S3 CSI Node with WiFi and aggregator settings",
121+
epilog="Example: python provision.py --port COM7 --ssid MyWiFi --password secret --target-ip 192.168.1.20",
122+
)
123+
parser.add_argument("--port", required=True, help="Serial port (e.g. COM7, /dev/ttyUSB0)")
124+
parser.add_argument("--baud", type=int, default=460800, help="Flash baud rate (default: 460800)")
125+
parser.add_argument("--ssid", help="WiFi SSID")
126+
parser.add_argument("--password", help="WiFi password")
127+
parser.add_argument("--target-ip", help="Aggregator host IP (e.g. 192.168.1.20)")
128+
parser.add_argument("--target-port", type=int, help="Aggregator UDP port (default: 5005)")
129+
parser.add_argument("--node-id", type=int, help="Node ID 0-255 (default: 1)")
130+
parser.add_argument("--dry-run", action="store_true", help="Generate NVS binary but don't flash")
131+
132+
args = parser.parse_args()
133+
134+
if not any([args.ssid, args.password is not None, args.target_ip,
135+
args.target_port, args.node_id is not None]):
136+
parser.error("At least one config value must be specified "
137+
"(--ssid, --password, --target-ip, --target-port, --node-id)")
138+
139+
print("Building NVS configuration:")
140+
if args.ssid:
141+
print(f" WiFi SSID: {args.ssid}")
142+
if args.password is not None:
143+
print(f" WiFi Password: {'*' * len(args.password)}")
144+
if args.target_ip:
145+
print(f" Target IP: {args.target_ip}")
146+
if args.target_port:
147+
print(f" Target Port: {args.target_port}")
148+
if args.node_id is not None:
149+
print(f" Node ID: {args.node_id}")
150+
151+
csv_content = build_nvs_csv(args.ssid, args.password, args.target_ip,
152+
args.target_port, args.node_id)
153+
154+
try:
155+
nvs_bin = generate_nvs_binary(csv_content, NVS_PARTITION_SIZE)
156+
except Exception as e:
157+
print(f"\nError generating NVS binary: {e}", file=sys.stderr)
158+
print("\nFallback: save CSV and flash manually with ESP-IDF tools.", file=sys.stderr)
159+
fallback_path = "nvs_config.csv"
160+
with open(fallback_path, "w") as f:
161+
f.write(csv_content)
162+
print(f"Saved NVS CSV to {fallback_path}", file=sys.stderr)
163+
print(f"Flash with: python $IDF_PATH/components/nvs_flash/"
164+
f"nvs_partition_generator/nvs_partition_gen.py generate "
165+
f"{fallback_path} nvs.bin 0x6000", file=sys.stderr)
166+
sys.exit(1)
167+
168+
if args.dry_run:
169+
out = "nvs_provision.bin"
170+
with open(out, "wb") as f:
171+
f.write(nvs_bin)
172+
print(f"NVS binary saved to {out} ({len(nvs_bin)} bytes)")
173+
print(f"Flash manually: python -m esptool --chip esp32s3 --port {args.port} "
174+
f"write_flash 0x9000 {out}")
175+
return
176+
177+
flash_nvs(args.port, args.baud, nvs_bin)
178+
179+
180+
if __name__ == "__main__":
181+
main()

0 commit comments

Comments
 (0)