Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 1 addition & 31 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,36 +98,6 @@ jobs:
- name: Build and check documentation
run: tox -e doc

frontend:
name: check frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up nodejs
uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm install
working-directory: ./dotbot/frontend
- run: npm run lint
working-directory: ./dotbot/frontend
- run: npm run typecheck
working-directory: ./dotbot/frontend
- run: npm run test
working-directory: ./dotbot/frontend
- run: npm run build
working-directory: ./dotbot/frontend
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
flags: frontend
verbose: true
- name: Upload frontend build
uses: actions/upload-artifact@v4
with:
name: frontend
path: ./dotbot/frontend/build

console:
name: check console
runs-on: ubuntu-latest
Expand Down Expand Up @@ -159,7 +129,7 @@ jobs:
path: ./dotbot/console-web/dist

package:
needs: [test, doc, frontend, console, control_loop]
needs: [test, doc, console, control_loop]
name: build source package
runs-on: ${{ matrix.os }}
strategy:
Expand Down
26 changes: 12 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

## Purpose

Python control plane for DotBots. Serial / cloud / edge adapters talk to a DotBot gateway (often via Mari → marilib); a FastAPI REST + WebSocket server exposes state; a React web UI provides joystick/map/lighthouse-position visualization. Ships a unified `dotbot` CLI whose top level is four object-namespaces: `fw` (firmware artifacts: build/fetch/list/make), `device` (one cabled device: flash/info), `swarm` (the fleet over the air), and `run` (host-side processes you launch — `dotbot run controller`, `run gateway`, `run simulator`, `run calibrate-lh2`, `run demo`, `run keyboard`, `run joystick`), plus DotBot/SailBot simulators. The `dotbot` dispatcher is the only console script — there are no per-command `dotbot-*` binaries.
Python control plane for DotBots. Serial / cloud / edge adapters talk to a DotBot gateway (often via Mari → marilib); a FastAPI REST + WebSocket server exposes state; a React web console (`dotbot/console-web/`, served at `/console`) is the one browser UI: map, joystick, waypoints and swarmit orchestration. Ships a unified `dotbot` CLI whose top level is four object-namespaces: `fw` (firmware artifacts: build/fetch/list/make), `device` (one cabled device: flash/info), `swarm` (the fleet over the air), and `run` (host-side processes you launch — `dotbot run controller`, `run gateway`, `run simulator`, `run calibrate-lh2`, `run demo`, `run keyboard`, `run joystick`), plus DotBot/SailBot simulators. The `dotbot` dispatcher is the only console script — there are no per-command `dotbot-*` binaries.

This is the most active repo in the ecosystem (187 commits in last 90 days as of 2026-05-05).

## Tech stack

- **Backend**: Python ≥3.7, FastAPI + uvicorn, click, gmqtt + qrkey for MQTT, pyserial, structlog, pygame, pynput, numpy
- **Frontend**: React 18 + TypeScript, Vite, Bootstrap 5, react-leaflet, MQTT.js, vitest + @testing-library
- **Build**: `hatchling` (PEP 517) with custom sdist hook that bundles the frontend; `tox` for orchestration; `pre-commit`; `ruff` / `isort` / `black`
- **Package**: pip (PyPI as `pydotbot`); npm for frontend
- **Frontend**: React 18 + TypeScript, Vite, vitest + @testing-library (`dotbot/console-web/`)
- **Build**: `hatchling` (PEP 517) with custom sdist hook that builds and bundles the console; `tox` for orchestration; `pre-commit`; `ruff` / `isort` / `black`
- **Package**: pip (PyPI as `pydotbot`); npm for the console

## Entry points

- `dotbot/cli/main.py` — unified `dotbot` Click group (lazy subcommand loader)
- `dotbot/controller_app.py` — `dotbot run controller` subcommand backend; wires adapters and settings
- `dotbot/controller.py:1` — 737-line `Controller` class; central object
- `dotbot/frontend/src/App.tsx` — React UI root
- `dotbot/console-web/src/App.tsx` — console UI root

## Controller surface (REST + WebSocket)

Expand Down Expand Up @@ -59,22 +59,22 @@ dotbot run calibrate-lh2 --help # LH2 calibration (optional: pip install pydotb
dotbot run demo --list # built-in research demos

# Tests / lint / build
tox # envs: tests, check, cli, web=npm run lint, doc
tox # envs: tests, check, cli, web=console npm run lint, doc

