Skip to content

docs: quickstart guide for a fresh laptop + remote over Tailscale - #114

Open
iliabaranov wants to merge 4 commits into
mainfrom
docs/quickstart
Open

docs: quickstart guide for a fresh laptop + remote over Tailscale#114
iliabaranov wants to merge 4 commits into
mainfrom
docs/quickstart

Conversation

@iliabaranov

@iliabaranov iliabaranov commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

docs/QUICKSTART.md: a copy-paste walkthrough from an empty Ubuntu 24.04 laptop to a bench test through STOP and ARM, over the user's own tailnet.

Sections: Tailscale account/key -> laptop joins -> USB tether (host/setup/install.sh) -> build + flash -> first boot -> ROS 2 Jazzy machine node -> pair -> test-station table -> troubleshooting table. Appendices: plain-C runner, PoE/LAN/WiFi, what persists across reflash vs erase-flash.

Traps found while tracing the code, fixed here

Where What
components/microlink/Kconfig Auth-key help said "reusable + ephemeral". Firmware re-sends the key on every re-register and persists the node key in NVS, so: reusable, not ephemeral, pre-approved.
sdkconfig.credentials.example Required vs optional grouping; warns that sdkconfig must be regenerated after editing (silent stale-config pitfall, previously only in ops/HANDOFF.md).
docs/API.md GET/POST /api/role was undocumented; it is one of the two arming gates. /api/enter_download is admin + ?confirm=1.
USB_NCM_SETUP.md, host/README.md, TROUBLESHOOTING.md Removed the "factory-default peer 10.42.0.1:8890" claim; fresh NVS has had no peer since #65 (ring white, not red). Fixed the verify snippet that curled the host's own IP.
hardware/ASSEMBLY.md Linked to README.md#quickstart, which does not exist.
CONTRIBUTING.md idf.py flash monitor implies working serial logs; the console goes quiet once TinyUSB takes the port.
README.md "Build and run" is now a pointer plus a 7-line short version.
TAILSCALE_ISOLATION.md Example email instead of a real address.

To verify on hardware (marked <!-- VERIFY --> in the guide)

  1. Time from flash to pstop-01xxxxxx in tailscale status over the USB tether (guide says ~60 s).
  2. Ring settles white on a fresh unit (code says so; older docs said red-pulse).
  3. SoftAP fallback gateway address (Appendix B, presumably 192.168.4.1).

Also in this PR (second commit)

tools/pstop_test_remote.py spoke the v1 40-byte frame with message codes 0..3; the current pstop_c is 48-byte with codewords 0x55/0x92/0xAD/0x6A, so the node dropped every datagram and the script could only ever print "bond failed". Now v2 codec + aux-channel role (--role operator|stop_only), verified 5/5 against host/machine_app_runner and against the ROS 2 machine_bridge_node (operators: [16909185]), plus the stop_only inverse assertion. TESTING.md documents both targets.

Not in this PR

pre-commit run --all-files passes.

Adds docs/QUICKSTART.md: Tailscale account and auth key, laptop join, USB
tether, build/flash, first boot, ROS 2 machine node, pairing, and a bench
test through STOP and ARM. Appendices: plain-C runner, PoE/LAN/WiFi,
what persists across reflash vs erase-flash.

Fixes the traps the walkthrough surfaced:
- Kconfig ML_TAILSCALE_AUTH_KEY help said "reusable + ephemeral"; the
  firmware re-sends the key on every re-register and persists the node
  identity, so it must be reusable, NOT ephemeral, pre-approved.
- sdkconfig.credentials.example regrouped required/optional; documents
  that sdkconfig must be regenerated after editing it.
- API.md: document GET/POST /api/role (the second arming gate) and the
  admin auth + ?confirm=1 on /api/enter_download.
- Remove the stale "factory-default peer 10.42.0.1:8890" claim from
  USB_NCM_SETUP, host/README and TROUBLESHOOTING: a fresh unit has had
  no peer since #65. Fix the verify snippet that curled the host IP.
