From e0db6746e35c4fa2ad3bc88f78a9d23dee4f22f7 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:00:10 +0200 Subject: [PATCH 01/20] dotbot/controller_app: open dashboard by default, add --headless Breaking: the controller/simulator `-w`/`--no-webbrowser` flags are gone; pass `--headless` to suppress the auto-opened dashboard. The browser now opens by default, which is what most first-run users expect. AI-assisted: Claude Opus 4.7 --- dotbot/controller.py | 4 ++-- dotbot/controller_app.py | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/dotbot/controller.py b/dotbot/controller.py index ac5dbdad..fa434296 100644 --- a/dotbot/controller.py +++ b/dotbot/controller.py @@ -129,7 +129,7 @@ class ControllerSettings: controller_http_port: int = CONTROLLER_HTTP_PORT_DEFAULT map_size: str = MAP_SIZE_DEFAULT background_map: str = "" - webbrowser: bool = False + webbrowser: bool = True verbose: bool = False log_level: str = "info" log_output: str = os.path.join(os.getcwd(), "pydotbot.log") @@ -352,7 +352,7 @@ def handle_received_frame( dotbot.calibrated = self.dotbots[source].calibrated else: # reload if a new dotbot comes in - logger.info("New robot") + logger.info("New DotBot") notification_cmd = DotBotNotificationCommand.NEW_DOTBOT if frame.packet.payload_type == PayloadType.ADVERTISEMENT: diff --git a/dotbot/controller_app.py b/dotbot/controller_app.py index 11066248..6b9bee9a 100644 --- a/dotbot/controller_app.py +++ b/dotbot/controller_app.py @@ -174,10 +174,9 @@ def _maybe_scaffold_sim_state(explicit_init_state): help=f"Controller HTTP port of the REST API. Defaults to '{CONTROLLER_HTTP_PORT_DEFAULT}'", ) @click.option( - "-w", - "--webbrowser/--no-webbrowser", - default=None, - help="Open a web browser automatically", + "--headless", + is_flag=True, + help="Run without opening a web browser (the dashboard is still served).", ) @click.option( "-v", @@ -238,7 +237,7 @@ def main( map_size, background_map, simulator_init_state, - webbrowser, + headless, verbose, log_level, log_output, @@ -297,7 +296,7 @@ def main( "map_size": map_size, "background_map": background_map, "simulator_init_state": simulator_init_state, - "webbrowser": webbrowser, + "webbrowser": False if headless else None, "verbose": verbose, "log_level": log_level, "log_output": log_output, From ad7aa66d37fbfc622e0cc5aa746de1f741dec907 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:00:16 +0200 Subject: [PATCH 02/20] dotbot/cli: say "DotBot" and spell out SEGGER Embedded Studio in help AI-assisted: Claude Opus 4.7 --- dotbot/calibration/ota.py | 2 +- dotbot/cli/_fw_helpers.py | 2 +- dotbot/cli/device.py | 2 +- dotbot/cli/fw.py | 7 ++++--- dotbot/cli/joystick.py | 2 +- dotbot/cli/keyboard.py | 2 +- dotbot/cli/main.py | 4 ++-- dotbot/cli/swarm_lh2.py | 12 ++++++------ 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/dotbot/calibration/ota.py b/dotbot/calibration/ota.py index d11e1686..306d934b 100644 --- a/dotbot/calibration/ota.py +++ b/dotbot/calibration/ota.py @@ -129,6 +129,6 @@ def capture( raise TimeoutError( f"no LH{lh_index} sample from {self._device} after " - f"{retries + 1} attempt(s); is the bot in READY (app stopped) " + f"{retries + 1} attempt(s); is the DotBot in READY (app stopped) " f"and in view of the lighthouse?" ) diff --git a/dotbot/cli/_fw_helpers.py b/dotbot/cli/_fw_helpers.py index a40f3bf2..681ecea9 100644 --- a/dotbot/cli/_fw_helpers.py +++ b/dotbot/cli/_fw_helpers.py @@ -273,7 +273,7 @@ def run_make( if not embuild.is_file(): raise click.ClickException( f"emBuild not found at {embuild}. Check that SEGGER_DIR points " - f"at a real SES install." + f"at a real SEGGER Embedded Studio install." ) cmd = ["make", f"BUILD_TARGET={target}", f"BUILD_CONFIG={config}"] if quiet: diff --git a/dotbot/cli/device.py b/dotbot/cli/device.py index f0b92323..68b45da9 100644 --- a/dotbot/cli/device.py +++ b/dotbot/cli/device.py @@ -249,7 +249,7 @@ def info(sn_starting_digits): if net_id == "unprovisioned": click.echo("config: not provisioned (no swarm config on this device)") click.echo( - " → run `dotbot device flash-swarmit-sandbox` (robot) or " + " → run `dotbot device flash-swarmit-sandbox` (DotBot) or " "`flash-mari-gateway` (gateway) first." ) else: diff --git a/dotbot/cli/fw.py b/dotbot/cli/fw.py index a1d4966e..ee9fa527 100644 --- a/dotbot/cli/fw.py +++ b/dotbot/cli/fw.py @@ -63,7 +63,8 @@ @click.group( name="fw", help=( - "Firmware artifacts: build (from source via SES), fetch (a release), " + "Firmware artifacts: build (from source via SEGGER Embedded Studio), " + "fetch (a release), " "list. Bare apps by default; `--sandbox` for TrustZone NS apps. " "Flashing lives under `dotbot device` (one board) and `dotbot swarm` " "(the fleet). Need a Makefile knob? `dotbot fw make` forwards to `make`." @@ -150,7 +151,7 @@ def _resolve_build_target(target: str, sandbox: bool) -> str: "--verbose", is_flag=True, default=False, - help="Show full SES `-verbose -echo` output.", + help="Show full SEGGER Embedded Studio `-verbose -echo` output.", ) @click.pass_context def build(ctx, target, project, config, sandbox, rebuild, verbose): @@ -188,7 +189,7 @@ def build(ctx, target, project, config, sandbox, rebuild, verbose): @click.option("-v", "--verbose", is_flag=True, default=False) @click.pass_context def clean(ctx, target, config, sandbox, verbose): - """Clean SES build outputs (default target: dotbot-v3).""" + """Clean SEGGER Embedded Studio build outputs (default target: dotbot-v3).""" target = from_config(ctx, "target", "board", "fw") config = from_config(ctx, "config", "build_config", "fw") sandbox = from_config(ctx, "sandbox", "sandbox", "fw") diff --git a/dotbot/cli/joystick.py b/dotbot/cli/joystick.py index e6526194..feaa442d 100644 --- a/dotbot/cli/joystick.py +++ b/dotbot/cli/joystick.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026-present Inria # SPDX-License-Identifier: BSD-3-Clause -"""`dotbot run joystick` — drive a bot live from a USB joystick.""" +"""`dotbot run joystick` — drive a DotBot live from a USB joystick.""" from dotbot.joystick import main as _joystick_main diff --git a/dotbot/cli/keyboard.py b/dotbot/cli/keyboard.py index 3fa3e7fb..f0163f25 100644 --- a/dotbot/cli/keyboard.py +++ b/dotbot/cli/keyboard.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026-present Inria # SPDX-License-Identifier: BSD-3-Clause -"""`dotbot run keyboard` — drive a bot live from the keyboard.""" +"""`dotbot run keyboard` — drive a DotBot live from the keyboard.""" from dotbot.keyboard import main as _keyboard_main diff --git a/dotbot/cli/main.py b/dotbot/cli/main.py index 8fca3951..a249e14d 100644 --- a/dotbot/cli/main.py +++ b/dotbot/cli/main.py @@ -72,8 +72,8 @@ subcommands=_SUBCOMMANDS, help=( "One CLI for the whole DotBot workflow: build and flash firmware, " - "program and control a single robot, and run experiments over the air " - "across a swarm - from one bot to a thousand." + "program and control a single DotBot, and run experiments over the air " + "across a swarm - from one DotBot to a thousand." ), ) @click.option( diff --git a/dotbot/cli/swarm_lh2.py b/dotbot/cli/swarm_lh2.py index 544020b8..39356dc4 100644 --- a/dotbot/cli/swarm_lh2.py +++ b/dotbot/cli/swarm_lh2.py @@ -10,7 +10,7 @@ - `collect` - walk one DotBot through the 4 arena corners, trigger a raw-count capture per corner over the air, solve the homography, and save the calibration under ~/.dotbot/. -- `push ` - send a saved calibration to the bot over the air. A thin +- `push ` - send a saved calibration to the DotBot over the air. A thin forward to swarmit's `calibrate-lh2`, which picks the payload format (legacy `.out` or `calibration-*.toml`) by extension. @@ -149,7 +149,7 @@ def cmd() -> None: @click.option( "--push", is_flag=True, - help="Send the computed calibration back to the bot over the air.", + help="Send the computed calibration back to the DotBot over the air.", ) @click.pass_context def _collect(ctx, device, conn, swarm_id, distance, timeout, retries, tag, push): @@ -196,7 +196,7 @@ def _collect(ctx, device, conn, swarm_id, distance, timeout, retries, tag, push) time.sleep(0.2) click.echo( f"\nCollecting LH2 calibration from {device.upper()}.\n" - "Stop the bot's app first (capture only runs in READY).\n" + "Stop the DotBot's app first (capture only runs in READY).\n" ) for corner in CORNERS: click.prompt( @@ -236,10 +236,10 @@ def _collect(ctx, device, conn, swarm_id, distance, timeout, retries, tag, push) if push: payload = manager.calibration_output_path.read_bytes() client.send_lh2_calibration(payload) - click.echo("Sent the calibration to the bot over the air.") + click.echo("Sent the calibration to the DotBot over the air.") else: click.echo( - "To send it to the bot over the air:\n" + "To send it to the DotBot over the air:\n" f" dotbot swarm lh2-calibration push {path}" ) @@ -247,7 +247,7 @@ def _collect(ctx, device, conn, swarm_id, distance, timeout, retries, tag, push) @cmd.command( name="push", help=( - "Send a saved LH2 calibration to the bot over the air. Forwards to " + "Send a saved LH2 calibration to the DotBot over the air. Forwards to " "swarmit's `calibrate-lh2`, which picks the payload format (legacy " "`.out` or `calibration-*.toml`) by file extension." ), From e72327f2ec861ae96945d6c93b23de54ef500f3d Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:00:23 +0200 Subject: [PATCH 03/20] doc: say "DotBot", spell out SEGGER Embedded Studio, drop pip --pre AI-assisted: Claude Opus 4.7 --- doc/cli/index.md | 4 +-- doc/cli/run.md | 18 +++++----- doc/cli/swarm.md | 18 +++++----- doc/guides/controller.md | 23 ++++++------ doc/guides/index.md | 12 +++---- doc/guides/lh2-calibration-cabled.md | 17 ++++----- doc/guides/lh2-calibration.md | 52 ++++++++++++++-------------- doc/guides/one-bot.md | 12 +++---- doc/guides/simulator.md | 20 +++++------ doc/hardware/index.md | 16 ++++----- doc/index.md | 15 ++++---- doc/reference/configuration.md | 16 ++++----- doc/reference/mqtt.md | 16 ++++----- doc/reference/troubleshooting.md | 2 +- doc/sdk/index.md | 8 ++--- 15 files changed, 126 insertions(+), 123 deletions(-) diff --git a/doc/cli/index.md b/doc/cli/index.md index c763f1ac..53ebc837 100644 --- a/doc/cli/index.md +++ b/doc/cli/index.md @@ -11,7 +11,7 @@ config One CLI for the whole DotBot workflow: build firmware, flash one board, control a whole swarm, and launch the host-side processes that tie it together - -from one bot to a thousand. +from one DotBot to a thousand. ```bash dotbot --help @@ -25,7 +25,7 @@ dotbot --help |---|---|---| | [`fw`](fw.md) | Build, fetch, and list firmware files. No hardware needed. | You want a `.hex`/`.bin` to flash later, or to see what builds. | | [`device`](device.md) | Flash one cabled board and read its info. | A DotBot or DK is plugged into your USB port right now. | -| [`swarm`](swarm.md) | Drive the whole fleet over the air - status, OTA flash, start/stop, monitor. | You're operating many provisioned bots through a gateway. | +| [`swarm`](swarm.md) | Drive the whole fleet over the air - status, OTA flash, start/stop, monitor. | You're operating many provisioned DotBots through a gateway. | | [`run`](run.md) | Start host processes on your computer - controller, gateway bridge, simulator, demos, teleop. | You need the web UI, a gateway bridge, the simulator, or a demo. | Beyond the four namespaces, [`config`](config.md) scaffolds and inspects the diff --git a/doc/cli/run.md b/doc/cli/run.md index 02db38cd..339547dc 100644 --- a/doc/cli/run.md +++ b/doc/cli/run.md @@ -14,7 +14,7 @@ dotbot run --help # the full list | `controller` | Control plane: REST/WS API + web dashboard. The hub everything else talks to. | | `gateway` | Host bridge: gateway firmware UART ↔ MQTT broker. | | `simulator` | Standalone simulator (no hardware). | -| `lh2-calibration` | LH2 calibration on one cabled board (capture / apply); deployed bots use `swarm lh2-calibration`. | +| `lh2-calibration` | LH2 calibration on one cabled board (capture / apply); deployed DotBots use `swarm lh2-calibration`. | | `demo` | Built-in research demos (qrkey phone bridge, …). | | `keyboard` | Drive a DotBot from the keyboard. | | `joystick` | Drive a DotBot from a joystick. | @@ -26,16 +26,16 @@ Connect to a swarm and serve the dashboard at `http://localhost:8000/PyDotBot/`. `simulator`. ```bash -dotbot run controller --conn mqtts://argus.paris.inria.fr:8883 --swarm-id 1234 -w -dotbot run controller --conn /dev/ttyACM0 -w +dotbot run controller --conn mqtts://argus.paris.inria.fr:8883 --swarm-id 1234 +dotbot run controller --conn /dev/ttyACM0 ``` | Flag | Meaning | |---|---| | `-n/--conn` | `mqtts://host:port`, serial path, or `simulator` | | `-s/--swarm-id` | hex swarm id - **required for MQTT**, ignored for serial/simulator | -| `-w/--webbrowser` | open the dashboard automatically | -| `--csv-data-output` | record robot data to a CSV file | +| `--headless` | don't open the dashboard in a browser (it's still served) | +| `--csv-data-output` | record DotBot data to a CSV file | Full options and the dashboard tour live in [the controller guide](../guides/controller.md). See `dotbot run controller --help`. @@ -60,15 +60,15 @@ No hardware, no gateway. Exactly equivalent to `run controller --conn simulator` so it shares the controller's flags and serves the same dashboard. ```bash -dotbot run simulator -w +dotbot run simulator ``` ## `lh2-calibration` - capture & apply (cabled) Lighthouse v2 calibration against a single serial-attached board. `collect` opens a TUI to capture LH2 counts; `apply` writes the saved calibration out as -a C header. This is the cabled, bench path - for deployed bots, capture over the -air with [`swarm lh2-calibration`](swarm.md) instead. +a C header. This is the cabled, bench path - for deployed DotBots, capture over +the air with [`swarm lh2-calibration`](swarm.md) instead. ```bash dotbot run lh2-calibration collect @@ -90,7 +90,7 @@ dotbot run demo qr # qrkey phone bridge Drive a DotBot live through a running controller (start one with `run controller` first). Both default to `localhost:8000`; pass `-d` to target a -specific robot by hex address. +specific DotBot by hex address. ```bash dotbot run keyboard diff --git a/doc/cli/swarm.md b/doc/cli/swarm.md index badaa5e4..2394fc99 100644 --- a/doc/cli/swarm.md +++ b/doc/cli/swarm.md @@ -1,8 +1,8 @@ # `dotbot swarm` - operate the fleet over the air -Run experiments across many robots at once. `dotbot swarm` drives the +Run experiments across many DotBots at once. `dotbot swarm` drives the [SwarmIT](https://github.com/DotBots/swarmit) orchestration backend: it -OTA-flashes a sandbox app to every bot, starts/stops it, and watches status - +OTA-flashes a sandbox app to every DotBot, starts/stops it, and watches status - all wirelessly through a gateway. For one cabled board, use [`device`](device.md). To build the apps you flash, @@ -19,14 +19,14 @@ see [`fw`](fw.md). The host bridge and dashboard come from [`run`](run.md). ## 1. Provision once -Each robot needs the SwarmIT sandbox-host firmware; the gateway is an +Each DotBot needs the SwarmIT sandbox-host firmware; the gateway is an nRF5340-DK running the Mari gateway firmware. Both are cabled flashes over USB-C (the DotBot v3 has an on-board programmer - no separate J-Link needed). Details and chip caveats live in [`device`](device.md). ```bash dotbot device flash-mari-gateway --swarm-id 1234 -s 10 -f 0.8.0rc1 # a DK -> gateway, net id 0x1234 -dotbot device flash-swarmit-sandbox --swarm-id 1234 -s 77 -f 0.8.0rc1 # each bot -> sandbox host +dotbot device flash-swarmit-sandbox --swarm-id 1234 -s 77 -f 0.8.0rc1 # each DotBot -> sandbox host ``` ## 2. Start the host bridge @@ -109,14 +109,14 @@ driving it over the swarm. The arena geometry and `-d` sizing live in the ```bash dotbot swarm stop # capture only runs in READY -dotbot swarm lh2-calibration collect --device BC3D... -d 500 # capture from one bot -> solve -> save -dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml # apply to every ready bot +dotbot swarm lh2-calibration collect --device BC3D... -d 500 # capture from one DotBot -> solve -> save +dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml # apply to every ready DotBot ``` -`collect` walks one bot through the four arena corners over the air, solves the +`collect` walks one DotBot through the four arena corners over the air, solves the homography, and saves it under `~/.dotbot/`. `push` (no `--device`) then sends -that calibration to **every ready bot** - the arena shares one transform. -(`collect --push` is a single-bot shortcut: it sends only to the captured bot.) +that calibration to **every ready DotBot** - the arena shares one transform. +(`collect --push` is a single-DotBot shortcut: it sends only to the captured DotBot.) `push` takes a `calibration-*.toml` or the legacy raw payload - the format is picked by file extension. Get the `--device` address from `dotbot swarm status`. diff --git a/doc/guides/controller.md b/doc/guides/controller.md index ee009066..04a10b78 100644 --- a/doc/guides/controller.md +++ b/doc/guides/controller.md @@ -2,7 +2,7 @@ The controller is the host-side control plane: it talks to your gateway (or a simulator), exposes a REST + WebSocket API, and serves a web UI to drive your -robots. +DotBots. ## Start it @@ -10,26 +10,27 @@ Point the controller at a connection and open the web UI: ```bash # serial gateway plugged into your computer (no swarm-id needed) -dotbot run controller --conn /dev/ttyACM0 -w +dotbot run controller --conn /dev/ttyACM0 # a swarm over MQTT (swarm-id required - the broker carries many swarms) -dotbot run controller --conn mqtts://argus.paris.inria.fr:8883 --swarm-id 1234 -w +dotbot run controller --conn mqtts://argus.paris.inria.fr:8883 --swarm-id 1234 # no hardware at all - pure software simulator -dotbot run controller --conn simulator -w +dotbot run controller --conn simulator ``` `--conn` takes one string: a serial device path (`/dev/ttyACM0`, `COM3` on Windows), an MQTT broker (`mqtts://host:port`), or `simulator`. -`-w` / `--webbrowser` opens a tab automatically. Otherwise browse to +The dashboard opens in a browser tab automatically. Pass `--headless` to +suppress that (it's still served); browse to yourself. | Flag | What it does | |---|---| | `-n, --conn` | Connection: serial path, `mqtts://host:port`, or `simulator` | | `-s, --swarm-id` | Swarm id in hex (required for MQTT, ignored otherwise) | -| `-w, --webbrowser` | Open the web UI automatically | +| `--headless` | Don't open the web UI in a browser (still served) | | `--controller-http-port` | HTTP/REST port (default `8000`) | | `--config-path` | Path to a `.toml` config file | | `--dotbot / --sailbot` | With `--conn simulator`: which robot to simulate | @@ -38,7 +39,7 @@ See `dotbot run controller --help` for the full list (logging, CSV export, map size, background map, simulator init state). `dotbot run simulator` is shorthand for `dotbot run controller --conn simulator` - try -the UI with no robot or gateway. +the UI with no DotBot or gateway. ## Use a config file @@ -64,9 +65,9 @@ discovered and the full schema. At the page lists every DotBot the controller sees. Select one to control it: -- **Joystick** - a virtual joystick drives the selected bot. -- **RGB LED** - pick a color and the bot's LED follows. -- If you flashed Lighthouse 2 localization, bots report their `(x, y)` position +- **Joystick** - a virtual joystick drives the selected DotBot. +- **RGB LED** - pick a color and the DotBot's LED follows. +- If you flashed Lighthouse 2 localization, DotBots report their `(x, y)` position on the map (see [LH2 calibration](lh2-calibration.md)). ## Firefox websockets note @@ -77,5 +78,5 @@ being blocked. Open `about:config` (Ctrl + L, then type it), find ## Next steps -- Flash robots and a gateway first - see [device flashing](../cli/device.md). +- Flash DotBots and a gateway first - see [device flashing](../cli/device.md). - Operate the whole fleet over the air - see [swarm](../cli/swarm.md). diff --git a/doc/guides/index.md b/doc/guides/index.md index 60fe7321..14399b88 100644 --- a/doc/guides/index.md +++ b/doc/guides/index.md @@ -11,13 +11,13 @@ lh2-calibration lh2-calibration-cabled ``` -- [Try it in the simulator](simulator.md) - run the full UI and script bots with - no hardware. -- [Drive a single DotBot](one-bot.md) - build, flash, and control one bot end to - end. +- [Try it in the simulator](simulator.md) - run the full UI and script DotBots + with no hardware. +- [Drive a single DotBot](one-bot.md) - build, flash, and control one DotBot end + to end. - [Run the controller + web UI](controller.md) - drive and visualize a swarm from the browser. -- [Lighthouse 2 localization](lh2-calibration.md) - give your bots real-world +- [Lighthouse 2 localization](lh2-calibration.md) - give your DotBots real-world `(x, y)` positions, calibrated over the air. - [LH2 calibration over a cable](lh2-calibration-cabled.md) - the bench - alternative, for a single USB-connected bot. + alternative, for a single USB-connected DotBot. diff --git a/doc/guides/lh2-calibration-cabled.md b/doc/guides/lh2-calibration-cabled.md index 9f20122c..f2b1a1c1 100644 --- a/doc/guides/lh2-calibration-cabled.md +++ b/doc/guides/lh2-calibration-cabled.md @@ -2,8 +2,9 @@ The bench alternative to the [over-the-air flow](lh2-calibration.md): calibrate a single DotBot connected over USB, with no swarm provisioned. Reach for this when -you're working with one bot on the bench, or before the fleet is set up. For -already-deployed bots, prefer the over-the-air flow - no cable, no firmware swap. +you're working with one DotBot on the bench, or before the fleet is set up. For +already-deployed DotBots, prefer the over-the-air flow - no cable, no firmware +swap. What LH2 calibration is, and the arena geometry (the `-d` square sizing), are covered in the [main guide](lh2-calibration.md); this page is just the cabled @@ -18,13 +19,13 @@ capture path. - The `[calibrate]` extra: ```bash -pip install --pre 'pydotbot[calibrate]' +pip install 'pydotbot[calibrate]' ``` ## 1. Flash the capture firmware The `lh2_calibration` app streams raw LH2 counts over serial. Flash it to the -cabled bot (see [device](../cli/device.md) for serial-prefix selection): +cabled DotBot (see [device](../cli/device.md) for serial-prefix selection): ```bash dotbot device flash lh2_calibration -s 77 # board defaults to dotbot-v3 @@ -32,14 +33,14 @@ dotbot device flash lh2_calibration -s 77 # board defaults to dotbot-v3 ## 2. Capture the four reference points -Place the bot on the floor square and run the TUI. `-d` is the side length of +Place the DotBot on the floor square and run the TUI. `-d` is the side length of the square, in millimeters: ```bash dotbot run lh2-calibration collect -p /dev/cu.usbmodem... -d 500 ``` -Move the bot to each corner - Top left -> Top right -> Bottom left -> Bottom +Move the DotBot to each corner - Top left -> Top right -> Bottom left -> Bottom right - pressing the matching button in the TUI at each. When all four are captured, save. The calibration is written under `~/.dotbot/` (a `calibration-.toml`), the same place the over-the-air flow uses. @@ -74,12 +75,12 @@ dotbot run lh2-calibration apply ./lh2_calibration.h ``` The swarmit secure bootloader `#include`s this file; rebuild and reflash the -bootloader for it to take effect. For already-running bots, prefer the +bootloader for it to take effect. For already-running DotBots, prefer the over-the-air push above - no reflash needed. ## Troubleshooting -- **No counts in the TUI** - wrong `-p` port, or the bot can't see both base +- **No counts in the TUI** - wrong `-p` port, or the DotBot can't see both base stations. Confirm line-of-sight and that the base-station LEDs are steady. - **Positions look skewed or mirrored** - the corners were captured out of order. Re-run `collect` and follow TL -> TR -> BL -> BR exactly. diff --git a/doc/guides/lh2-calibration.md b/doc/guides/lh2-calibration.md index f39eb36d..e00640c3 100644 --- a/doc/guides/lh2-calibration.md +++ b/doc/guides/lh2-calibration.md @@ -1,63 +1,63 @@ # Lighthouse 2 (LH2) calibration Lighthouse 2 gives every DotBot a real-world **(x, y) position** on your arena -floor. Two SteamVR base stations sweep the room with IR; each bot's LH2 sensor +floor. Two SteamVR base stations sweep the room with IR; each DotBot's LH2 sensor times the sweeps. Calibration is the one-time step that maps those raw sweep -counts to metric coordinates: you place one bot on four known corners of a +counts to metric coordinates: you place one DotBot on four known corners of a square, capture, and the resulting transform is pushed to the whole fleet. You do this once per physical setup (move a base station -> recalibrate). -**The default flow is over the air** - drive one already-deployed bot through the -corners over the swarm, no cable and no firmware swap. If you'd rather calibrate -a single bot on the bench over USB, see +**The default flow is over the air** - drive one already-deployed DotBot through +the corners over the swarm, no cable and no firmware swap. If you'd rather +calibrate a single DotBot on the bench over USB, see [LH2 calibration over a cable](lh2-calibration-cabled.md). ## Prerequisites -- A provisioned swarm: a gateway plus sandbox-host bots, reachable from your +- A provisioned swarm: a gateway plus sandbox-host DotBots, reachable from your config (see [swarm](../cli/swarm.md)). - Two LH2 base stations mounted ~2 m up, facing the arena. - A square marked on the floor with a known side length. - The `[calibrate]` extra (the homography solve uses opencv): ```bash -pip install --pre 'pydotbot[calibrate]' +pip install 'pydotbot[calibrate]' ``` ## Capture and push The calibration is a property of the **arena** (the base-station layout), not the -individual bot, so you capture once from any one bot and apply the result to the -whole fleet. Two steps: +individual DotBot, so you capture once from any one DotBot and apply the result +to the whole fleet. Two steps: ```bash -dotbot swarm stop # put the bots in READY +dotbot swarm stop # put the DotBots in READY dotbot swarm lh2-calibration collect \ - --device BC3D3C8A2A6F8E68 -d 500 # capture from one bot -> solve -> save + --device BC3D3C8A2A6F8E68 -d 500 # capture from one DotBot -> solve -> save dotbot swarm lh2-calibration push \ - ~/.dotbot/calibration-.toml # apply to every ready bot + ~/.dotbot/calibration-.toml # apply to every ready DotBot ``` -`collect` walks one bot through the four corners - **top-left -> top-right -> -bottom-left -> bottom-right** - (capture only runs while the bot is in READY, so +`collect` walks one DotBot through the four corners - **top-left -> top-right -> +bottom-left -> bottom-right** - (capture only runs while the DotBot is in READY, so `swarm stop` first). Each prompt triggers a raw-count capture over the air; it then solves the homography and saves a `calibration-.toml` under `~/.dotbot/` (the path is printed at the end). Find the `--device` address with `dotbot swarm status`. -`push` with **no `--device`** sends that calibration to **every ready bot** - the -whole arena shares one transform. It accepts a `calibration-*.toml` or the legacy -raw `calibration.out` payload; the format is picked by file extension. +`push` with **no `--device`** sends that calibration to **every ready DotBot** - +the whole arena shares one transform. It accepts a `calibration-*.toml` or the +legacy raw `calibration.out` payload; the format is picked by file extension. ```{note} -`collect --push` is a **single-bot shortcut**: it sends the result to *only* the -`--device` bot you captured from (handy to spot-check that one bot, or for a -single-bot setup). To calibrate the fleet, run the standalone `push` above - it -targets all ready bots. +`collect --push` is a **single-DotBot shortcut**: it sends the result to *only* +the `--device` DotBot you captured from (handy to spot-check that one DotBot, or +for a single-DotBot setup). To calibrate the fleet, run the standalone `push` +above - it targets all ready DotBots. ``` -Once pushed, the bots report positions, which show up live in the +Once pushed, the DotBots report positions, which show up live in the [controller](../cli/run.md) Web UI. ### `collect` flags @@ -70,7 +70,7 @@ Once pushed, the bots report positions, which show up live in the | `--timeout` | `5` s | Seconds to wait for each capture before re-triggering. | | `--retries` | `3` | Re-trigger this many times per corner before giving up. | | `--tag` | - | Arena/setup label (e.g. `office-2x2m`) added to the filename + metadata. | -| `--push` | off | After solving, send to the captured `--device` bot **only** (use the standalone `push` for the whole fleet). | +| `--push` | off | After solving, send to the captured `--device` DotBot **only** (use the standalone `push` for the whole fleet). | See `dotbot swarm lh2-calibration collect --help` for the full list. @@ -97,18 +97,18 @@ side): ⌖ LH2 base station (mounted ~2 m up, facing the arena) ``` -`TL/TR/BL/BR` are the four reference points you place the bot on; `d` is the +`TL/TR/BL/BR` are the four reference points you place the DotBot on; `d` is the square side (`--distance`, in mm), `5·d` the resulting arena. | `-d` | Square | Usable arena | |---|---|---| | `400` | 40 cm | 2.0 m × 2.0 m | | `500` | 50 cm | 2.5 m × 2.5 m | -| `800` | 80 cm | 4.0 m × 4.0 m (used for the 725-bot Limerick run) | +| `800` | 80 cm | 4.0 m × 4.0 m (used for the 725-DotBot Limerick run) | ## Troubleshooting -- **Capture times out** - the bot isn't in READY (run `dotbot swarm stop` +- **Capture times out** - the DotBot isn't in READY (run `dotbot swarm stop` first), or it can't see both base stations. The address passed to `--device` must match one from `dotbot swarm status`. - **Positions look skewed or mirrored** - the corners were captured out of diff --git a/doc/guides/one-bot.md b/doc/guides/one-bot.md index 7b059608..7cd284c3 100644 --- a/doc/guides/one-bot.md +++ b/doc/guides/one-bot.md @@ -1,7 +1,7 @@ # Drive a single DotBot Build and flash one DotBot and a gateway, cable them to your computer, and drive -the bot from the web UI. This is the smallest real-hardware setup. For the +the DotBot from the web UI. This is the smallest real-hardware setup. For the no-hardware path, use the [simulator](simulator.md) instead. Building firmware needs SEGGER Embedded Studio and `nrfjprog` (see the README @@ -17,7 +17,7 @@ app) and the network core (the radio) - so you build and flash two images: # build the bare dotbot apps into the cache (needs SEGGER Embedded Studio) dotbot fw artifacts --app dotbot dotbot fw artifacts --app nrf5340_net --target nrf5340dk-net -# cable-flash to the bot whose J-Link serial starts with 77 +# cable-flash to the DotBot whose J-Link serial starts with 77 dotbot device flash dotbot -s 77 # app core dotbot device flash nrf5340_net -b nrf5340dk-net -s 77 # network core ``` @@ -25,7 +25,7 @@ dotbot device flash nrf5340_net -b nrf5340dk-net -s 77 # network core ## 2. Build and flash the gateway The gateway is a dev board (e.g. an nRF52840-DK) plugged into your computer; it -bridges the robot's radio to USB serial. +bridges the DotBot's radio to USB serial. ```bash dotbot fw artifacts --app dotbot_gateway --target nrf52840dk @@ -39,15 +39,15 @@ With the gateway plugged in, point the controller at its serial port and open the web UI: ```bash -dotbot run controller --conn /dev/ttyACM0 -w # serial gateway; no swarm-id needed +dotbot run controller --conn /dev/ttyACM0 # serial gateway; no swarm-id needed ``` -Select the bot in the browser and steer it with the joystick. See the +Select the DotBot in the browser and steer it with the joystick. See the [controller + web UI guide](controller.md) for the full UI tour, and [`fw`](../cli/fw.md) / [`device`](../cli/device.md) for the firmware commands. ## Next -- Operate many bots over the air - the [`swarm`](../cli/swarm.md) reference. +- Operate many DotBots over the air - the [`swarm`](../cli/swarm.md) reference. - Add real-world positions - [LH2 calibration over a cable](lh2-calibration-cabled.md) (or [over the air](lh2-calibration.md) once you've provisioned a swarm). diff --git a/doc/guides/simulator.md b/doc/guides/simulator.md index 75b1e717..e8746bb4 100644 --- a/doc/guides/simulator.md +++ b/doc/guides/simulator.md @@ -1,26 +1,26 @@ # Try it in the simulator -The simulator runs the **full controller and web UI with no hardware** - no bot, -no gateway, no radio. It's the fastest way to see DotBot work, and because it -exposes the exact same REST/WebSocket API as the real controller, code you write -against the simulator runs unchanged against real robots. +The simulator runs the **full controller and web UI with no hardware** - no +DotBot, no gateway, no radio. It's the fastest way to see DotBot work, and +because it exposes the exact same REST/WebSocket API as the real controller, code +you write against the simulator runs unchanged against real DotBots. ## Start it ```bash -dotbot run simulator -w +dotbot run simulator ``` This opens the web UI at driving a simulated swarm. `dotbot run simulator` is shorthand for `dotbot run controller --conn simulator`, so everything in the -[controller + web UI guide](controller.md) applies. Drive the bots from the +[controller + web UI guide](controller.md) applies. Drive the DotBots from the joystick and watch them on the map. -## Drive one bot in a circle +## Drive one DotBot in a circle -The simplest demo - it grabs the first bot the controller sees and drives it in a -circle. With the simulator running, in a second terminal: +The simplest demo - it grabs the first DotBot the controller sees and drives it +in a circle. With the simulator running, in a second terminal: ```bash dotbot run demo circle @@ -59,7 +59,7 @@ dotbot run demo --list # what's available dotbot run demo qr # phone-as-joystick over QrKey ``` -Richer multi-bot scenarios - work-and-charge, charging-station, labyrinth, the +Richer multi-DotBot scenarios - work-and-charge, charging-station, labyrinth, the naming game, motion shapes - live in `dotbot/examples/`, each with its own README and a simulator init state. They drive the controller over the same REST/WebSocket API shown above, so they run against the simulator or real diff --git a/doc/hardware/index.md b/doc/hardware/index.md index 983a431a..5fd377c7 100644 --- a/doc/hardware/index.md +++ b/doc/hardware/index.md @@ -20,14 +20,14 @@ What to have on hand (the two USB cables are the ones you'll reach for most): | **micro-USB to USB-A (or USB-C)** | The nRF5340-DK gateway's on-board J-Link. | | **Barrel-jack charger** (2.5 mm, 6-18 V) | Charges the DotBot v3 supercap (J4); free-roaming only. | -## DotBot v3 - the robot +## DotBot v3 -The robot has two connectors you'll use: +The DotBot has two connectors you'll use: | Connector | What it's for | |---|---| -| **USB-C (J2)** | Flash and program the bot. Also powers it while plugged in. | -| **Barrel jack (J4)** | Charges the on-board supercapacitor (the bot's "battery"). | +| **USB-C (J2)** | Flash and program the DotBot. Also powers it while plugged in. | +| **Barrel jack (J4)** | Charges the on-board supercapacitor (the DotBot's "battery"). | **USB-C (J2) - flashing.** The DotBot v3 has an **on-board programmer** behind the USB-C port: a J-Link-OB / DAPLink debug chip plus an SWD mux that routes the @@ -35,7 +35,7 @@ debug lines to the nRF5340. **You do not need a separate J-Link** for normal flashing - just a USB-C cable. Plug it in and flash: ```bash -# cabled flash of one bot (board defaults to dotbot-v3) +# cabled flash of one DotBot (board defaults to dotbot-v3) dotbot device flash dotbot -s 77 ``` @@ -45,11 +45,11 @@ See [device](../cli/device.md) for the full flashing workflow. **Barrel jack (J4) - charging.** The barrel jack feeds the BQ24640 charger, which tops up the on-board supercapacitor (a ~240 F stack at 3.0 V max). The -supercap is what runs the bot when it's untethered; expect short, fast charges +supercap is what runs the DotBot when it's untethered; expect short, fast charges rather than a slow battery cycle. ```{note} -The bot is powered whenever USB-C is connected, so you can flash and bench-test +The DotBot is powered whenever USB-C is connected, so you can flash and bench-test without charging first. For free-roaming, charge via the barrel jack. ``` @@ -76,7 +76,7 @@ start `10` (the `-s` prefix selects which probe to talk to). See For position tracking, the testbed uses **Valve Lighthouse 2** base stations. Each DotBot v3 carries an LH2 sensor shield (a TS4231 light-to-digital receiver with a photodiode) that decodes the base station's sweeping IR beams into a -position. One base station illuminates the arena; the bots compute where they +position. One base station illuminates the arena; the DotBots compute where they are from what they see. Once the optical setup is in place, calibrate it before relying on the diff --git a/doc/index.md b/doc/index.md index fdcce713..1bea851c 100644 --- a/doc/index.md +++ b/doc/index.md @@ -12,14 +12,14 @@ Reference :class: tip New here? DotBots are small wheeled robots you drive from your browser or your -own code - one bot, or a swarm of hundreds. Pick a starting point: +own code - one DotBot, or a swarm of hundreds. Pick a starting point: -- **Try it with no hardware** - the simulator runs the full web UI with no bot - or gateway needed: `dotbot run simulator -w`. See the +- **Try it with no hardware** - the simulator runs the full web UI with no + DotBot or gateway needed: `dotbot run simulator`. See the [simulator guide](guides/simulator.md). -- **Get one bot moving** - build and cable-flash a single DotBot and gateway, +- **Get one DotBot moving** - build and cable-flash a single DotBot and gateway, then drive it from the browser. See the [one-bot guide](guides/one-bot.md). -- **Run a swarm experiment** - provision and command many bots over the air. +- **Run a swarm experiment** - provision and command many DotBots over the air. The swarm quickstart below is the main path; then see [`swarm`](cli/swarm.md) and [LH2 localization](guides/lh2-calibration.md). - **Script it / collect data** - drive the swarm from your own code today over @@ -30,8 +30,9 @@ own code - one bot, or a swarm of hundreds. Pick a starting point: [CLI reference](cli/index.md); the firmware flows live under [`fw`](cli/fw.md) and [`device`](cli/device.md). - **Before flashing hardware** - the simulator and driving an existing swarm - need only Python; building and cable-flashing firmware also need SES and the - nRF Command Line Tools (`nrfjprog`). See **Prerequisites** below before you start. + need only Python; building and cable-flashing firmware also need SEGGER + Embedded Studio and the nRF Command Line Tools (`nrfjprog`). See + **Prerequisites** below before you start. ``` ```{include} ../README.md diff --git a/doc/reference/configuration.md b/doc/reference/configuration.md index ead8a3ea..830e0506 100644 --- a/doc/reference/configuration.md +++ b/doc/reference/configuration.md @@ -38,8 +38,8 @@ directory. Discovery looks only at the cwd - it does not walk up to parent directories, so the active config is always unambiguous. `~/.dotbot/config.toml` (4) is the per-machine fallback for settings you set -once and want everywhere - typically `[fw].segger_dir`, since the SES install -path rarely changes. Per-project settings like `[fw].firmware_repo` belong in +once and want everywhere - typically `[fw].segger_dir`, since the SEGGER +Embedded Studio install path rarely changes. Per-project settings like `[fw].firmware_repo` belong in the project's `./dotbot.toml` instead. Every command, including `dotbot fw`, reads through this same resolver. @@ -126,7 +126,7 @@ The four tables mirror the four CLI namespaces (`fw` / `device` / `swarm` / | `[run.controller] background_map` | Background map image. | | `[run.controller] log_output` | Log output path. | | `[run.controller] csv_data_output` | CSV data output path. | -| `[run.controller] webbrowser` | Open the web UI on start. | +| `[run.controller] webbrowser` | Open the web UI on start (default true; set false to stay headless). | | `[run.controller] gw_address` | Gateway address. | | `[run.controller] simulator_init_state` | Initial simulator state. | | `[run.gateway] serial_port` | Gateway serial port. | @@ -138,8 +138,8 @@ than being silently ignored. ## What a deployment is A **deployment** here means one physical deployment - one set of real DotBots -behind one broker, in one place (e.g. the ~100-bot setup at Inria Paris, or a -1000-bot campaign). You define each one as a `[deployment.]` table and +behind one broker, in one place (e.g. the ~100-DotBot setup at Inria Paris, or a +1000-DotBot campaign). You define each one as a `[deployment.]` table and **select** it; you do not edit the file to switch between them. Select the active deployment with, in precedence order, `--deployment NAME`, the @@ -148,7 +148,7 @@ deployment's keys slot into the file layer (above top-level, below sections), so explicit flag or env var still overrides it. Selecting a name with no matching `[deployment.]` table is an error that lists the defined deployments. -A deployment is **not** the simulator. To drive simulated bots, set the connection +A deployment is **not** the simulator. To drive simulated DotBots, set the connection to `simulator` (`--conn simulator`, or `conn = "simulator"`); that is a connection kind, not a deployment. @@ -161,7 +161,7 @@ metadata: | `swarm_id` | Swarm id for this deployment. | | `serial_port` | Default serial port for this deployment. | | `location` | Descriptive label (shown by `dotbot deployment list`). | -| `bots` | Descriptive bot count. | +| `bots` | Descriptive DotBot count. | ## Managing deployments @@ -258,7 +258,7 @@ conn = "mqtts://broker.local:8883" [run.controller] http_port = 8000 -webbrowser = true +webbrowser = false # default is true; set false to stay headless # background_map = "./map.png" [run.gateway] diff --git a/doc/reference/mqtt.md b/doc/reference/mqtt.md index 1e644505..9d125902 100644 --- a/doc/reference/mqtt.md +++ b/doc/reference/mqtt.md @@ -1,7 +1,7 @@ # MQTT Talk to the swarm from any language that speaks MQTT - no Python, no SDK. This -is the low-magic integration path: subscribe to bot state, publish commands, on +is the low-magic integration path: subscribe to DotBot state, publish commands, on standard topics. For Python, the [REST API](rest.md) is usually simpler. ## How it works @@ -30,23 +30,23 @@ base64 string derived from the current PIN code (see [Secured brokers](#secured- | Topic (under `/pydotbot/`) | Direction | Purpose | |---|---|---| -| `/command//
//` | you publish | drive a bot (`move_raw`, `rgb_led`, `waypoints`, `clear_position_history`) | +| `/command//
//` | you publish | drive a DotBot (`move_raw`, `rgb_led`, `waypoints`, `clear_position_history`) | | `/notify` | you subscribe | controller state changes + position updates | -| `/request` / `/reply/` | request/reply | one-shot queries (e.g. list of bots, map size) | +| `/request` / `/reply/` | request/reply | one-shot queries (e.g. list of DotBots, map size) | Command-topic fields: -- `` - 4-hex swarm identifier (bots behind one gateway), e.g. `0000`. +- `` - 4-hex swarm identifier (DotBots behind one gateway), e.g. `0000`. - `
` - 16-hex DotBot address, e.g. `9903ef26257feb31`. - `` - application type: `0` = DotBot, `1` = SailBot. - `` - the command name (last segment). -Get a bot's address and the swarm id from the controller's +Get a DotBot's address and the swarm id from the controller's [REST API](rest.md) (`GET /controller/dotbots`). ## Send commands -Payloads are JSON. Drive a bot forward and turn its LED red: +Payloads are JSON. Drive a DotBot forward and turn its LED red: ```bash # move_raw - left_y / right_y drive the wheels, values in [-100, 100] @@ -66,8 +66,8 @@ mosquitto_pub -h \ mosquitto_sub -h -t '/pydotbot//notify' | jq ``` -Notifications carry a `cmd` field: `RELOAD` (refetch all bots), `UPDATE` -(per-bot state delta, incl. LH2 position), `PIN_CODE_UPDATE` (the secret topic +Notifications carry a `cmd` field: `RELOAD` (refetch all DotBots), `UPDATE` +(per-DotBot state delta, incl. LH2 position), `PIN_CODE_UPDATE` (the secret topic and key are about to rotate - see below). ## Secured brokers diff --git a/doc/reference/troubleshooting.md b/doc/reference/troubleshooting.md index 7f8be403..e903a4f7 100644 --- a/doc/reference/troubleshooting.md +++ b/doc/reference/troubleshooting.md @@ -26,7 +26,7 @@ plain source checkout, so a git clone (or a wheel-less install) has no `dotbot/frontend/build/`. The controller and its REST/WebSocket API still run - only the browser UI is unavailable. Fixes: -- **From PyPI** - install the wheel, which bundles the UI: `pip install --pre pydotbot`. +- **From PyPI** - install the wheel, which bundles the UI: `pip install pydotbot`. - **From a git checkout** - build the UI once: `cd dotbot/frontend && npm install && npm run build`. On a version without this check, the same cause surfaces as a startup crash, diff --git a/doc/sdk/index.md b/doc/sdk/index.md index e6e2315a..47eec772 100644 --- a/doc/sdk/index.md +++ b/doc/sdk/index.md @@ -9,7 +9,7 @@ intend to build. The imports (`from dotbot import Swarm`) and every method (`Swarm.connect`, `Swarm.run`, `bot.move_to`, ...) are **aspirational**. To script the swarm **today**, use the CLI: start a controller with -[`dotbot run controller`](../cli/run.md), then drive bots over its +[`dotbot run controller`](../cli/run.md), then drive DotBots over its [REST](../reference/rest.md) / [WebSocket](../reference/rest.md) surface (or the [MQTT bridge](../reference/mqtt.md)). ``` @@ -19,7 +19,7 @@ To script the swarm **today**, use the CLI: start a controller with The SDK will be a thin Python wrapper over a running controller's REST/WS surface, so you write swarm logic in Python instead of hand-rolling HTTP and asyncio. You start a controller once (`dotbot run controller`), then a script -connects to it and commands bots. The same script targets real hardware, the +connects to it and commands DotBots. The same script targets real hardware, the simulator, or a remote testbed - the backend is chosen at run time, not in the code. @@ -27,8 +27,8 @@ code. All three snippets are **aspirational** - they will not run until the SDK ships. -**Connect and drive one bot** - connect to a local controller, grab a bot, set -its color, move it: +**Connect and drive one DotBot** - connect to a local controller, grab a DotBot, +set its color, move it: ```python from dotbot import Swarm From 3bc55650ae776bbfd5bab7816327e8fa2e3336cc Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:00:28 +0200 Subject: [PATCH 04/20] readme: use "DotBot", drop pip install --pre AI-assisted: Claude Opus 4.7 --- README.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 35f7558b..09bf8db4 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ The control plane for the [DotBot](http://www.dotbots.org) - a small wireless wheeled robot built to operate in large swarms, for research and education. -PyDotBot allows you to flash a robot and control a whole fleet over the air, -from one bot to a thousand. +PyDotBot allows you to flash a DotBot and control a whole fleet over the air, +from one DotBot to a thousand. [▶️ Click to see a DotBot swarm in action](https://www.youtube.com/watch?v=pXGTLqafReU) @@ -25,8 +25,8 @@ from one bot to a thousand. **What you can do** -- 🕹️ Drive one bot or a whole fleet from a **web UI** (live map + joystick) or your own **Python** code -- 📡 Flash the swarm **over the air** - one command, hundreds of bots at once +- 🕹️ Drive one DotBot or a whole fleet from a **web UI** (live map + joystick) or your own **Python** code +- 📡 Flash the swarm **over the air** - one command, hundreds of DotBots at once - 🛰️ Get real-world **(x, y) positions** with Lighthouse 2 localization - 🧪 Try it all with **zero hardware** using the built-in simulator - 🛠️ One `dotbot` CLI takes you from build → flash → run @@ -36,7 +36,7 @@ from one bot to a thousand. PyDotBot is available on [PyPi](https://pypi.org/project/pydotbot/), install it with: ```bash -pip install --pre pydotbot +pip install pydotbot ``` Then, check your installation with `dotbot --version` and learn what's possible with `dotbot --help`. @@ -50,10 +50,11 @@ See the whole thing run with nothing but Python! The command below will run a simulated swarm, which you can observe in a web UI at http://localhost:8000/PyDotBot/ : ```bash -dotbot run simulator -w +dotbot run simulator ``` -Drive the simulated bots from the UI, or run a bundled demo in a +The web UI opens automatically; pass `--headless` to suppress it (it's still +served). Drive the simulated DotBots from the UI, or run a bundled demo in a second terminal: ```bash @@ -132,7 +133,7 @@ dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-1.22.0rc1/dotbot-sandbox- Observe and control your swarm from a web interface: ```bash -dotbot run controller -w # will open a webpage at http://localhost:8000/PyDotBot/ +dotbot run controller # opens a webpage at http://localhost:8000/PyDotBot/ ``` Full walkthrough of fleet operations - status, OTA flash, start/stop, monitor - @@ -140,23 +141,23 @@ is in the [`swarm` reference][swarm-doc]. ### Calibrate positions (optional) -Give the bots real-world `(x, y)` with Lighthouse 2 - capture once from any bot -over the air, then push the result to the whole fleet (needs the `[calibrate]` -extra, below): +Give the DotBots real-world `(x, y)` with Lighthouse 2 - capture once from any +DotBot over the air, then push the result to the whole fleet (needs the +`[calibrate]` extra, below): ```bash -dotbot swarm stop # bots must be idle to capture +dotbot swarm stop # DotBots must be idle to capture dotbot swarm lh2-calibration collect --device -d 500 # capture + solve + save -dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml # apply to every bot +dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml # apply to every DotBot ``` -`-d` is your reference square's side, in mm (one bot's capture calibrates the +`-d` is your reference square's side, in mm (one DotBot's capture calibrates the whole arena). Full walkthrough - arena sizing and the cabled alternative - is in the [LH2 calibration guide][lh2-doc]. ## Going further -- **Drive a single bot** end to end - build, flash, and control one DotBot: +- **Drive a single DotBot** end to end - build, flash, and control one DotBot: the [one-bot guide][one-bot-doc]. - **Position tracking with Lighthouse 2** - give the fleet real-world `(x, y)`, calibrated over the air: the [LH2 calibration guide][lh2-doc] (a cabled @@ -178,7 +179,7 @@ the [LH2 calibration guide][lh2-doc]. Most of `dotbot` is in the base install; only LH2 calibration needs an extra: ```bash -pip install --pre 'pydotbot[calibrate]' # opencv (the LH2 homography solve) +pip install 'pydotbot[calibrate]' # opencv (the LH2 homography solve) ``` Hitting a snag (e.g. the web UI not loading in Firefox)? See From b8b9beff2ee30e88e453726e95520aeef4796af5 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:26:53 +0200 Subject: [PATCH 05/20] dotbot/controller: replace the webbrowser setting with headless Breaking: the `[run.controller] webbrowser` config key (and the legacy --config-path `webbrowser` key) becomes `headless` with inverted sense - default false opens the browser; set `headless = true` to suppress it. AI-assisted: Claude Opus 4.7 --- dotbot/config.py | 2 +- dotbot/controller.py | 4 ++-- dotbot/controller_app.py | 2 +- dotbot/tests/test_config.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dotbot/config.py b/dotbot/config.py index 250d048a..ea9fbc83 100644 --- a/dotbot/config.py +++ b/dotbot/config.py @@ -142,7 +142,7 @@ class ControllerSection(_Strict): background_map: str | None = None log_output: str | None = None csv_data_output: str | None = None - webbrowser: bool | None = None + headless: bool | None = None gw_address: str | None = None simulator_init_state: str | None = None diff --git a/dotbot/controller.py b/dotbot/controller.py index fa434296..043862d2 100644 --- a/dotbot/controller.py +++ b/dotbot/controller.py @@ -129,7 +129,7 @@ class ControllerSettings: controller_http_port: int = CONTROLLER_HTTP_PORT_DEFAULT map_size: str = MAP_SIZE_DEFAULT background_map: str = "" - webbrowser: bool = True + headless: bool = False verbose: bool = False log_level: str = "info" log_output: str = os.path.join(os.getcwd(), "pydotbot.log") @@ -269,7 +269,7 @@ async def _open_webbrowser(self): break url = f"http://localhost:{self.settings.controller_http_port}/PyDotBot" self.logger.debug("Using frontend URL", url=url) - if self.settings.webbrowser is True: + if not self.settings.headless: self.logger.info("Opening webbrowser", url=url) webbrowser.open(url) diff --git a/dotbot/controller_app.py b/dotbot/controller_app.py index 6b9bee9a..117f8377 100644 --- a/dotbot/controller_app.py +++ b/dotbot/controller_app.py @@ -296,7 +296,7 @@ def main( "map_size": map_size, "background_map": background_map, "simulator_init_state": simulator_init_state, - "webbrowser": False if headless else None, + "headless": True if headless else None, "verbose": verbose, "log_level": log_level, "log_output": log_output, diff --git a/dotbot/tests/test_config.py b/dotbot/tests/test_config.py index da23f020..b3e88df7 100644 --- a/dotbot/tests/test_config.py +++ b/dotbot/tests/test_config.py @@ -296,9 +296,9 @@ def test_resolve_env_coercion_int(): def test_resolve_env_coercion_bool(): got = cfg.resolve( - "webbrowser", + "headless", section="run", - environ={"DOTBOT_RUN_WEBBROWSER": "true"}, + environ={"DOTBOT_RUN_HEADLESS": "true"}, default=False, ) assert got is True From 753d12efe1a47949d694c24e065c7a7ef3d32b3c Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:27:01 +0200 Subject: [PATCH 06/20] doc: rename the controller webbrowser config to headless AI-assisted: Claude Opus 4.7 --- doc/cli/swarm.md | 2 +- doc/reference/configuration.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/cli/swarm.md b/doc/cli/swarm.md index 2394fc99..6acc1da6 100644 --- a/doc/cli/swarm.md +++ b/doc/cli/swarm.md @@ -124,7 +124,7 @@ picked by file extension. Get the `--device` address from `dotbot swarm status`. | Command | What it serves | Default port | |---|---|---| -| `dotbot run controller -w` | drive/visualize Web UI + REST/WS | `8000` | +| `dotbot run controller` | drive/visualize Web UI + REST/WS | `8000` | | `dotbot swarm serve` | SwarmIT FastAPI orchestration backend | `8001` | `dotbot swarm` auto-discovers a running `serve` daemon; pass `--no-server` to diff --git a/doc/reference/configuration.md b/doc/reference/configuration.md index 830e0506..2457df3e 100644 --- a/doc/reference/configuration.md +++ b/doc/reference/configuration.md @@ -126,7 +126,7 @@ The four tables mirror the four CLI namespaces (`fw` / `device` / `swarm` / | `[run.controller] background_map` | Background map image. | | `[run.controller] log_output` | Log output path. | | `[run.controller] csv_data_output` | CSV data output path. | -| `[run.controller] webbrowser` | Open the web UI on start (default true; set false to stay headless). | +| `[run.controller] headless` | Stay headless - don't open the web UI in a browser on start (default false; it's still served). | | `[run.controller] gw_address` | Gateway address. | | `[run.controller] simulator_init_state` | Initial simulator state. | | `[run.gateway] serial_port` | Gateway serial port. | @@ -258,7 +258,7 @@ conn = "mqtts://broker.local:8883" [run.controller] http_port = 8000 -webbrowser = false # default is true; set false to stay headless +headless = true # default is false; set true to suppress the browser (still served) # background_map = "./map.png" [run.gateway] From 977183316df95b31e19d39f534f53fea39da99a1 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:18:14 +0200 Subject: [PATCH 07/20] readme: lead calibration with the install + device-addr hint AI-assisted: Claude Opus 4.7 --- README.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 09bf8db4..8277b052 100644 --- a/README.md +++ b/README.md @@ -142,18 +142,26 @@ is in the [`swarm` reference][swarm-doc]. ### Calibrate positions (optional) Give the DotBots real-world `(x, y)` with Lighthouse 2 - capture once from any -DotBot over the air, then push the result to the whole fleet (needs the -`[calibrate]` extra, below): +DotBot over the air, then push the result to the whole fleet. This needs the +`[calibrate]` extra (opencv, for the homography solve): ```bash -dotbot swarm stop # DotBots must be idle to capture +pip install 'pydotbot[calibrate]' +``` + +Then capture and push: + +```bash +dotbot swarm status # find the Device Addr to capture from +dotbot swarm stop # DotBots must be idle to capture dotbot swarm lh2-calibration collect --device -d 500 # capture + solve + save dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml # apply to every DotBot ``` -`-d` is your reference square's side, in mm (one DotBot's capture calibrates the -whole arena). Full walkthrough - arena sizing and the cabled alternative - is in -the [LH2 calibration guide][lh2-doc]. +`` is a DotBot's link-layer address - copy it from the `dotbot swarm +status` **Device Addr** column. `-d` is your reference square's side, in mm (one +DotBot's capture calibrates the whole arena). Full walkthrough - arena sizing +and the cabled alternative - is in the [LH2 calibration guide][lh2-doc]. ## Going further @@ -176,12 +184,6 @@ the [LH2 calibration guide][lh2-doc]. + `config`), the REST/WS and MQTT surfaces, and hardware notes: the [documentation][doc-link]. -Most of `dotbot` is in the base install; only LH2 calibration needs an extra: - -```bash -pip install 'pydotbot[calibrate]' # opencv (the LH2 homography solve) -``` - Hitting a snag (e.g. the web UI not loading in Firefox)? See [Troubleshooting][troubleshooting-doc]. From e0d77c23962801877ef17b79d063782d8327cea2 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:31:58 +0200 Subject: [PATCH 08/20] dotbot/cli: name the searched config locations when none is found AI-assisted: Claude Opus 4.7 --- dotbot/cli/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dotbot/cli/main.py b/dotbot/cli/main.py index a249e14d..b5f033b2 100644 --- a/dotbot/cli/main.py +++ b/dotbot/cli/main.py @@ -112,6 +112,8 @@ def cli(ctx, config_path, deployment_name): keys (`segger_dir`, `firmware_repo`, ...) through this same resolver. """ from dotbot.config import ( + PROJECT_CONFIG_NAME, + USER_CONFIG_PATH, ConfigError, discover_config_path, load_config, @@ -131,7 +133,11 @@ def cli(ctx, config_path, deployment_name): if path is not None: click.echo(f"using config file at {path}", err=True) else: - click.echo("no config file found; using built-in defaults", err=True) + click.echo( + f"no config file found (looked for ./{PROJECT_CONFIG_NAME} and " + f"{USER_CONFIG_PATH}); using built-in defaults", + err=True, + ) ctx.obj["config"] = config ctx.obj["config_path"] = path From 9673e342a9bc2c27cf68a0336c14c909ecda291c Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 09:32:06 +0200 Subject: [PATCH 09/20] dotbot/cli: clarify the missing-swarm-id error (-s is not the swarm id) AI-assisted: Claude Opus 4.7 --- dotbot/cli/device.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dotbot/cli/device.py b/dotbot/cli/device.py index 68b45da9..6ed1b456 100644 --- a/dotbot/cli/device.py +++ b/dotbot/cli/device.py @@ -142,8 +142,10 @@ def flash_swarmit_sandbox( swarm_id = from_config(ctx, "swarm_id", "swarm_id", None) if swarm_id is None: raise click.ClickException( - "no swarm id: pass --swarm-id, or set swarm_id (or a " - "deployment) in your config." + "no swarm id. Pass --swarm-id (a 16-bit hex value, e.g. " + "--swarm-id 0100), or set swarm_id (or a deployment) in your " + "config. Note: -s / --sn-starting-digits is the J-Link serial " + "prefix, not the swarm id." ) if fw_version is None: fw_version = pinned_version("swarmit") @@ -184,8 +186,10 @@ def flash_mari_gateway(ctx, swarm_id, fw_version, sn_starting_digits): swarm_id = from_config(ctx, "swarm_id", "swarm_id", None) if swarm_id is None: raise click.ClickException( - "no swarm id: pass --swarm-id, or set swarm_id (or a " - "deployment) in your config." + "no swarm id. Pass --swarm-id (a 16-bit hex value, e.g. " + "--swarm-id 0100), or set swarm_id (or a deployment) in your " + "config. Note: -s / --sn-starting-digits is the J-Link serial " + "prefix, not the swarm id." ) if fw_version is None: fw_version = pinned_version("swarmit") From 8f6857e8188c368e8cf00796067d45d936ca6a89 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 10:24:24 +0200 Subject: [PATCH 10/20] readme: clarify swarm config note and split calibration collect/push AI-assisted: Claude Opus 4.7 --- README.md | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8277b052..b4c7285c 100644 --- a/README.md +++ b/README.md @@ -86,10 +86,13 @@ To operate as a swarm, set your swarm connection config: dotbot config init --conn mqtts://argus.paris.inria.fr:8883 --swarm-id 1234 ``` -> `argus.paris.inria.fr` is our Inria Paris broker and `1234` our swarm - pass -> your own `--conn` and `--swarm-id` (your testbed admin provides these). This -> writes `./dotbot.toml`; commands run from this directory pick it up, so you -> don't repeat the flags. Full schema: the [configuration reference][config-doc]. +> `--conn` is your MQTT broker and `--swarm-id` a 16-bit hex id that identifies +> your swarm. Running your own handful of DotBots? Pick any swarm +> id - the example points `--conn` at our Inria Paris broker so it works out of +> the box, but swap in your own broker once you have one. (On a shared testbed, +> your admin gives you the broker and swarm id to use.) This writes +> `./dotbot.toml`; commands run from this directory pick it up, so you don't +> repeat the flags. Full schema: the [configuration reference][config-doc]. The swarm mode also requires a special "sandbox" firmware in each dotbot. We also need a more powerful gateway firmware. Let's flash both - the network @@ -141,27 +144,33 @@ is in the [`swarm` reference][swarm-doc]. ### Calibrate positions (optional) -Give the DotBots real-world `(x, y)` with Lighthouse 2 - capture once from any -DotBot over the air, then push the result to the whole fleet. This needs the -`[calibrate]` extra (opencv, for the homography solve): +Give the DotBots real-world `(x, y)` with Lighthouse 2. It's a two-step flow: +**collect** a calibration from one DotBot over the air, then **push** it to the +whole fleet - a single DotBot's capture calibrates the shared arena. This needs +the `[calibrate]` extra (opencv, for the homography solve): ```bash pip install 'pydotbot[calibrate]' ``` -Then capture and push: +First, collect from one DotBot. Get its address from `dotbot swarm status` (the +**Device Addr** column): ```bash -dotbot swarm status # find the Device Addr to capture from +dotbot swarm status # pick one Device Addr dotbot swarm stop # DotBots must be idle to capture dotbot swarm lh2-calibration collect --device -d 500 # capture + solve + save -dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml # apply to every DotBot ``` -`` is a DotBot's link-layer address - copy it from the `dotbot swarm -status` **Device Addr** column. `-d` is your reference square's side, in mm (one -DotBot's capture calibrates the whole arena). Full walkthrough - arena sizing -and the cabled alternative - is in the [LH2 calibration guide][lh2-doc]. +`-d` is your reference square's side, in mm. This saves a +`~/.dotbot/calibration-.toml`. Then push that file to the whole fleet: + +```bash +dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml +``` + +Full walkthrough - arena sizing and the cabled alternative - is in the +[LH2 calibration guide][lh2-doc]. ## Going further From 225eca84d893947b4f01f81994be8446c350eb48 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 11:20:29 +0200 Subject: [PATCH 11/20] dotbot/cli: skip the config-discovery echo for the config group AI-assisted: Claude Opus 4.7 --- dotbot/cli/main.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/dotbot/cli/main.py b/dotbot/cli/main.py index b5f033b2..c80c37aa 100644 --- a/dotbot/cli/main.py +++ b/dotbot/cli/main.py @@ -130,14 +130,18 @@ def cli(ctx, config_path, deployment_name): except ConfigError as exc: raise click.ClickException(str(exc)) from exc - if path is not None: - click.echo(f"using config file at {path}", err=True) - else: - click.echo( - f"no config file found (looked for ./{PROJECT_CONFIG_NAME} and " - f"{USER_CONFIG_PATH}); using built-in defaults", - err=True, - ) + # The `config` group inspects config state itself (show/path) or scaffolds + # it (init), so the root-level "which config is in effect" echo is redundant + # there - and reads as a contradiction right before `config init` writes one. + if ctx.invoked_subcommand != "config": + if path is not None: + click.echo(f"using config file at {path}", err=True) + else: + click.echo( + f"no config file found (looked for ./{PROJECT_CONFIG_NAME} and " + f"{USER_CONFIG_PATH}); using built-in defaults", + err=True, + ) ctx.obj["config"] = config ctx.obj["config_path"] = path From d291d83c6337c93b08dca73877b551a88fe54023 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 11:20:29 +0200 Subject: [PATCH 12/20] dotbot/calibration: save TOML records under ~/.dotbot/calibrations/ AI-assisted: Claude Opus 4.7 --- dotbot/calibration/lighthouse2.py | 21 +++++++++++++++----- dotbot/cli/swarm_lh2.py | 3 ++- dotbot/tests/test_calibration_lighthouse2.py | 8 ++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/dotbot/calibration/lighthouse2.py b/dotbot/calibration/lighthouse2.py index f181c5d2..8f6da5ba 100644 --- a/dotbot/calibration/lighthouse2.py +++ b/dotbot/calibration/lighthouse2.py @@ -34,6 +34,10 @@ # learn to read the new TOML format. Once they do, drop the .out write. CALIBRATION_LEGACY_OUT = "calibration.out" CALIBRATION_TOML_GLOB = "calibration-*.toml" +# Timestamped TOML records live in this subdirectory of CALIBRATION_DIR so they +# don't clutter ~/.dotbot itself. The loader still reads legacy files saved +# directly in CALIBRATION_DIR (see load_calibration). +CALIBRATION_TOML_SUBDIR = "calibrations" REFERENCE_POINTS_DEFAULT = [ [0.4, 0.4], # Top-left [0.6, 0.4], # Top-right @@ -242,8 +246,8 @@ def __init__( extra_lh_num: int = 0, ): Path.mkdir(CALIBRATION_DIR, exist_ok=True) - # Legacy path, kept for back-compat with external consumers. - # The primary record is now timestamped TOML files in CALIBRATION_DIR. + # Legacy path, kept for back-compat with external consumers. The primary + # record is now timestamped TOML files under CALIBRATION_DIR/calibrations. self.calibration_output_path = CALIBRATION_DIR / CALIBRATION_LEGACY_OUT self.calibration_distance = calibration_distance self.extra_lh_num = extra_lh_num @@ -364,10 +368,15 @@ def load_calibration(self) -> list[bytes]: Prefers the newest timestamped `calibration-*.toml`; falls back to the legacy binary `calibration.out` if no TOML files exist - (so setups predating the format change keep working). + (so setups predating the format change keep working). Looks in + both `CALIBRATION_DIR/calibrations` (new) and `CALIBRATION_DIR` + itself (where older files were saved flat). """ toml_files = sorted( - CALIBRATION_DIR.glob(CALIBRATION_TOML_GLOB), + [ + *(CALIBRATION_DIR / CALIBRATION_TOML_SUBDIR).glob(CALIBRATION_TOML_GLOB), + *CALIBRATION_DIR.glob(CALIBRATION_TOML_GLOB), + ], key=lambda p: p.stat().st_mtime, reverse=True, ) @@ -407,7 +416,9 @@ def save_calibration(self, tag: Optional[str] = None) -> Path: if slug else f"calibration-{ts_for_filename}" ) - toml_path = CALIBRATION_DIR / f"{stem}.toml" + toml_dir = CALIBRATION_DIR / CALIBRATION_TOML_SUBDIR + toml_dir.mkdir(parents=True, exist_ok=True) + toml_path = toml_dir / f"{stem}.toml" tag_line = f'tag = "{slug}"\n' if slug else "" # Explicit UTF-8 — TOML is spec'd as UTF-8, and Path.write_text # defaults to the platform encoding (cp1252 on Windows), which diff --git a/dotbot/cli/swarm_lh2.py b/dotbot/cli/swarm_lh2.py index 39356dc4..dd6d6c77 100644 --- a/dotbot/cli/swarm_lh2.py +++ b/dotbot/cli/swarm_lh2.py @@ -9,7 +9,8 @@ - `collect` - walk one DotBot through the 4 arena corners, trigger a raw-count capture per corner over the air, solve the - homography, and save the calibration under ~/.dotbot/. + homography, and save the calibration under + ~/.dotbot/calibrations/. - `push ` - send a saved calibration to the DotBot over the air. A thin forward to swarmit's `calibrate-lh2`, which picks the payload format (legacy `.out` or `calibration-*.toml`) by extension. diff --git a/dotbot/tests/test_calibration_lighthouse2.py b/dotbot/tests/test_calibration_lighthouse2.py index d069ffbd..ff238c84 100644 --- a/dotbot/tests/test_calibration_lighthouse2.py +++ b/dotbot/tests/test_calibration_lighthouse2.py @@ -42,7 +42,7 @@ def test_save_calibration_writes_toml_and_legacy_out(monkeypatch, tmp_path): mgr.save_calibration() - toml_files = list(tmp_path.glob("calibration-*.toml")) + toml_files = list((tmp_path / "calibrations").glob("calibration-*.toml")) assert len(toml_files) == 1, f"expected exactly one TOML file, got {toml_files}" assert ( tmp_path / "calibration.out" @@ -82,9 +82,9 @@ def test_save_calibration_sanitizes_and_omits_empty_tag(monkeypatch, tmp_path): mgr.homographies = [_seed_homography(1.0)] # Unsafe characters collapse to dashes and the leading ".." is trimmed; - # the slug stays a single filename component inside ~/.dotbot. + # the slug stays a single filename component inside ~/.dotbot/calibrations. path = mgr.save_calibration(tag="../lab room/A") - assert path.parent == tmp_path + assert path.parent == tmp_path / "calibrations" assert path.name.startswith("calibration-lab-room-A-") # A tag that reduces to nothing is treated as absent (no stray dashes). @@ -110,7 +110,7 @@ def test_load_calibration_prefers_newest_toml(monkeypatch, tmp_path): mgr.homographies = [_seed_homography(3.0)] mgr.save_calibration() - first = list(tmp_path.glob("calibration-*.toml"))[0] + first = list((tmp_path / "calibrations").glob("calibration-*.toml"))[0] first.stat() # touch to avoid mtime tie import os import time From d4b5f0c86980044a530ce671a354dc96b4312b7b Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 11:20:30 +0200 Subject: [PATCH 13/20] readme: say "DotBot" consistently, point calibration at the new subdir AI-assisted: Claude Opus 4.7 --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b4c7285c..b2766785 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ served). Drive the simulated DotBots from the UI, or run a bundled demo in a second terminal: ```bash -dotbot run demo circle # drive one bot in a circle (the simplest demo) +dotbot run demo circle # drive one DotBot in a circle (the simplest demo) ``` Learn how to script the swarm from your own code, run the richer examples, and more - all with @@ -94,14 +94,14 @@ dotbot config init --conn mqtts://argus.paris.inria.fr:8883 --swarm-id 1234 > `./dotbot.toml`; commands run from this directory pick it up, so you don't > repeat the flags. Full schema: the [configuration reference][config-doc]. -The swarm mode also requires a special "sandbox" firmware in each dotbot. +The swarm mode also requires a special "sandbox" firmware in each DotBot. We also need a more powerful gateway firmware. Let's flash both - the network id comes from your config: ```bash dotbot fw fetch # pull the pinned pre-compiled firmwares (swarmit + dotbot-firmware) dotbot device flash-mari-gateway -s 10 # flash the gateway -dotbot device flash-swarmit-sandbox -s 77 # the sandbox firmware - do this on each dotbot +dotbot device flash-swarmit-sandbox -s 77 # the sandbox firmware - do this on each DotBot ``` (`device flash-mari-gateway` / `flash-swarmit-sandbox` auto-fetch @@ -115,10 +115,10 @@ dotbot run gateway -p /dev/cu.usbmodem0010500324491 ### Deploy and control -You can flash as many dotbots as you want, all at once! First, how about making them spinnnn 🔄 🔄 +You can flash as many DotBots as you want, all at once! First, how about making them spinnnn 🔄 🔄 ```bash -dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-1.22.0rc1/spin-sandbox-dotbot-v3.bin -ys # flash the whole fleet with a simple spinning app +dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-1.22.0/spin-sandbox-dotbot-v3.bin -ys # flash the whole fleet with a simple spinning app ``` (`dotbot swarm` reads the same `dotbot.toml` as the rest - pass `--conn` / @@ -129,8 +129,8 @@ and versions on your machine.) Then, flash another experiment: ```bash -dotbot swarm stop # ensure all robots are in bootloader -dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-1.22.0rc1/dotbot-sandbox-dotbot-v3.bin -ys # this firmware allows bots to be remote-controlled +dotbot swarm stop # ensure all DotBots are in bootloader +dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-1.22.0/dotbot-sandbox-dotbot-v3.bin -ys # this firmware lets DotBots be remote-controlled ``` Observe and control your swarm from a web interface: @@ -163,10 +163,11 @@ dotbot swarm lh2-calibration collect --device -d 500 # capture + solve ``` `-d` is your reference square's side, in mm. This saves a -`~/.dotbot/calibration-.toml`. Then push that file to the whole fleet: +`~/.dotbot/calibrations/calibration-.toml`. Then push that file to the +whole fleet: ```bash -dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml +dotbot swarm lh2-calibration push ~/.dotbot/calibrations/calibration-.toml ``` Full walkthrough - arena sizing and the cabled alternative - is in the From 592652181c6bca0aaa81eeaff3b4076bba76872f Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 11:37:37 +0200 Subject: [PATCH 14/20] dotbot/cli: rename device --sn-starting-digits to --probe The CLI flag and the [device] config key become --probe / probe; the firmware engine keeps sn_starting_digits internally (it accurately names a J-Link serial-number prefix), so the rename should not propagate into flash.py / nrf.py. AI-assisted: Claude Opus 4.7 --- README.md | 4 +-- dotbot/cli/device.py | 53 ++++++++++++++++++------------------- dotbot/config.py | 2 +- dotbot/firmware/flash.py | 9 ++++--- dotbot/firmware/nrf.py | 5 +++- dotbot/tests/test_device.py | 27 +++++++++++++++---- 6 files changed, 61 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index b2766785..e744d3fd 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,8 @@ id comes from your config: ```bash dotbot fw fetch # pull the pinned pre-compiled firmwares (swarmit + dotbot-firmware) -dotbot device flash-mari-gateway -s 10 # flash the gateway -dotbot device flash-swarmit-sandbox -s 77 # the sandbox firmware - do this on each DotBot +dotbot device flash-mari-gateway --probe 10 # flash the gateway +dotbot device flash-swarmit-sandbox --probe 77 # the sandbox firmware - do this on each DotBot ``` (`device flash-mari-gateway` / `flash-swarmit-sandbox` auto-fetch diff --git a/dotbot/cli/device.py b/dotbot/cli/device.py index 6ed1b456..7b9aed5f 100644 --- a/dotbot/cli/device.py +++ b/dotbot/cli/device.py @@ -45,9 +45,20 @@ def _looks_like_path(value: str) -> bool: ) +def _probe_option(f): + return click.option( + "--probe", + help=( + "Select the attached board by its J-Link serial-number prefix " + "(e.g. 77 = a DotBot, 10 = the DK). Omit it when only one probe " + "is attached." + ), + )(f) + + @cmd.command() @click.argument("app") -@click.option("--sn-starting-digits", "-s", help="J-Link serial prefix, e.g. 77.") +@_probe_option @click.option( "--board", "-b", @@ -68,7 +79,7 @@ def _looks_like_path(value: str) -> bool: help="Build configuration (for auto-resolving the artifact).", ) @click.pass_context -def flash(ctx, app, sn_starting_digits, board, sandbox, config): +def flash(ctx, app, probe, board, sandbox, config): """Flash a firmware image to one cabled device (whole-chip program). APP is an app name (resolved against ~/.dotbot/artifacts/, building from source @@ -79,9 +90,7 @@ def flash(ctx, app, sn_starting_digits, board, sandbox, config): from dotbot.firmware.flash import flash_app_image board = from_config(ctx, "board", "board", "device") - sn_starting_digits = from_config( - ctx, "sn_starting_digits", "sn_starting_digits", "device" - ) + probe = from_config(ctx, "probe", "probe", "device") config = from_config(ctx, "config", "build_config", "device") ensure_nrfjprog() if _looks_like_path(app): @@ -90,7 +99,7 @@ def flash(ctx, app, sn_starting_digits, board, sandbox, config): raise click.ClickException(f"Firmware image not found: {image}") else: image = resolve_app_artifact(app, board=board, config=config, sandbox=sandbox) - flash_app_image(image, board=board, sn_starting_digits=sn_starting_digits) + flash_app_image(image, board=board, sn_starting_digits=probe) def _fw_version_option(f): @@ -105,12 +114,6 @@ def _fw_version_option(f): )(f) -def _sn_option(f): - return click.option( - "--sn-starting-digits", "-s", help="J-Link serial prefix, e.g. 77." - )(f) - - @cmd.command(name="flash-swarmit-sandbox") @click.option( "--swarm-id", @@ -125,11 +128,9 @@ def _sn_option(f): help="Optional LH2 calibration file to bake into the config page.", ) @_fw_version_option -@_sn_option +@_probe_option @click.pass_context -def flash_swarmit_sandbox( - ctx, swarm_id, calibration_path, fw_version, sn_starting_digits -): +def flash_swarmit_sandbox(ctx, swarm_id, calibration_path, fw_version, probe): """Turn a DotBot v3 into a swarm sandbox host (was `provision -d dotbot-v3`). Flashes the SwarmIT bootloader (app core) + netcore + writes the @@ -144,8 +145,7 @@ def flash_swarmit_sandbox( raise click.ClickException( "no swarm id. Pass --swarm-id (a 16-bit hex value, e.g. " "--swarm-id 0100), or set swarm_id (or a deployment) in your " - "config. Note: -s / --sn-starting-digits is the J-Link serial " - "prefix, not the swarm id." + "config." ) if fw_version is None: fw_version = pinned_version("swarmit") @@ -160,7 +160,7 @@ def flash_swarmit_sandbox( fw_version=fw_version, calibration_path=calibration_path, bin_dir=artifacts_dir(), - sn_starting_digits=sn_starting_digits, + sn_starting_digits=probe, ) @@ -171,9 +171,9 @@ def flash_swarmit_sandbox( help="16-bit hex swarm id (e.g. 0100); defaults to your config's swarm_id.", ) @_fw_version_option -@_sn_option +@_probe_option @click.pass_context -def flash_mari_gateway(ctx, swarm_id, fw_version, sn_starting_digits): +def flash_mari_gateway(ctx, swarm_id, fw_version, probe): """Turn an nRF5340-DK into the swarm gateway (was `provision -d gateway`). Flashes the Mari gateway firmware (both cores) + writes the network @@ -188,8 +188,7 @@ def flash_mari_gateway(ctx, swarm_id, fw_version, sn_starting_digits): raise click.ClickException( "no swarm id. Pass --swarm-id (a 16-bit hex value, e.g. " "--swarm-id 0100), or set swarm_id (or a deployment) in your " - "config. Note: -s / --sn-starting-digits is the J-Link serial " - "prefix, not the swarm id." + "config." ) if fw_version is None: fw_version = pinned_version("swarmit") @@ -203,7 +202,7 @@ def flash_mari_gateway(ctx, swarm_id, fw_version, sn_starting_digits): net_id=net_id, fw_version=fw_version, bin_dir=artifacts_dir(), - sn_starting_digits=sn_starting_digits, + sn_starting_digits=probe, ) @@ -232,8 +231,8 @@ def flash_programmer(programmer_firmware, files_dir, probe_uid): @cmd.command() -@_sn_option -def info(sn_starting_digits): +@_probe_option +def info(probe): """Read a device's provisioning state (chip id + network identity). Never fails on a blank/unprovisioned board — reports 'not @@ -243,7 +242,7 @@ def info(sn_starting_digits): ensure_nrfjprog() try: - net_id, device_id = read_config_report(sn_starting_digits) + net_id, device_id = read_config_report(probe) except RuntimeError as exc: raise click.ClickException(f"Could not read the device: {exc}") from exc diff --git a/dotbot/config.py b/dotbot/config.py index ea9fbc83..7027c43d 100644 --- a/dotbot/config.py +++ b/dotbot/config.py @@ -126,7 +126,7 @@ class FwSection(_Strict): class DeviceSection(_Strict): board: str | None = None - sn_starting_digits: str | None = None + probe: str | None = None build_config: str | None = None diff --git a/dotbot/firmware/flash.py b/dotbot/firmware/flash.py index 943592dc..17e2e67b 100644 --- a/dotbot/firmware/flash.py +++ b/dotbot/firmware/flash.py @@ -326,7 +326,8 @@ def flash_role( snr = pick_last_jlink_snr() if snr is None: raise click.ClickException( - "Unable to auto-select J-Link; provide --snr explicitly." + "Unable to auto-select J-Link; pass --probe with the board's " + "J-Link serial-number prefix (e.g. --probe 77)." ) click.echo(f"[INFO] using J-Link with serial number: {snr}") @@ -519,7 +520,8 @@ def flash_app_image( snr = pick_last_jlink_snr() if snr is None: raise click.ClickException( - "Unable to auto-select J-Link; provide --snr explicitly." + "Unable to auto-select J-Link; pass --probe with the board's " + "J-Link serial-number prefix (e.g. --probe 77)." ) click.echo(f"[INFO] using J-Link with serial number: {snr}") image_hex = ( @@ -549,7 +551,8 @@ def read_config_report(sn_starting_digits: str | None = None) -> tuple[str, str] snr = pick_last_jlink_snr() if snr is None: raise click.ClickException( - "Unable to auto-select J-Link; provide --snr explicitly." + "Unable to auto-select J-Link; pass --probe with the board's " + "J-Link serial-number prefix (e.g. --probe 77)." ) click.echo(f"[INFO] using J-Link with serial number: {snr}", err=True) return read_net_id(snr=snr), read_device_id(snr=snr) diff --git a/dotbot/firmware/nrf.py b/dotbot/firmware/nrf.py index db432a24..09d90790 100644 --- a/dotbot/firmware/nrf.py +++ b/dotbot/firmware/nrf.py @@ -211,7 +211,10 @@ def pick_last_jlink_snr(nrfjprog_opt=None): print(f"[DEBUG] Found J-Link IDs: {ids}") if ids: return ids[-1] - raise RuntimeError("Unable to auto-select J-Link; provide --snr explicitly.") + raise RuntimeError( + "Unable to auto-select J-Link; pass --probe with the board's " + "J-Link serial-number prefix (e.g. --probe 77)." + ) def pick_matching_jlink_snr(sn_starting_digits: str, nrfjprog_opt: str | None = None): diff --git a/dotbot/tests/test_device.py b/dotbot/tests/test_device.py index 551176b2..62f850c8 100644 --- a/dotbot/tests/test_device.py +++ b/dotbot/tests/test_device.py @@ -93,7 +93,15 @@ def fake_flash_role(role, **kw): monkeypatch.setattr("dotbot.firmware.flash.flash_role", fake_flash_role) result = runner.invoke( device_cmd, - ["flash-swarmit-sandbox", "--swarm-id", "0100", "-f", "0.8.0rc1", "-s", "77"], + [ + "flash-swarmit-sandbox", + "--swarm-id", + "0100", + "-f", + "0.8.0rc1", + "--probe", + "77", + ], ) assert result.exit_code == 0, result.output assert calls["role"] == "dotbot-v3" @@ -114,7 +122,7 @@ def test_flash_swarmit_sandbox_defaults_to_pinned_version( lambda role, **kw: calls.update(role=role, kw=kw), ) result = runner.invoke( - device_cmd, ["flash-swarmit-sandbox", "--swarm-id", "0100", "-s", "77"] + device_cmd, ["flash-swarmit-sandbox", "--swarm-id", "0100", "--probe", "77"] ) assert result.exit_code == 0, result.output assert calls["kw"]["fw_version"] == fetch.pinned_version("swarmit") @@ -163,7 +171,16 @@ def test_flash_mari_gateway_net_id_from_deployment( ) result = runner.invoke( cli, - ["-c", str(cfg), "device", "flash-mari-gateway", "-s", "10", "-f", "0.8.0rc1"], + [ + "-c", + str(cfg), + "device", + "flash-mari-gateway", + "--probe", + "10", + "-f", + "0.8.0rc1", + ], ) assert result.exit_code == 0, result.output assert calls["role"] == "gateway" @@ -234,7 +251,7 @@ def test_flash_swarmit_sandbox_net_id_from_deployment( str(cfg), "device", "flash-swarmit-sandbox", - "-s", + "--probe", "10", "-f", "0.8.0rc1", @@ -253,7 +270,7 @@ def test_info_reports_provisioned(runner, _no_nrfjprog_gate, monkeypatch): "dotbot.firmware.flash.read_config_report", lambda sn=None: ("1234", "BDF2B04BC00D2725"), ) - result = runner.invoke(device_cmd, ["info", "-s", "77"]) + result = runner.invoke(device_cmd, ["info", "--probe", "77"]) assert result.exit_code == 0, result.output assert "provisioned" in result.output assert "0x1234" in result.output From c9859f0fed21f54dbd914fc9da5fb682dd1f9822 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 11:43:03 +0200 Subject: [PATCH 15/20] dotbot/cli: let swarm flash take a bundled app name AI-assisted: Claude Opus 4.7 --- dotbot/cli/_swarm_flash.py | 118 +++++++++++++++++++++++++++++++ dotbot/cli/swarm.py | 10 +++ dotbot/tests/test_swarm_flash.py | 69 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 dotbot/cli/_swarm_flash.py create mode 100644 dotbot/tests/test_swarm_flash.py diff --git a/dotbot/cli/_swarm_flash.py b/dotbot/cli/_swarm_flash.py new file mode 100644 index 00000000..7903769d --- /dev/null +++ b/dotbot/cli/_swarm_flash.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2026-present Inria +# SPDX-License-Identifier: BSD-3-Clause + +"""Name -> firmware resolution for `dotbot swarm flash `. + +`dotbot swarm` is a passthrough to swarmit's CLI, whose `flash` takes a +firmware file. This lets an operator flash a bundled example app by a short, +persona-friendly name (`rc-car`, `spin`, `lights`) instead of typing the full +~/.dotbot/artifacts/-/-sandbox-.bin path. A token +that already looks like a path is passed straight through, so the +explicit-path workflow keeps working unchanged. + +The catalog is intentionally tiny and curated: these are the demos an operator +reaches for, named for what they DO rather than the firmware filename. Anything +else is still flashable by passing an explicit `.bin`/`.hex` path. +""" + +from pathlib import Path + +import click + +from dotbot.cli._artifacts import _find_in_cache + +# Friendly name -> sandbox-app stem. The stem resolves to +# `-sandbox-.bin` in the fetched dotbot-firmware release. +APP_CATALOG = { + "rc-car": "dotbot", # drive the DotBot from the UI / keyboard / joystick + "spin": "spin", # the DotBots spin in place + "lights": "rgbled", # the on-board RGB LED +} + +_DEFAULT_BOARD = "dotbot-v3" +# swarmit's `flash` options that consume the following token as their value; +# everything else starting with "-" is a bare flag. Used to find the firmware +# positional among the flash args. +_VALUE_FLAGS = {"-t", "--ota-timeout", "-r", "--ota-max-retries"} + + +def _looks_like_path(value: str) -> bool: + return ( + value.endswith((".hex", ".bin")) + or "/" in value + or "\\" in value + or Path(value).is_file() + ) + + +def _bin_for(stem: str, board: str = _DEFAULT_BOARD) -> Path | None: + return _find_in_cache(f"{stem}-sandbox-{board}.bin") + + +def _first_positional(rest: list[str]) -> int | None: + """Index of the first non-flag token in `rest` (the firmware argument).""" + i = 0 + while i < len(rest): + tok = rest[i] + if tok in _VALUE_FLAGS: + i += 2 # skip the flag and the value it consumes + continue + if tok.startswith("-"): + i += 1 # a bare flag (-y/--yes, ...) or --flag=value + continue + return i + return None + + +def render_catalog() -> str: + lines = ["Bundled apps you can flash by name:", ""] + width = max(len(name) for name in APP_CATALOG) + for name, stem in APP_CATALOG.items(): + filename = f"{stem}-sandbox-{_DEFAULT_BOARD}.bin" + fetched = _bin_for(stem) is not None + suffix = "" if fetched else " (not fetched - run `dotbot fw fetch`)" + lines.append(f" {name:<{width}} -> {filename}{suffix}") + lines += [ + "", + "Or pass an explicit .hex/.bin path. For a non-v3 board, pass the path.", + ] + return "\n".join(lines) + + +def resolve_flash_args(rest: list[str]) -> tuple[list[str], bool]: + """Rewrite the tokens after `flash` so a known app name becomes a .bin path. + + Returns `(new_rest, handled)`. `handled=True` means the command was fully + serviced here (e.g. `--list`) and the caller must NOT forward it to swarmit. + A token that's already a path, or any flag-only invocation, passes through + untouched. + """ + rest = list(rest) + if "--list" in rest: + click.echo(render_catalog()) + return rest, True + + idx = _first_positional(rest) + if idx is None: + return rest, False # no firmware token; let swarmit report it + + target = rest[idx] + if target in APP_CATALOG: + stem = APP_CATALOG[target] + path = _bin_for(stem) + if path is None: + raise click.ClickException( + f"'{target}' maps to {stem}-sandbox-{_DEFAULT_BOARD}.bin, which " + "isn't in the artifacts cache yet. Run `dotbot fw fetch` first." + ) + rest[idx] = str(path) + click.echo(f"Flashing '{target}' ({path.name})", err=True) + return rest, False + + if _looks_like_path(target): + return rest, False # explicit path - passthrough + + raise click.ClickException( + f"Unknown app '{target}'. Pass a .hex/.bin path, or one of: " + f"{', '.join(APP_CATALOG)} (see `dotbot swarm flash --list`)." + ) diff --git a/dotbot/cli/swarm.py b/dotbot/cli/swarm.py index dead6f17..ed3e5b49 100644 --- a/dotbot/cli/swarm.py +++ b/dotbot/cli/swarm.py @@ -73,6 +73,16 @@ def cmd(ctx, args): obj=ctx.obj, ) return + # `flash ` is PyDotBot sugar: resolve a bundled app name to its + # fetched .bin path before handing off (an explicit path passes + # through), and service `--list` without touching the transport. + if args and args[0] == "flash": + from dotbot.cli._swarm_flash import resolve_flash_args + + rest, handled = resolve_flash_args(args[1:]) + if handled: + return + args = ["flash", *rest] final = inject_config(args, ctx.obj) if args else args _run_swarmit(swarmit_group, final) diff --git a/dotbot/tests/test_swarm_flash.py b/dotbot/tests/test_swarm_flash.py new file mode 100644 index 00000000..39a24d07 --- /dev/null +++ b/dotbot/tests/test_swarm_flash.py @@ -0,0 +1,69 @@ +"""Tests for `dotbot swarm flash ` resolution. + +Pure arg-rewriting + artifacts-cache lookup; no MQTT/serial/hardware. The +cache is pointed at a tmp dir via DOTBOT_ARTIFACTS_DIR so a fake .bin stands +in for a fetched release asset. +""" + +import click +import pytest + +from dotbot.cli import _swarm_flash +from dotbot.cli._swarm_flash import resolve_flash_args + + +@pytest.fixture +def fake_cache(tmp_path, monkeypatch): + """A populated artifacts cache with one fetched dotbot-firmware release.""" + monkeypatch.setenv("DOTBOT_ARTIFACTS_DIR", str(tmp_path)) + fw = tmp_path / "dotbot-firmware-1.22.0" + fw.mkdir() + for stem in ("dotbot", "spin", "rgbled"): + (fw / f"{stem}-sandbox-dotbot-v3.bin").write_bytes(b"\x00") + return fw + + +def test_known_name_resolves_to_bin_path(fake_cache): + rest, handled = resolve_flash_args(["rc-car", "-y"]) + assert handled is False + assert rest[0] == str(fake_cache / "dotbot-sandbox-dotbot-v3.bin") + assert rest[1] == "-y" + + +def test_name_after_flags_is_found(fake_cache): + # The firmware positional can trail value-consuming flags. + rest, _ = resolve_flash_args(["-t", "5", "spin"]) + assert rest[-1] == str(fake_cache / "spin-sandbox-dotbot-v3.bin") + + +def test_explicit_path_passes_through(fake_cache, tmp_path): + custom = tmp_path / "my-app.bin" + custom.write_bytes(b"\x00") + rest, handled = resolve_flash_args([str(custom), "-ys"]) + assert handled is False + assert rest == [str(custom), "-ys"] + + +def test_unknown_name_errors_with_hint(fake_cache): + with pytest.raises(click.ClickException, match="Unknown app 'wiggle'"): + resolve_flash_args(["wiggle"]) + + +def test_known_name_not_fetched_errors(tmp_path, monkeypatch): + monkeypatch.setenv("DOTBOT_ARTIFACTS_DIR", str(tmp_path)) # empty cache + with pytest.raises(click.ClickException, match="fw fetch"): + resolve_flash_args(["rc-car"]) + + +def test_list_is_handled_without_passthrough(fake_cache, capsys): + rest, handled = resolve_flash_args(["--list"]) + assert handled is True + out = capsys.readouterr().out + for name in _swarm_flash.APP_CATALOG: + assert name in out + + +def test_no_firmware_token_passes_through(fake_cache): + rest, handled = resolve_flash_args(["-y"]) + assert handled is False + assert rest == ["-y"] From 331dc2280204bbecd5cd94bcdd3340c065fa545e Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 12:04:41 +0200 Subject: [PATCH 16/20] readme: flash swarm demos by bundled name in the quickstart AI-assisted: Claude Opus 4.7 --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e744d3fd..e9bab94d 100644 --- a/README.md +++ b/README.md @@ -118,19 +118,20 @@ dotbot run gateway -p /dev/cu.usbmodem0010500324491 You can flash as many DotBots as you want, all at once! First, how about making them spinnnn 🔄 🔄 ```bash -dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-1.22.0/spin-sandbox-dotbot-v3.bin -ys # flash the whole fleet with a simple spinning app +dotbot swarm flash spin -ys # flash the whole fleet with a simple spinning app ``` (`dotbot swarm` reads the same `dotbot.toml` as the rest - pass `--conn` / -`--swarm-id` to override it for one run. That path is the `dotbot-firmware` -release `dotbot fw fetch` cached - run `dotbot fw list` to see the exact paths -and versions on your machine.) +`--swarm-id` to override it for one run. `spin` is a bundled app name - +`dotbot swarm flash --list` shows them all, and an explicit `.bin` path still +works. Names resolve to what `dotbot fw fetch` cached; `dotbot fw list` shows +the exact paths and versions on your machine.) Then, flash another experiment: ```bash dotbot swarm stop # ensure all DotBots are in bootloader -dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-1.22.0/dotbot-sandbox-dotbot-v3.bin -ys # this firmware lets DotBots be remote-controlled +dotbot swarm flash rc-car -ys # this firmware lets DotBots be remote-controlled ``` Observe and control your swarm from a web interface: From 8444c41610a7141ceb67e884c7729fe4e7011cbc Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 12:04:52 +0200 Subject: [PATCH 17/20] doc: rename device -s to --probe and document swarm flash names AI-assisted: Claude Opus 4.7 --- doc/cli/device.md | 18 ++++++++--------- doc/cli/swarm.md | 29 +++++++++++++++++++++++----- doc/guides/lh2-calibration-cabled.md | 6 +++--- doc/guides/lh2-calibration.md | 2 +- doc/guides/one-bot.md | 6 +++--- doc/hardware/index.md | 4 ++-- doc/reference/configuration.md | 8 ++++---- 7 files changed, 46 insertions(+), 27 deletions(-) diff --git a/doc/cli/device.md b/doc/cli/device.md index ca1f94ab..3a2bc1bf 100644 --- a/doc/cli/device.md +++ b/doc/cli/device.md @@ -41,7 +41,7 @@ export DOTBOT_FIRMWARE_REPO=$(pwd)/repos/DotBot-firmware # Build, then flash the bare DotBot app onto a DotBot v3 (board defaults to dotbot-v3) dotbot fw artifacts --app dotbot -dotbot device flash dotbot -s 77 +dotbot device flash dotbot --probe 77 ``` `-b/--board` selects the **chip family and core** to program. The nrfjprog family @@ -55,7 +55,7 @@ e.g. don't flash an nRF53 image onto a connected nRF52 (or vice versa). ```bash # Gateway onto an nRF52840-DK (device flash picks -f NRF52 from the board) dotbot fw artifacts --app dotbot_gateway -t nrf52840dk -dotbot device flash dotbot_gateway -b nrf52840dk -s 10 +dotbot device flash dotbot_gateway -b nrf52840dk --probe 10 ``` ### nRF5340 = two cores @@ -67,11 +67,11 @@ net-core image. Build and flash each for its own target - the app image is ```bash # App core dotbot fw artifacts --app dotbot_gateway -t nrf5340dk-app -dotbot device flash dotbot_gateway -b nrf5340dk-app -s 10 +dotbot device flash dotbot_gateway -b nrf5340dk-app --probe 10 # Net core (-b *-net routes to CP_NETWORK) dotbot fw artifacts --app nrf5340_net -t nrf5340dk-net -dotbot device flash nrf5340_net -b nrf5340dk-net -s 10 +dotbot device flash nrf5340_net -b nrf5340dk-net --probe 10 ``` **`flash` flags** (see `dotbot device flash --help` for the full list): @@ -79,7 +79,7 @@ dotbot device flash nrf5340_net -b nrf5340dk-net -s 10 | Flag | Meaning | |---|---| | `-b, --board` | Target board → chip family + core (default `dotbot-v3`) | -| `-s, --sn-starting-digits` | J-Link serial **prefix**, e.g. `77` (v3) or `10` (DK) | +| `--probe` | J-Link serial **prefix**, e.g. `77` (v3) or `10` (DK); omit it when only one probe is attached | | `--sandbox` | Resolve the sandbox-app flavor (`.bin`) | | `--build-config` | `Debug` \| `Release` (default `Release`) | @@ -91,17 +91,17 @@ named release into `~/.dotbot/artifacts/` if it isn't cached. ```bash # nRF5340-DK → swarm gateway -dotbot device flash-mari-gateway --swarm-id 0100 -f 0.8.0rc1 -s 10 +dotbot device flash-mari-gateway --swarm-id 0100 -f 0.8.0rc1 --probe 10 # DotBot v3 → swarm sandbox host (the firmware that runs OTA apps) -dotbot device flash-swarmit-sandbox --swarm-id 0100 -f 0.8.0rc1 -s 77 +dotbot device flash-swarmit-sandbox --swarm-id 0100 -f 0.8.0rc1 --probe 77 ``` | Flag | `flash-mari-gateway` | `flash-swarmit-sandbox` | |---|---|---| | `--swarm-id` | 16-bit hex swarm id (or from config) | 16-bit hex swarm id (or from config) | | `-f, --fw-version` | release to flash (default: the pinned swarmit version) | release to flash (default: the pinned swarmit version) | -| `-s, --sn-starting-digits` | J-Link serial prefix | J-Link serial prefix | +| `--probe` | J-Link serial prefix | J-Link serial prefix | | `-l, --calibration` | - | optional LH2 calibration file to bake in | A board flashed with `flash-swarmit-sandbox` is what [`swarm flash`](swarm.md) @@ -110,7 +110,7 @@ targets to run sandboxed apps over the air. ## Inspect a board ```bash -dotbot device info -s 77 +dotbot device info --probe 77 ``` Reports the chip id and network identity. It never fails on a blank board - it diff --git a/doc/cli/swarm.md b/doc/cli/swarm.md index 6acc1da6..7d185b4f 100644 --- a/doc/cli/swarm.md +++ b/doc/cli/swarm.md @@ -25,8 +25,8 @@ USB-C (the DotBot v3 has an on-board programmer - no separate J-Link needed). Details and chip caveats live in [`device`](device.md). ```bash -dotbot device flash-mari-gateway --swarm-id 1234 -s 10 -f 0.8.0rc1 # a DK -> gateway, net id 0x1234 -dotbot device flash-swarmit-sandbox --swarm-id 1234 -s 77 -f 0.8.0rc1 # each DotBot -> sandbox host +dotbot device flash-mari-gateway --swarm-id 1234 --probe 10 -f 0.8.0rc1 # a DK -> gateway, net id 0x1234 +dotbot device flash-swarmit-sandbox --swarm-id 1234 --probe 77 -f 0.8.0rc1 # each DotBot -> sandbox host ``` ## 2. Start the host bridge @@ -54,6 +54,9 @@ Sandbox apps include `dotbot`, `move`, `rgbled`, `spin`, `timer`. Artifact names look like `spin-sandbox-dotbot-v3.bin`. (Bare `.hex` apps are *not* OTA payloads - those are cabled via [`device flash`](device.md).) +The common demos have short names, so you rarely type a path - see +[Flash by name](#flash-by-name) below. + ## 4. Connect The connection is given as global options *before* the subcommand, or in a @@ -83,7 +86,7 @@ to override). If the broker needs auth, set `DOTBOT_MQTT_USER` / ```bash dotbot swarm status # who's out there + their state dotbot swarm status -w # keep watching -dotbot swarm flash ~/.dotbot/artifacts/dotbot-firmware-local/spin-sandbox-dotbot-v3.bin -ys +dotbot swarm flash spin -ys # flash a bundled demo by name dotbot swarm stop # back to bootloader (before re-flashing) dotbot swarm start # (re)start the loaded app dotbot swarm monitor # tail SWARMIT_EVENT_LOG from bots @@ -92,10 +95,26 @@ dotbot swarm message "hello" # custom text to the bots To replace a running experiment: `stop`, then `flash ... -ys`. +### Flash by name + +`swarm flash` takes either a bundled app **name** or an explicit `.hex`/`.bin` +**path**. A name resolves to the matching `-sandbox-dotbot-v3.bin` in your +artifacts cache (run `dotbot fw fetch` first); a path is flashed as-is. List +the names with `dotbot swarm flash --list`: + +| Name | Firmware | What it does | +|---|---|---| +| `rc-car` | `dotbot-sandbox-dotbot-v3.bin` | drive the DotBot from the UI / keyboard / joystick | +| `spin` | `spin-sandbox-dotbot-v3.bin` | the DotBots spin in place | +| `lights` | `rgbled-sandbox-dotbot-v3.bin` | the on-board RGB LED | + +For another board or an app outside this list, pass the full `.bin` path. + ### `swarm flash` flags | Flag | Meaning | |---|---| +| `--list` | print the bundled-app names and exit | | `-y`, `--yes` | flash without the confirmation prompt | | `-s`, `--start` | start the app once flashed | | `-t`, `--ota-timeout` | seconds per OTA ACK (default `0.7`) | @@ -110,11 +129,11 @@ driving it over the swarm. The arena geometry and `-d` sizing live in the ```bash dotbot swarm stop # capture only runs in READY dotbot swarm lh2-calibration collect --device BC3D... -d 500 # capture from one DotBot -> solve -> save -dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml # apply to every ready DotBot +dotbot swarm lh2-calibration push ~/.dotbot/calibrations/calibration-.toml # apply to every ready DotBot ``` `collect` walks one DotBot through the four arena corners over the air, solves the -homography, and saves it under `~/.dotbot/`. `push` (no `--device`) then sends +homography, and saves it under `~/.dotbot/calibrations/`. `push` (no `--device`) then sends that calibration to **every ready DotBot** - the arena shares one transform. (`collect --push` is a single-DotBot shortcut: it sends only to the captured DotBot.) `push` takes a `calibration-*.toml` or the legacy raw payload - the format is diff --git a/doc/guides/lh2-calibration-cabled.md b/doc/guides/lh2-calibration-cabled.md index f2b1a1c1..a545966d 100644 --- a/doc/guides/lh2-calibration-cabled.md +++ b/doc/guides/lh2-calibration-cabled.md @@ -28,7 +28,7 @@ The `lh2_calibration` app streams raw LH2 counts over serial. Flash it to the cabled DotBot (see [device](../cli/device.md) for serial-prefix selection): ```bash -dotbot device flash lh2_calibration -s 77 # board defaults to dotbot-v3 +dotbot device flash lh2_calibration --probe 77 # board defaults to dotbot-v3 ``` ## 2. Capture the four reference points @@ -42,7 +42,7 @@ dotbot run lh2-calibration collect -p /dev/cu.usbmodem... -d 500 Move the DotBot to each corner - Top left -> Top right -> Bottom left -> Bottom right - pressing the matching button in the TUI at each. When all four are -captured, save. The calibration is written under `~/.dotbot/` (a +captured, save. The calibration is written under `~/.dotbot/calibrations/` (a `calibration-.toml`), the same place the over-the-air flow uses. | Flag | Default | Meaning | @@ -61,7 +61,7 @@ over-the-air flow uses - stop any running app first): ```bash dotbot swarm stop -dotbot swarm lh2-calibration push ~/.dotbot/calibration-.toml +dotbot swarm lh2-calibration push ~/.dotbot/calibrations/calibration-.toml ``` ### Bake it into the bootloader (header path) diff --git a/doc/guides/lh2-calibration.md b/doc/guides/lh2-calibration.md index e00640c3..40db270a 100644 --- a/doc/guides/lh2-calibration.md +++ b/doc/guides/lh2-calibration.md @@ -36,7 +36,7 @@ dotbot swarm stop # put the DotBots in READY dotbot swarm lh2-calibration collect \ --device BC3D3C8A2A6F8E68 -d 500 # capture from one DotBot -> solve -> save dotbot swarm lh2-calibration push \ - ~/.dotbot/calibration-.toml # apply to every ready DotBot + ~/.dotbot/calibrations/calibration-.toml # apply to every ready DotBot ``` `collect` walks one DotBot through the four corners - **top-left -> top-right -> diff --git a/doc/guides/one-bot.md b/doc/guides/one-bot.md index 7cd284c3..5ca7c75b 100644 --- a/doc/guides/one-bot.md +++ b/doc/guides/one-bot.md @@ -18,8 +18,8 @@ app) and the network core (the radio) - so you build and flash two images: dotbot fw artifacts --app dotbot dotbot fw artifacts --app nrf5340_net --target nrf5340dk-net # cable-flash to the DotBot whose J-Link serial starts with 77 -dotbot device flash dotbot -s 77 # app core -dotbot device flash nrf5340_net -b nrf5340dk-net -s 77 # network core +dotbot device flash dotbot --probe 77 # app core +dotbot device flash nrf5340_net -b nrf5340dk-net --probe 77 # network core ``` ## 2. Build and flash the gateway @@ -30,7 +30,7 @@ bridges the DotBot's radio to USB serial. ```bash dotbot fw artifacts --app dotbot_gateway --target nrf52840dk # cable-flash to the DK whose J-Link serial starts with 10 -dotbot device flash dotbot_gateway -b nrf52840dk -s 10 +dotbot device flash dotbot_gateway -b nrf52840dk --probe 10 ``` ## 3. Drive it from the web UI diff --git a/doc/hardware/index.md b/doc/hardware/index.md index 5fd377c7..55ace651 100644 --- a/doc/hardware/index.md +++ b/doc/hardware/index.md @@ -36,7 +36,7 @@ flashing - just a USB-C cable. Plug it in and flash: ```bash # cabled flash of one DotBot (board defaults to dotbot-v3) -dotbot device flash dotbot -s 77 +dotbot device flash dotbot --probe 77 ``` A standalone J-Link is only needed to re-flash the on-board programmer's *own* @@ -61,7 +61,7 @@ host to the swarm radio. ```bash # flash the gateway role onto a DK (writes the network id + both cores) -dotbot device flash-mari-gateway --swarm-id 0100 -f 0.8.0rc1 -s 10 +dotbot device flash-mari-gateway --swarm-id 0100 -f 0.8.0rc1 --probe 10 # then run the host-side UART<->MQTT bridge dotbot run gateway diff --git a/doc/reference/configuration.md b/doc/reference/configuration.md index 2457df3e..28a24842 100644 --- a/doc/reference/configuration.md +++ b/doc/reference/configuration.md @@ -103,7 +103,7 @@ The four tables mirror the four CLI namespaces (`fw` / `device` / `swarm` / | Key | Meaning | |---|---| | `board` | Target board for flashing. | -| `sn_starting_digits` | J-Link serial-number prefix selecting which probe. | +| `probe` | J-Link serial-number prefix selecting which probe (the `--probe` flag). | | `build_config` | `Debug` or `Release`. | `[swarm]` - the fleet over the air (`dotbot swarm`): @@ -244,9 +244,9 @@ build_config = "Release" # One cabled device (dotbot device). [device] -board = "dotbot-v3" -sn_starting_digits = "77" # J-Link serial prefix -build_config = "Release" +board = "dotbot-v3" +probe = "77" # J-Link serial prefix +build_config = "Release" # The fleet over the air (dotbot swarm). [swarm] From 57540c91225f96b7e8a033bb8933e4ff0cb6a796 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 12:05:47 +0200 Subject: [PATCH 18/20] dotbot/calibration: wrap a long glob line for black AI-assisted: Claude Opus 4.7 --- dotbot/calibration/lighthouse2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dotbot/calibration/lighthouse2.py b/dotbot/calibration/lighthouse2.py index 8f6da5ba..38f98269 100644 --- a/dotbot/calibration/lighthouse2.py +++ b/dotbot/calibration/lighthouse2.py @@ -374,7 +374,9 @@ def load_calibration(self) -> list[bytes]: """ toml_files = sorted( [ - *(CALIBRATION_DIR / CALIBRATION_TOML_SUBDIR).glob(CALIBRATION_TOML_GLOB), + *(CALIBRATION_DIR / CALIBRATION_TOML_SUBDIR).glob( + CALIBRATION_TOML_GLOB + ), *CALIBRATION_DIR.glob(CALIBRATION_TOML_GLOB), ], key=lambda p: p.stat().st_mtime, From 9e5cbfb3ecb5bdcb224b4dbcda4cedc81be3d337 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 14:59:16 +0200 Subject: [PATCH 19/20] dotbot/cli: print resolved path and list flash apps in --help epilog AI-assisted: Claude Opus 4.7 --- dotbot/cli/_swarm_flash.py | 18 +++++++++++++++++- dotbot/cli/swarm.py | 5 ++++- dotbot/tests/test_swarm_flash.py | 8 ++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/dotbot/cli/_swarm_flash.py b/dotbot/cli/_swarm_flash.py index 7903769d..20f47229 100644 --- a/dotbot/cli/_swarm_flash.py +++ b/dotbot/cli/_swarm_flash.py @@ -79,6 +79,22 @@ def render_catalog() -> str: return "\n".join(lines) +def flash_help_epilog() -> str: + """A two-line epilog appended to swarmit's `flash --help` output. + + swarmit owns the `flash` command, so the bundled-name sugar isn't in its + native help. Attaching this as the command's epilog lets Click render the + names after the Options block (the `\\b` keeps the two lines unwrapped). + """ + names = ", ".join(APP_CATALOG) + return ( + "\b\n" + f"Examples: flash a bundled app by name ({names}), or an explicit " + ".hex/.bin path.\n" + "Run `dotbot swarm flash --list` to see what each bundled app flashes." + ) + + def resolve_flash_args(rest: list[str]) -> tuple[list[str], bool]: """Rewrite the tokens after `flash` so a known app name becomes a .bin path. @@ -106,7 +122,7 @@ def resolve_flash_args(rest: list[str]) -> tuple[list[str], bool]: "isn't in the artifacts cache yet. Run `dotbot fw fetch` first." ) rest[idx] = str(path) - click.echo(f"Flashing '{target}' ({path.name})", err=True) + click.echo(f"Flashing '{target}' -> {path}", err=True) return rest, False if _looks_like_path(target): diff --git a/dotbot/cli/swarm.py b/dotbot/cli/swarm.py index ed3e5b49..0eb89808 100644 --- a/dotbot/cli/swarm.py +++ b/dotbot/cli/swarm.py @@ -77,8 +77,11 @@ def cmd(ctx, args): # fetched .bin path before handing off (an explicit path passes # through), and service `--list` without touching the transport. if args and args[0] == "flash": - from dotbot.cli._swarm_flash import resolve_flash_args + from dotbot.cli._swarm_flash import flash_help_epilog, resolve_flash_args + flash_cmd = swarmit_group.commands.get("flash") + if flash_cmd is not None and not flash_cmd.epilog: + flash_cmd.epilog = flash_help_epilog() rest, handled = resolve_flash_args(args[1:]) if handled: return diff --git a/dotbot/tests/test_swarm_flash.py b/dotbot/tests/test_swarm_flash.py index 39a24d07..ac7a5f0c 100644 --- a/dotbot/tests/test_swarm_flash.py +++ b/dotbot/tests/test_swarm_flash.py @@ -67,3 +67,11 @@ def test_no_firmware_token_passes_through(fake_cache): rest, handled = resolve_flash_args(["-y"]) assert handled is False assert rest == ["-y"] + + +def test_help_epilog_lists_bundled_names(): + # The epilog (attached to swarmit's flash --help) names every bundled app. + epilog = _swarm_flash.flash_help_epilog() + for name in _swarm_flash.APP_CATALOG: + assert name in epilog + assert "--list" in epilog From bbcb28b908ca24006332e376a2b7ad75e7416a77 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jun 2026 14:59:16 +0200 Subject: [PATCH 20/20] doc: note swarm flash --help summarizes the bundled names AI-assisted: Claude Opus 4.7 --- README.md | 7 +++++-- doc/cli/swarm.md | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e9bab94d..6b56ddf7 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,10 @@ dotbot run gateway -p /dev/cu.usbmodem0010500324491 You can flash as many DotBots as you want, all at once! First, how about making them spinnnn 🔄 🔄 ```bash -dotbot swarm flash spin -ys # flash the whole fleet with a simple spinning app +# flash the whole fleet with a simple spinning app +# the -ys flags confirms (y) the flash, +# and tell the app to start (s) right away after flashing is done +dotbot swarm flash spin -ys ``` (`dotbot swarm` reads the same `dotbot.toml` as the rest - pass `--conn` / @@ -158,7 +161,7 @@ First, collect from one DotBot. Get its address from `dotbot swarm status` (the **Device Addr** column): ```bash -dotbot swarm status # pick one Device Addr +dotbot swarm status # pick one Device Addr, e.g., BDF2B04BC00D2725 dotbot swarm stop # DotBots must be idle to capture dotbot swarm lh2-calibration collect --device -d 500 # capture + solve + save ``` diff --git a/doc/cli/swarm.md b/doc/cli/swarm.md index 7d185b4f..b86e6d3b 100644 --- a/doc/cli/swarm.md +++ b/doc/cli/swarm.md @@ -100,7 +100,8 @@ To replace a running experiment: `stop`, then `flash ... -ys`. `swarm flash` takes either a bundled app **name** or an explicit `.hex`/`.bin` **path**. A name resolves to the matching `-sandbox-dotbot-v3.bin` in your artifacts cache (run `dotbot fw fetch` first); a path is flashed as-is. List -the names with `dotbot swarm flash --list`: +the names with `dotbot swarm flash --list` (they're also summarized at the foot +of `dotbot swarm flash --help`): | Name | Firmware | What it does | |---|---|---|