# Frontend
cd dotbot/frontend && npm install
# Console
cd dotbot/console-web && npm install
npm start # dev
npm run build
npm test # vitest — NOT currently run in CI
npm test # vitest, run in CI with lint, typecheck, build
```

CI: `.github/workflows/continuous-integration.yml` — `tox` on Linux/macOS/Windows (Py 3.11/3.12, Node 18/20). Also a CMake build of `utils/control_loop` against `DotBots/DotBot-libs`.

## Cross-repo dependencies

- **`qrkey`** — `pyproject.toml:42`; `dotbot/qrkey.py`; frontend `package.json` (`qrkey ^0.12.0`)
- **`qrkey`** — `pyproject.toml`; `dotbot/examples/qrkey_demo/`
- **`marilib`** — `pyproject.toml:48` (`marilib-pkg`); imported in `dotbot/adapter.py` (MarilibCloud, MarilibEdge, MQTT/Serial adapters, MariFrame). **Tight coupling.**
- **`PyDotBot-utils`** — `pyproject.toml:49`; used by `utils/hooks/sdist.py:build_frontend`
- **`PyDotBot-utils`** — `pyproject.toml`
- **`DotBot-libs`** — checked out in CI to build `utils/control_loop` C library
- **`DotBot-firmware`** — referenced only in README (flashing instructions); no code dep
- **`swarmit`** — sibling package, a core dependency (`pyproject.toml`);
Expand Down Expand Up @@ -104,7 +104,6 @@ CI: `.github/workflows/continuous-integration.yml` — `tox` on Linux/macOS/Wind
- **`dotbot/examples/`** (`charging_station`, `work_and_charge`, `minimum_naming_game`, `labyrinth`, `motions`) is a research-experiment dumping ground shipped inside the package; most TODOs live here. Good candidate to extract or prune.
- **`tox.ini` references `dotbot/pin_code_ui`** (env `pin_code`) but that directory does not exist — dead config.
- **`.env` file is committed** (only `.env.example` should be) — audit for secrets.
- **Frontend has parallel `*.test.tsx` files** for every component, but CI only runs `npm run lint` (not `vitest`) — frontend tests are written but not executed.
- Maintainer (`aabadie`) is leaving summer 2026 — onboarding ergonomics matter here.

## Branch policy
Expand All @@ -115,7 +114,6 @@ CI: `.github/workflows/continuous-integration.yml` — `tox` on Linux/macOS/Wind

## Agent-task ideas

- **Wire vitest into CI** (the tests already exist; the wiring is missing).
- **Audit `.env` for secrets** and replace with `.env.example`. Add `.env` to `.gitignore`.
- **Remove dead `pin_code` tox env** and the missing `dotbot/pin_code_ui` reference.
- **Investigate stale LH2 branches** (`#132`, `#141`): are they worth rebasing or are they superseded?
Expand All @@ -127,6 +125,6 @@ CI: `.github/workflows/continuous-integration.yml` — `tox` on Linux/macOS/Wind
## Don't

- **Don't push to `main` without a PR** — this is the hottest repo and traceability matters.
- **Don't break the FastAPI REST/WebSocket contract** without bumping the major version — external scripts and the React UI depend on the surface.
- **Don't break the FastAPI REST/WebSocket contract** without bumping the major version — external scripts and the console depend on the surface.
- **Don't refactor `dotbot/adapter.py`** in isolation; coordinate with `marilib` and `qrkey`.
- **Don't bump `marilib-pkg` or `qrkey`** without verifying the adapter still works end-to-end.
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
it from another machine pass `--controller-http-host 0.0.0.0`, set
`[run.controller] http_host`, or `DOTBOT_RUN_CONTROLLER_HTTP_HOST`; binding
beyond loopback logs a warning.
- **`dotbot run controller` now opens the unified web console** at `/console`
instead of the classic dashboard at `/PyDotBot`. The classic UI is still
served and still carries the qrkey demo, the REST demo and the SailBot
views. If only one of the two is built, that one is opened; if neither is,
the controller serves the API and says so rather than opening a dead tab.
- **`dotbot run controller` now opens the unified web console** at `/console`,
and `/` redirects there. If the console is not built, the controller serves
the API and says so rather than opening a dead tab.
- **Device addresses are rendered uppercase everywhere**, through a single
`dotbot.addr_to_hex()` helper, and are matched case-sensitively. The address
is the join key between the control plane and swarmit, which already
Expand Down Expand Up @@ -96,6 +94,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Removed