- ASSEMBLY.md linked to a README anchor that does not exist.
- CONTRIBUTING: idf.py monitor goes quiet once the USB tether starts.
- README "Build and run" is now a pointer plus the short version.
- TAILSCALE_ISOLATION: example email instead of a real one.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, straightforward documentation change. Reviewed the new docs/QUICKSTART.md and the updated cross-references (README.md, CONTRIBUTING.md, ASSEMBLY.md, host/README.md all now point at it correctly); checked the "factory-default peer" removal in USB_NCM_SETUP.md/TROUBLESHOOTING.md/host/README.md for consistency; verified the new /api/role and /api/enter_download?confirm=1 docs in API.md against the actual handlers in firmware/components/dcs_support/src/dcs_admin_pages.c (admin auth + confirm param match); and confirmed the ROS 2 package/executable names and operators/[[operator]] config keys referenced in the guide exist in ros2/protective_stop_machine and host/machine.toml.

Extended reasoning...

Overview

This PR is documentation-only: it adds docs/QUICKSTART.md (a 290-line end-to-end walkthrough) and updates README.md, CONTRIBUTING.md, hardware/ASSEMBLY.md, host/README.md, docs/API.md, docs/USB_NCM_SETUP.md, docs/TROUBLESHOOTING.md, docs/TAILSCALE_ISOLATION.md, components/microlink/Kconfig, and firmware/sdkconfig.credentials.example. No source code changes are included.

Verification performed

I cross-checked several factual claims in the new/updated docs against the actual firmware and ROS 2 source (which was touched in the immediately preceding commit e67b5c5, "remote-announced operator/stop-only role"):

  • docs/API.md's new /api/role (GET/POST, admin-gated) and updated /api/enter_download?confirm=1 (admin-gated) descriptions match api_role_get, api_role_post, and api_enter_download in firmware/components/dcs_support/src/dcs_admin_pages.c exactly (auth checks, query params, behavior).
  • The removed "factory-default machine peer at 10.42.0.1:8890" claim is consistently removed across USB_NCM_SETUP.md, TROUBLESHOOTING.md, and host/README.md, replaced with "fresh unit has no peer" language, and the internal anchor fix in hardware/ASSEMBLY.md (README.md#quickstartdocs/QUICKSTART.md) is correct — no other stale anchors remain.
  • ROS 2 package name (protective_stop_machine), executable (machine_bridge_node), topics (~/machine_state, ~/remotes, /pstop_hb), and the operators parameter all exist as referenced in ros2/protective_stop_machine.
  • host/machine.toml's [[operator]] table and stop_only field match what Appendix A of the quickstart describes.
  • The Kconfig and sdkconfig.credentials.example changes (reusable/non-ephemeral Tailscale key guidance, required-vs-optional grouping) are consistent with the role/NVS persistence behavior in the firmware.

The guide itself is honest about what hasn't been verified on hardware, marking timing/ring-color/AP-gateway claims with <!-- VERIFY --> comments rather than asserting them as fact.

Security risks

None. This is prose/config-comment documentation; no code paths, auth logic, or credentials are altered. The one example credential file only gains a comment structure and empty-string placeholders (no real secrets introduced).

Level of scrutiny

Low-to-moderate is appropriate here — accuracy-of-documentation review rather than code-correctness review. I spent effort cross-referencing the new API and config claims against actual source to make sure the guide doesn't mislead readers, and found no discrepancies.

Other factors

No CODEOWNERS restriction applies to these paths, the bug-hunting pass reported no findings, and the PR author's own "traps found" table lines up with what the diff actually contains. Given the diligence already visible in the diff and my own source cross-checks, this is a good candidate for approval without requiring further human review.

…pdated

The scripted software remote still used the v1 40-byte layout with message
codes 0..3, so every datagram was dropped by the size check in the current
pstop_c (48 bytes, codewords 0x55/0x92/0xAD/0x6A, two padding words) and
"bond failed" was the only possible outcome. It also never announced a
role, so even with a fixed codec the AND-rule would have kept it stop-only.

Now: v2 codec, aux-channel role in padding1 (--role operator|stop_only,
default operator), length-checked decode. --role stop_only asserts the
inverse contract (a full press-and-release must NOT arm).

Verified against host/machine_app_runner (5/5 + stop_only) and the ROS 2
machine_bridge_node with operators: [16909185] (5/5).

TESTING.md documents both targets and the stop_only mode; QUICKSTART gets a
one-line pointer for hardware-free bring-up; RECOVERY_PLAYBOOK no longer
calls the vendored pstop_c a submodule.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding, I traced the new v2 wire-protocol simulation in tools/pstop_test_remote.py against common/pstop_aux_channel.h — the padding1 encoding (version in bits 0-7, role in bits 8-15) and struct layout match bit-for-bit, so the --role/aux-channel test logic is exercising the real machine-side gate correctly.

Extended reasoning...

This run's new commit (54c966b) adds the substantive part of the diff: updating tools/pstop_test_remote.py from the v0/40-byte wire format to the v2/48-byte format with the aux-channel role announcement, plus doc touch-ups. I compared the script's encode()/decode() and the padding1 aux-channel bit layout against the actual wire-format definition in common/pstop_aux_channel.h (added in the base commit e67b5c5) and confirmed they agree exactly (schema version in low byte, role in high byte of padding1), and that the new length guard in decode() correctly handles short/malformed UDP datagrams instead of raising, which is an improvement over the previous behavior. This is a test-only script, not firmware or application logic, so the blast radius of any bug here is limited to the bench test tool's own assertions.

One finding (docs/QUICKSTART.md Appendix C persistence table incorrectly lumping the machine-side operator allowlist in with remote-side NVS state) has already been confirmed and will be posted as an inline comment. That is a documentation accuracy issue only, not a functional or security defect in the codebase. Since a finding already exists, per policy I should not restate it or produce a "what was reviewed" summary; instead I'm using the narrow ruled-out-note exception to record the additional wire-format cross-check I performed on the new commit, which was not part of the prior review (posted before this commit was pushed).

Comment thread docs/QUICKSTART.md Outdated
…te NVS

Review: Appendix C grouped 'operators' with the remote's NVS-persisted peer
and role. The allowlist lives in the machine's params (pstop_machine.yaml,
machine.toml, or /api/operators on machn); reflashing or erasing the remote
never touches it.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the only outstanding item from my prior review (Appendix C's persistence table conflating the remote's NVS-backed peer/role with the machine-side operator allowlist) has been correctly fixed by splitting that row.

