fix(provision): per-device state keyed by serial port path, so a different board inherits the previous board's node_id
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 94.5k
- Forks
- 12.5k
- Avg merge
- 21h 27m
- Merged PRs (30d)
- 43
Description
Summary
provision.py keys its per-device state file by serial port path only, with no board
identity, so provisioning a different board on a port previously used by another board
silently offers the earlier board's settings — including node_id, which is the fusion cohort
key.
provision.py:126-129:
def _state_path_for(port: str, state_dir: str) -> str:
safe = port.replace("/", "_").replace(":", "_").replace("\\", "_")
return os.path.join(state_dir, f"{safe}.json")
Port names are reused aggressively — on macOS a second board plugged into the same hub slot
commonly enumerates as the same /dev/cu.usbmodemXXXX.
To be fair to the current design: the merge behaviour is documented (provision.py:22),
--reset is offered for recycled boards (:31, :362), and the merged values are printed
before flashing (:443). This is a footgun rather than a hidden failure. But the printed
values look identical whether they were chosen by the operator or inherited from a different
physical board, so there is nothing to notice.
Why now
Hit three times while provisioning three ESP32-S3 boards in one session. Each new board was
offered the previous board's node_id. Without --reset every board would have been
provisioned as the same node id, which on a multistatic deployment silently corrupts the
cohort rather than failing loudly.
Reproduction
provision.py --port <PORT> --node-id 2 ...with board A.- Unplug A, plug board B into the same port (same device path).
python3 provision.py --port <PORT> --state→ prints board A'snode_id,target_port
andchannel.- Provisioning B without
--resetwrites those values to B.
Done when
State is bound to board identity, so a different board on the same port does not inherit.
Work items
- Key the state file by chip identity —
esptool read-mac/ chip id — instead of, or in
addition to, the port path. The script already talks to the chip. - When a port's stored identity does not match the connected board, do not merge silently:
either ignore the stale state or require--reset. - Mark inherited values distinctly in the pre-flash summary (
:443), so "inherited from a
previous board" is visually different from "you passed this".
Note
--force-partial is marked deprecated "since #391/#574 … the script now merges with prior
state by default", which is the change that introduced this. Worth checking those for prior
discussion.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with provision.py:22, 126-129, and the pre-flash summary around line 443; run the reproduction with two boards sharing one port path. Check the existing chip communication and esptool read-mac support, then review issues #391 and #574. Done means a changed chip identity cannot silently inherit state, and inherited values are visibly distinct or require --reset.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, embedded-iot
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100