- **The classic web UI** (`dotbot/frontend/`, served at `/PyDotBot`). The
console at `/console` is the only browser UI; `/PyDotBot` now answers 404,
so update bookmarks. Its classic-only views go with it: the REST demo page,
the SailBot map and the qrkey phone page. The phone page is retired pending
a qrkey mode in the console: `dotbot run demo qr` still relays the
controller's notifications to MQTT and shows the QR, but no phone page
reads what it relays yet.
- `dotbot-qrkey` console script — use `python -m dotbot.examples.qrkey_demo`
or `dotbot run demo qr` instead.
- `dotbot-edge-gateway` console script — the referenced module
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ dotbot run simulator
The console opens automatically; pass `--headless` to suppress it (it's still
served). It is one map-first UI for both driving the fleet and orchestrating the
testbed - firmware flashing, start/stop and live events - when a swarmit server
is reachable. The classic UI remains at `/PyDotBot`; it is where the qrkey demo,
the REST demo and the SailBot views live.
is reachable.

Drive the simulated DotBots from the console, or run a bundled demo in a
second terminal:
Expand Down
21 changes: 8 additions & 13 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Codecov config — the gate belongs on the Python control plane, not on the
# two web apps.
# web console.
#
# Coverage arrives from three uploads (pytest, the classic frontend's vitest,
# the console's vitest). Untagged they merge into one project total, so adding
# a few thousand lines of thinly-covered TS drags the number down and fails a
# PR that did nothing to the Python. Flags keep the three apart.
# Coverage arrives from two uploads (pytest and the console's vitest).
# Untagged they merge into one project total, so adding a few thousand lines
# of thinly-covered TS drags the number down and fails a PR that did nothing to
# the Python. Flags keep the two apart.
#
# The web apps are reported but never block: their vitest suites cover the pure
# The console is reported but never blocks: its vitest suite covers the pure
# logic (state merge, mission grouping, SSE parsing, the drive mixing) and
# deliberately not the rendering, so their absolute number is low by design and
# deliberately not the rendering, so its absolute number is low by design and
# says little about whether a change is safe.

flags:
Expand All @@ -17,10 +17,6 @@ flags:
- dotbot/
- utils/
carryforward: true
frontend:
paths:
- dotbot/frontend/
carryforward: true
console:
paths:
- dotbot/console-web/
Expand All @@ -29,7 +25,7 @@ flags:
coverage:
status:
project:
# No unflagged aggregate status: that is the one that mixed the three.
# No unflagged aggregate status: that is the one that mixed the two.
default: false
python:
flags:
Expand All @@ -40,7 +36,6 @@ coverage:
threshold: 1%
web:
flags:
- frontend
- console
# Reported in the PR comment, never a CI gate.
informational: true
Expand Down
7 changes: 3 additions & 4 deletions doc/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dotbot run --help # the full list

| Subcommand | Launches |
|---|---|
| `controller` | Control plane: REST/WS API + web dashboard. The hub everything else talks to. |
| `controller` | Control plane: REST/WS API + web console. The hub everything else talks to. |
| `gateway` | Host bridge: gateway firmware UART ↔ MQTT broker. |
| `simulator` | Standalone simulator (no hardware). |
| `calibrate-lh2` | **Deprecated.** Cabled LH2 calibration on one board (capture / apply). Use [`swarm calibrate-lh2`](swarm.md) instead. |
Expand All @@ -24,9 +24,8 @@ dotbot run --help # the full list

Connect to a swarm and serve the console at `http://localhost:8000/console/`.
The console is one map-first UI for driving the fleet and, when a swarmit server
is reachable, orchestrating the testbed. The classic dashboard stays served at
`/PyDotBot`, which is where the qrkey demo, the REST demo and the SailBot views
live.
is reachable, orchestrating the testbed. `http://localhost:8000/` redirects
to it.
`--conn` is one discriminated string: `mqtts://host:port`, a serial path, or
`simulator`.

Expand Down
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
("py:class", r"pynput.*"),
("py:class", r"threading.*"),
("py:class", r"starlette.*"),
("py:class", r"fastapi.*"),
("py:class", r"ConfigDict"),
("py:class", r"DotenvType"),
("py:class", r"FieldInfo"),
Expand Down
9 changes: 5 additions & 4 deletions doc/guides/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ 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`.

The dashboard opens in a browser tab automatically. Pass `--headless` to
The web console opens in a browser tab automatically. Pass `--headless` to
suppress that (it's still served); browse to
<http://localhost:8000/PyDotBot> yourself.
<http://localhost:8000/console/> yourself (<http://localhost:8000/> redirects
there).

| Flag | What it does |
|---|---|
Expand Down Expand Up @@ -62,11 +63,11 @@ discovered and the full schema.

## The web UI

At <http://localhost:8000/PyDotBot> the page lists every DotBot the controller
At <http://localhost:8000/console/> a map shows every DotBot the controller
sees. Select one to control it:

- **Joystick** - a virtual joystick drives the selected DotBot.
- **RGB LED** - pick a color and the DotBot's LED follows.
- **Waypoints** - set waypoints on the map for the selected DotBots to drive to.
- If you flashed Lighthouse 2 localization, DotBots report their `(x, y)` position
on the map (see [LH2 calibration](lh2-calibration.md)).

Expand Down
2 changes: 1 addition & 1 deletion doc/guides/simulator.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ you write against the simulator runs unchanged against real DotBots.
dotbot run simulator
```

This opens the web UI at <http://localhost:8000/PyDotBot/> driving a simulated
This opens the web console at <http://localhost:8000/console/> 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 DotBots from the
Expand Down
15 changes: 6 additions & 9 deletions doc/reference/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,18 @@ to the controller, so the map and joystick never come alive. Turn it off:

Chromium-based browsers (Chrome, Edge, Brave) are unaffected.

## Web UI is missing (frontend build not found)
## Web console is missing (console build not found)

Starting the controller or simulator logs:

```
Frontend build not found at .../dotbot/frontend/build; the web UI will be unavailable.
Console build not found at .../dotbot/console-web/dist; /console will be unavailable.
```

The React web UI ships inside the published wheel but is **not** built by a
The web console ships inside the published wheel but is **not** built by a
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 -
`dotbot/console-web/dist/`. 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 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,
`RuntimeError: Directory '.../dotbot/frontend/build' does not exist`.
- **From PyPI** - install the wheel, which bundles the console: `pip install pydotbot`.
- **From a git checkout** - build it once: `cd dotbot/console-web && npm install && npm run build`.
6 changes: 3 additions & 3 deletions dotbot/cli/_lazygroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

Why lazy: importing e.g. `dotbot.controller_app` pulls in `dotbot.server`,
which mounts FastAPI StaticFiles at module load. That's fine for the
`controller` subcommand but `dotbot run --help` shouldn't pay the cost (or
fail when the frontend bundle isn't built). The root group and the `run`
group both use this so the laziness holds at every level of the tree.
`controller` subcommand but `dotbot run --help` shouldn't pay the cost. The
root group and the `run` group both use this so the laziness holds at every
level of the tree.
"""

import importlib
Expand Down
8 changes: 5 additions & 3 deletions dotbot/examples/qrkey_demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ The demo's FastAPI serves on `http://localhost:8080` by default:
- `GET /pin_code/qr_code` — the scannable QR image (SVG)
- `WS /ws` — pin-rotation notifications for the desktop QR display

The QR encoded URL points at a phone-friendly UI (default:
`https://dotbots.github.io/PyDotBot`); set `FRONTEND_BASE_URL=…` to
override (e.g. point at your laptop's LAN IP for local-only testing).
The QR encoded URL is set with `FRONTEND_BASE_URL=…`, and
`--webbrowser` opens that same URL, PIN included, on the machine running
the demo. The phone page it pointed at is retired pending a qrkey mode in
the console: the relay publishes the controller's notifications on
`/notify`, which no published phone page reads yet.

## Architecture

Expand Down
2 changes: 1 addition & 1 deletion dotbot/examples/qrkey_demo/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ async def _open_webbrowser(self):
writer.close()
break
url = (
f"http://{self.settings.http_host}:{self.settings.http_port}/PyDotBot?"
f"{qrkey_settings.frontend_base_url}?"
f"use_qrkey=true&"
f"pin={self.qrkey.pin_code}&"
f"mqtt_host={qrkey_settings.mqtt_host}&"
Expand Down
5 changes: 0 additions & 5 deletions dotbot/frontend/.env

This file was deleted.

11 changes: 0 additions & 11 deletions dotbot/frontend/.env.example

This file was deleted.

1 change: 0 additions & 1 deletion dotbot/frontend/.env.test

This file was deleted.

23 changes: 0 additions & 23 deletions dotbot/frontend/.gitignore

This file was deleted.

Loading
Loading