What was reviewed: the only new commit since the last review (6549ad0) touches docs/QUICKSTART.md's persistence table; confirmed it now attributes operators to laptop-side config (pstop_machine.yaml/machine.toml), consistent with docs/API.md, and keeps Machine peer, self-role as the actual remote NVS state. tools/pstop_test_remote.py's v2 48-byte frame struct layout, codewords, and aux-channel role encoding are unchanged from the previously-reviewed revision.

Extended reasoning...

Overview

This run's diff against the last-reviewed revision is a single new commit (6549ad0) touching only docs/QUICKSTART.md. It splits the Appendix C persistence table row that previously grouped "Machine peer, role, operators" as remote NVS state, into two rows: Machine peer, self-role (remote NVS, correct) and Operator allowlist (operators) (laptop-side pstop_machine.yaml/machine.toml, not on the remote at all).

Security risks

None introduced by this change — it is a documentation-only correction. It actually reduces a real risk: the prior wording could have led an operator to believe re-flashing/erasing the remote affects who is authorized to re-arm the system, which is a safety-relevant misunderstanding for a protective-stop device. The fix correctly clarifies that authorization state lives off the remote.

Level of scrutiny

Low — this is a one-line-table documentation fix that directly and accurately addresses a specific inline comment I raised in a prior review of this PR. I re-checked it against docs/API.md's description of where operators is configured, and it is consistent. No code paths changed in this commit.

Other factors

No other code changed since the last reviewed revision (tools/pstop_test_remote.py and all other docs are identical to what was already reviewed twice). The bug hunting system reported no findings this run, and my own re-check of the fixed table found nothing further to raise.

… behaviour

Bench 2026-09-04 on PSTOP06: POST /api/iface/usb disables the tether (DHCP
Release + TinyUSB removed) rather than selecting it; API.md claimed 'select'.
Six Eth<->USB transitions with a bonded, armed ROS 2 machine: 0 stops,
0 rebonds; Eth->USB costs one ~5 s re-register.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants