openbricks 0.10.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openbricks-0.10.2/CHANGELOG.md +97 -0
- openbricks-0.10.2/MANIFEST.in +22 -0
- openbricks-0.10.2/PKG-INFO +144 -0
- openbricks-0.10.2/README.md +103 -0
- openbricks-0.10.2/examples/color_drive.py +54 -0
- openbricks-0.10.2/examples/repeat_mission.py +32 -0
- openbricks-0.10.2/examples/scored_mission.py +53 -0
- openbricks-0.10.2/examples/wall_avoid.py +57 -0
- openbricks-0.10.2/examples/wander.py +39 -0
- openbricks-0.10.2/examples/wander_hardware_style.py +46 -0
- openbricks-0.10.2/examples/wander_with_gyro.py +53 -0
- openbricks-0.10.2/native/_shared/drivebase_core.c +218 -0
- openbricks-0.10.2/native/_shared/drivebase_core.h +138 -0
- openbricks-0.10.2/native/_shared/motor_process_core.c +69 -0
- openbricks-0.10.2/native/_shared/motor_process_core.h +87 -0
- openbricks-0.10.2/native/_shared/observer_core.c +42 -0
- openbricks-0.10.2/native/_shared/observer_core.h +26 -0
- openbricks-0.10.2/native/_shared/servo_core.c +132 -0
- openbricks-0.10.2/native/_shared/servo_core.h +121 -0
- openbricks-0.10.2/native/_shared/trajectory_core.c +111 -0
- openbricks-0.10.2/native/_shared/trajectory_core.h +69 -0
- openbricks-0.10.2/native/openbricks_sim_native.c +714 -0
- openbricks-0.10.2/openbricks.egg-info/PKG-INFO +144 -0
- openbricks-0.10.2/openbricks.egg-info/SOURCES.txt +78 -0
- openbricks-0.10.2/openbricks.egg-info/dependency_links.txt +1 -0
- openbricks-0.10.2/openbricks.egg-info/entry_points.txt +2 -0
- openbricks-0.10.2/openbricks.egg-info/requires.txt +12 -0
- openbricks-0.10.2/openbricks.egg-info/top_level.txt +2 -0
- openbricks-0.10.2/openbricks_dev/__init__.py +4 -0
- openbricks-0.10.2/openbricks_dev/__main__.py +7 -0
- openbricks-0.10.2/openbricks_dev/_nus.py +157 -0
- openbricks-0.10.2/openbricks_dev/cli.py +274 -0
- openbricks-0.10.2/openbricks_dev/flash.py +177 -0
- openbricks-0.10.2/openbricks_dev/log.py +104 -0
- openbricks-0.10.2/openbricks_dev/run.py +260 -0
- openbricks-0.10.2/openbricks_dev/scan.py +99 -0
- openbricks-0.10.2/openbricks_dev/stop.py +53 -0
- openbricks-0.10.2/openbricks_dev/upload.py +117 -0
- openbricks-0.10.2/openbricks_sim/__init__.py +4 -0
- openbricks-0.10.2/openbricks_sim/__main__.py +7 -0
- openbricks-0.10.2/openbricks_sim/chassis.py +222 -0
- openbricks-0.10.2/openbricks_sim/cli.py +226 -0
- openbricks-0.10.2/openbricks_sim/robot.py +346 -0
- openbricks-0.10.2/openbricks_sim/runtime.py +563 -0
- openbricks-0.10.2/openbricks_sim/shim.py +618 -0
- openbricks-0.10.2/openbricks_sim/world.py +110 -0
- openbricks-0.10.2/pyproject.toml +100 -0
- openbricks-0.10.2/setup.cfg +4 -0
- openbricks-0.10.2/setup.py +116 -0
- openbricks-0.10.2/tests/test_chassis.py +91 -0
- openbricks-0.10.2/tests/test_cli.py +187 -0
- openbricks-0.10.2/tests/test_color_sensor.py +136 -0
- openbricks-0.10.2/tests/test_distance_sensor.py +163 -0
- openbricks-0.10.2/tests/test_flash.py +178 -0
- openbricks-0.10.2/tests/test_imu.py +94 -0
- openbricks-0.10.2/tests/test_log.py +137 -0
- openbricks-0.10.2/tests/test_native_drivebase.py +252 -0
- openbricks-0.10.2/tests/test_native_motor_process.py +84 -0
- openbricks-0.10.2/tests/test_native_observer.py +97 -0
- openbricks-0.10.2/tests/test_native_servo.py +159 -0
- openbricks-0.10.2/tests/test_native_trajectory.py +126 -0
- openbricks-0.10.2/tests/test_robot.py +266 -0
- openbricks-0.10.2/tests/test_run.py +232 -0
- openbricks-0.10.2/tests/test_runtime.py +229 -0
- openbricks-0.10.2/tests/test_scan.py +114 -0
- openbricks-0.10.2/tests/test_sdist_build.py +123 -0
- openbricks-0.10.2/tests/test_shim.py +269 -0
- openbricks-0.10.2/tests/test_sim_cli.py +199 -0
- openbricks-0.10.2/tests/test_stop.py +79 -0
- openbricks-0.10.2/tests/test_upload.py +192 -0
- openbricks-0.10.2/tests/test_world.py +78 -0
- openbricks-0.10.2/worlds/wro_2026_elementary_robot_rockstars/README.md +41 -0
- openbricks-0.10.2/worlds/wro_2026_elementary_robot_rockstars/mat.png +0 -0
- openbricks-0.10.2/worlds/wro_2026_elementary_robot_rockstars/world.xml +229 -0
- openbricks-0.10.2/worlds/wro_2026_junior_heritage_heroes/README.md +38 -0
- openbricks-0.10.2/worlds/wro_2026_junior_heritage_heroes/mat.png +0 -0
- openbricks-0.10.2/worlds/wro_2026_junior_heritage_heroes/world.xml +228 -0
- openbricks-0.10.2/worlds/wro_2026_senior_mosaic_masters/README.md +38 -0
- openbricks-0.10.2/worlds/wro_2026_senior_mosaic_masters/mat.png +0 -0
- openbricks-0.10.2/worlds/wro_2026_senior_mosaic_masters/world.xml +198 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# openbricks — host-tooling changelog
|
|
2
|
+
|
|
3
|
+
Versions the unified `openbricks` PyPI package (CLI + MuJoCo sim).
|
|
4
|
+
Firmware versions are tracked separately on the `v*` tag namespace.
|
|
5
|
+
|
|
6
|
+
## 0.10.2 — sdist-only publish (PyPI manylinux requirement)
|
|
7
|
+
|
|
8
|
+
The 0.10.1 publish failed at the upload step:
|
|
9
|
+
|
|
10
|
+
400 Bad Request: Binary wheel
|
|
11
|
+
'openbricks-0.10.1-cp311-cp311-linux_x86_64.whl' has an
|
|
12
|
+
unsupported platform tag 'linux_x86_64'.
|
|
13
|
+
|
|
14
|
+
PyPI requires Linux wheels to be tagged ``manylinux*``, built inside
|
|
15
|
+
the ``quay.io/pypa/manylinux2014`` Docker image (or via
|
|
16
|
+
``cibuildwheel``). The CI's ``python -m build`` on a stock Ubuntu
|
|
17
|
+
runner produces ``linux_x86_64`` wheels, which PyPI rejects.
|
|
18
|
+
|
|
19
|
+
Quick fix: publish ``--sdist`` only. Users ``pip install openbricks``
|
|
20
|
+
get the source distribution and compile the native extension on
|
|
21
|
+
first install (slower, but gcc + python headers are typical on
|
|
22
|
+
Linux/macOS dev machines). Multi-platform manylinux wheels are a
|
|
23
|
+
follow-up.
|
|
24
|
+
|
|
25
|
+
No functional changes vs. 0.10.0 / 0.10.1 — same code, just shippable.
|
|
26
|
+
|
|
27
|
+
## 0.10.1 — sdist build fix (no functional changes)
|
|
28
|
+
|
|
29
|
+
Fixes a build failure that prevented 0.10.0 from publishing:
|
|
30
|
+
`python -m build` runs `setup.py` inside an isolated environment
|
|
31
|
+
that can't reach `../../native/user_c_modules/openbricks/`. The
|
|
32
|
+
sdist now bundles the synced cores via `MANIFEST.in`, and
|
|
33
|
+
`_sync_cores()` falls through cleanly when the upstream isn't
|
|
34
|
+
available (sdist build context). Regression test in
|
|
35
|
+
`tests/test_sdist_build.py` builds a fresh sdist and asserts every
|
|
36
|
+
shared core is inside.
|
|
37
|
+
|
|
38
|
+
If you saw a 0.10.0 install fail, install 0.10.1 instead — it's the
|
|
39
|
+
same code, just shippable.
|
|
40
|
+
|
|
41
|
+
## 0.10.0 — unification
|
|
42
|
+
|
|
43
|
+
The host CLI (`openbricks-dev`) and the MuJoCo simulator
|
|
44
|
+
(`openbricks-sim`) are now a single `openbricks` PyPI package with
|
|
45
|
+
optional `[sim]` extra. One install, one console script, every
|
|
46
|
+
subcommand under `openbricks`.
|
|
47
|
+
|
|
48
|
+
### New install commands
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pip install openbricks # CLI only (lightweight)
|
|
52
|
+
pip install 'openbricks[sim]' # CLI + MuJoCo physics
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Breaking changes
|
|
56
|
+
|
|
57
|
+
- **Console script renamed.** `openbricks-dev <subcmd>` →
|
|
58
|
+
`openbricks <subcmd>`. The old binary is gone in 0.10.0.
|
|
59
|
+
- **Sim subcommand.** `openbricks-sim <subcmd>` → `openbricks sim
|
|
60
|
+
<subcmd>`. Same arguments, dispatched via passthrough to the sim's
|
|
61
|
+
CLI when the `[sim]` extra is installed.
|
|
62
|
+
- **PyPI distribution names.** Users on `openbricks-dev` and
|
|
63
|
+
`openbricks-sim` should `pip uninstall openbricks-dev openbricks-sim`
|
|
64
|
+
and `pip install 'openbricks[sim]'`.
|
|
65
|
+
- **Release-tag namespace.** `openbricks-dev/v*` → `openbricks/v*`.
|
|
66
|
+
Firmware tags `v*` are unchanged.
|
|
67
|
+
|
|
68
|
+
### What did NOT change
|
|
69
|
+
|
|
70
|
+
- The Python module names: `openbricks_dev` and `openbricks_sim`
|
|
71
|
+
stay as they are. The firmware-side `openbricks` package on the
|
|
72
|
+
hub (drivers, robotics, native cores) is also imported on the host
|
|
73
|
+
by the sim's driver shim — collapsing the host CLI's module name
|
|
74
|
+
to `openbricks` would shadow it.
|
|
75
|
+
- Subcommand argument grammars: `flash` / `list` / `run` / `upload` /
|
|
76
|
+
`stop` / `log` / `sim {preview,run}` accept the same flags as before.
|
|
77
|
+
- The codecov flag names (`openbricks-dev`, `openbricks-sim`) — the
|
|
78
|
+
CI workflow uploads coverage under both flags from the unified job
|
|
79
|
+
so the dashboard split stays meaningful.
|
|
80
|
+
|
|
81
|
+
### Migration guide
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Uninstall the legacy split packages.
|
|
85
|
+
pip uninstall openbricks-dev openbricks-sim
|
|
86
|
+
|
|
87
|
+
# Install the unified package. Drop the [sim] if you only need flash + run.
|
|
88
|
+
pip install 'openbricks[sim]'
|
|
89
|
+
|
|
90
|
+
# Update your shell aliases / scripts:
|
|
91
|
+
# openbricks-dev flash … → openbricks flash …
|
|
92
|
+
# openbricks-sim run … → openbricks sim run …
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The legacy `openbricks-dev` PyPI project is frozen at 0.9.x — its
|
|
96
|
+
last release will carry a deprecation notice in the description. No
|
|
97
|
+
new versions will be published under that name.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Files that need to land in the sdist tarball but aren't covered by
|
|
2
|
+
# ``setuptools.packages.find`` (which only catches ``*.py`` inside
|
|
3
|
+
# the package directories).
|
|
4
|
+
|
|
5
|
+
# The CPython extension's C source (sibling to the package).
|
|
6
|
+
include native/openbricks_sim_native.c
|
|
7
|
+
|
|
8
|
+
# Shared cores synced from the firmware's user_c_modules. Without
|
|
9
|
+
# these in the sdist, ``setup.py``'s isolated build environment
|
|
10
|
+
# can't find them (the firmware tree is two levels up, outside the
|
|
11
|
+
# sdist root). ``_sync_cores()`` populates this directory before
|
|
12
|
+
# the sdist is built; including it here lets the wheel-build step
|
|
13
|
+
# (which runs from the unpacked sdist) compile cleanly.
|
|
14
|
+
recursive-include native/_shared *.c *.h
|
|
15
|
+
|
|
16
|
+
# WRO worlds + small examples — useful for users who want to load a
|
|
17
|
+
# world without a repo checkout.
|
|
18
|
+
recursive-include worlds *.xml *.png README.md
|
|
19
|
+
recursive-include examples *.py
|
|
20
|
+
|
|
21
|
+
# CHANGELOG so the migration guide ships with the wheel.
|
|
22
|
+
include CHANGELOG.md
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openbricks
|
|
3
|
+
Version: 0.10.2
|
|
4
|
+
Summary: openbricks — host-side CLI + MuJoCo-backed simulator for the openbricks MicroPython firmware. Flash, list, run, upload, log; ``openbricks sim`` for the physics simulator.
|
|
5
|
+
Author: openbricks contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/1e0ng/openbricks
|
|
8
|
+
Project-URL: Source, https://github.com/1e0ng/openbricks
|
|
9
|
+
Project-URL: Issues, https://github.com/1e0ng/openbricks/issues
|
|
10
|
+
Project-URL: Releases, https://github.com/1e0ng/openbricks/releases
|
|
11
|
+
Keywords: openbricks,micropython,robotics,esp32,ble,bluetooth,pybricks,lego,firmware,simulation,mujoco,wro
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
27
|
+
Classifier: Topic :: Education
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
Requires-Dist: bleak>=0.22
|
|
32
|
+
Requires-Dist: esptool>=4.7
|
|
33
|
+
Requires-Dist: mpremote>=1.23
|
|
34
|
+
Provides-Extra: sim
|
|
35
|
+
Requires-Dist: mujoco>=3.2; extra == "sim"
|
|
36
|
+
Requires-Dist: numpy>=1.24; extra == "sim"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest; extra == "dev"
|
|
39
|
+
Requires-Dist: coverage; extra == "dev"
|
|
40
|
+
Requires-Dist: build; extra == "dev"
|
|
41
|
+
|
|
42
|
+
# openbricks (host CLI + sim)
|
|
43
|
+
|
|
44
|
+
Host-side tooling for openbricks hubs — same UX as `pybricksdev`, built on commodity Python tooling (`bleak`, `esptool`, `mpremote`). One package, one console script: `openbricks flash / list / run / upload / stop / log`. The MuJoCo-backed simulator is built in too — `openbricks sim …` opens the sim's CLI when the `[sim]` extra is installed.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
Recommended — with [`pipx`](https://pipx.pypa.io/) so the CLI lands in an isolated venv and avoids the "externally managed environment" error on modern macOS / Linux distros:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pipx install openbricks # CLI only (lightweight; bleak / esptool / mpremote)
|
|
52
|
+
pipx install 'openbricks[sim]' # CLI + MuJoCo physics simulator
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Plain `pip` works too:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
pip install openbricks
|
|
59
|
+
pip install 'openbricks[sim]'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For development against a repo checkout (editable install):
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
pip install -e 'tools/openbricks[sim]'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`[sim]` adds `mujoco` (~50 MB, native OpenGL) and `numpy` — most users (flash + run + log) don't need it. Without `[sim]`, `openbricks sim …` prints a helpful "pip install openbricks[sim]" hint instead of crashing.
|
|
69
|
+
|
|
70
|
+
## Commands
|
|
71
|
+
|
|
72
|
+
### `flash` — program a hub
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
openbricks flash \
|
|
76
|
+
--name RobotA \
|
|
77
|
+
--port /dev/tty.usbserial-XXXX \
|
|
78
|
+
--firmware native/micropython/ports/esp32/build-openbricks_esp32s3/firmware.bin
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Writes the firmware image and bakes the hub's BLE advertising name into NVS. `--name` is mandatory: two hubs with the same name can't be individually addressed over BLE.
|
|
82
|
+
|
|
83
|
+
Port syntax: `/dev/ttyUSB0` (Linux), `/dev/cu.usbserial-*` (macOS), `COM5` (Windows).
|
|
84
|
+
|
|
85
|
+
Useful flags: `--chip esp32s3`, `--baud 921600`, `--skip-erase` (faster dev loop, keeps stale NVS).
|
|
86
|
+
|
|
87
|
+
### `list` — scan for hubs
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
openbricks list [--timeout 5.0] [--all]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Runs a BLE scan and prints every named device found, sorted by RSSI (strongest first). `--all` includes unnamed devices too.
|
|
94
|
+
|
|
95
|
+
### `run` — stage and launch; button stops it; client exits on stop
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
openbricks run -n RobotA examples/hello.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Stages the script to `/program.py` (same target as `upload`) and triggers the hub's launcher to execute it immediately. Output streams back to your terminal in real time.
|
|
102
|
+
|
|
103
|
+
- **Button stop.** Pressing the hub button while the program runs raises `KeyboardInterrupt` via the same launcher path `upload`+button uses. The client sees the clean "stopped by button press" line and exits.
|
|
104
|
+
- **Program completion.** When the program finishes (or raises), the client disconnects and exits — same as `pybricks-dev run`.
|
|
105
|
+
- **Script persists.** Because `run` stages to `/program.py`, the hub can re-run the last program via a button press without another upload. `upload` and `run` differ only in whether the client auto-launches after upload.
|
|
106
|
+
|
|
107
|
+
Stderr (e.g. exception tracebacks) arrives after stdout and is surfaced with a blank-line separator. No paste-mode `===` echo — raw-paste mode is clean.
|
|
108
|
+
|
|
109
|
+
### `stop` — interrupt the running program
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
openbricks stop -n RobotA
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Sends a single Ctrl-C byte over the NUS REPL bridge, which MicroPython surfaces as `KeyboardInterrupt`. Useful when `openbricks run` has already exited but the hub's still chewing on a long-running user program.
|
|
116
|
+
|
|
117
|
+
### `upload` — stage a script; hub button launches it
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
openbricks upload -n RobotA examples/wander.py
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Writes the script to `/program.py` on the hub. **The code does not run automatically.** Place your robot, press the **program button** (GPIO 4), and the program starts. Press again to stop it mid-run.
|
|
124
|
+
|
|
125
|
+
(Pybricks calls this same operation `download` from the hub's "download to me" perspective. We name by direction-of-data-travel — bytes flow *up* to the hub, hence `upload`.)
|
|
126
|
+
|
|
127
|
+
This works because the firmware ships a frozen `main.py` that:
|
|
128
|
+
|
|
129
|
+
1. Brings BLE + REPL bridge up immediately (so `openbricks run` / `upload` / `stop` are always reachable, even when no program is running).
|
|
130
|
+
2. Instantiates the board's Hub, which wires the **BLE-toggle button** (short-press on GPIO 5) with LED feedback.
|
|
131
|
+
3. Watches the **program button** (GPIO 4) via `openbricks.launcher.run()` — a short-press runs `/program.py`, a second short-press raises `KeyboardInterrupt` in the running program.
|
|
132
|
+
|
|
133
|
+
Two separate pins (4 for program, 5 for BLE toggle), each handled by short-press only — no duration-based dispatch.
|
|
134
|
+
|
|
135
|
+
Pass `--path /alt.py` to stage at a different filename (if you've written your own `main.py` that reads from there).
|
|
136
|
+
|
|
137
|
+
## Tests
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
cd tools/openbricks-dev
|
|
141
|
+
PYTHONPATH=. python -m unittest discover -s tests -t .
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
No real hardware needed — `esptool`, `mpremote`, and `bleak` are mocked.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# openbricks (host CLI + sim)
|
|
2
|
+
|
|
3
|
+
Host-side tooling for openbricks hubs — same UX as `pybricksdev`, built on commodity Python tooling (`bleak`, `esptool`, `mpremote`). One package, one console script: `openbricks flash / list / run / upload / stop / log`. The MuJoCo-backed simulator is built in too — `openbricks sim …` opens the sim's CLI when the `[sim]` extra is installed.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Recommended — with [`pipx`](https://pipx.pypa.io/) so the CLI lands in an isolated venv and avoids the "externally managed environment" error on modern macOS / Linux distros:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pipx install openbricks # CLI only (lightweight; bleak / esptool / mpremote)
|
|
11
|
+
pipx install 'openbricks[sim]' # CLI + MuJoCo physics simulator
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Plain `pip` works too:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
pip install openbricks
|
|
18
|
+
pip install 'openbricks[sim]'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For development against a repo checkout (editable install):
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pip install -e 'tools/openbricks[sim]'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`[sim]` adds `mujoco` (~50 MB, native OpenGL) and `numpy` — most users (flash + run + log) don't need it. Without `[sim]`, `openbricks sim …` prints a helpful "pip install openbricks[sim]" hint instead of crashing.
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
### `flash` — program a hub
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
openbricks flash \
|
|
35
|
+
--name RobotA \
|
|
36
|
+
--port /dev/tty.usbserial-XXXX \
|
|
37
|
+
--firmware native/micropython/ports/esp32/build-openbricks_esp32s3/firmware.bin
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Writes the firmware image and bakes the hub's BLE advertising name into NVS. `--name` is mandatory: two hubs with the same name can't be individually addressed over BLE.
|
|
41
|
+
|
|
42
|
+
Port syntax: `/dev/ttyUSB0` (Linux), `/dev/cu.usbserial-*` (macOS), `COM5` (Windows).
|
|
43
|
+
|
|
44
|
+
Useful flags: `--chip esp32s3`, `--baud 921600`, `--skip-erase` (faster dev loop, keeps stale NVS).
|
|
45
|
+
|
|
46
|
+
### `list` — scan for hubs
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
openbricks list [--timeout 5.0] [--all]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Runs a BLE scan and prints every named device found, sorted by RSSI (strongest first). `--all` includes unnamed devices too.
|
|
53
|
+
|
|
54
|
+
### `run` — stage and launch; button stops it; client exits on stop
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
openbricks run -n RobotA examples/hello.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Stages the script to `/program.py` (same target as `upload`) and triggers the hub's launcher to execute it immediately. Output streams back to your terminal in real time.
|
|
61
|
+
|
|
62
|
+
- **Button stop.** Pressing the hub button while the program runs raises `KeyboardInterrupt` via the same launcher path `upload`+button uses. The client sees the clean "stopped by button press" line and exits.
|
|
63
|
+
- **Program completion.** When the program finishes (or raises), the client disconnects and exits — same as `pybricks-dev run`.
|
|
64
|
+
- **Script persists.** Because `run` stages to `/program.py`, the hub can re-run the last program via a button press without another upload. `upload` and `run` differ only in whether the client auto-launches after upload.
|
|
65
|
+
|
|
66
|
+
Stderr (e.g. exception tracebacks) arrives after stdout and is surfaced with a blank-line separator. No paste-mode `===` echo — raw-paste mode is clean.
|
|
67
|
+
|
|
68
|
+
### `stop` — interrupt the running program
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
openbricks stop -n RobotA
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Sends a single Ctrl-C byte over the NUS REPL bridge, which MicroPython surfaces as `KeyboardInterrupt`. Useful when `openbricks run` has already exited but the hub's still chewing on a long-running user program.
|
|
75
|
+
|
|
76
|
+
### `upload` — stage a script; hub button launches it
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
openbricks upload -n RobotA examples/wander.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Writes the script to `/program.py` on the hub. **The code does not run automatically.** Place your robot, press the **program button** (GPIO 4), and the program starts. Press again to stop it mid-run.
|
|
83
|
+
|
|
84
|
+
(Pybricks calls this same operation `download` from the hub's "download to me" perspective. We name by direction-of-data-travel — bytes flow *up* to the hub, hence `upload`.)
|
|
85
|
+
|
|
86
|
+
This works because the firmware ships a frozen `main.py` that:
|
|
87
|
+
|
|
88
|
+
1. Brings BLE + REPL bridge up immediately (so `openbricks run` / `upload` / `stop` are always reachable, even when no program is running).
|
|
89
|
+
2. Instantiates the board's Hub, which wires the **BLE-toggle button** (short-press on GPIO 5) with LED feedback.
|
|
90
|
+
3. Watches the **program button** (GPIO 4) via `openbricks.launcher.run()` — a short-press runs `/program.py`, a second short-press raises `KeyboardInterrupt` in the running program.
|
|
91
|
+
|
|
92
|
+
Two separate pins (4 for program, 5 for BLE toggle), each handled by short-press only — no duration-based dispatch.
|
|
93
|
+
|
|
94
|
+
Pass `--path /alt.py` to stage at a different filename (if you've written your own `main.py` that reads from there).
|
|
95
|
+
|
|
96
|
+
## Tests
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
cd tools/openbricks-dev
|
|
100
|
+
PYTHONPATH=. python -m unittest discover -s tests -t .
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
No real hardware needed — `esptool`, `mpremote`, and `bleak` are mocked.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Drive forward and stop on a coloured zone — using the firmware
|
|
4
|
+
``TCS34725`` driver unmodified.
|
|
5
|
+
|
|
6
|
+
Run with::
|
|
7
|
+
|
|
8
|
+
openbricks-sim run examples/color_drive.py \\
|
|
9
|
+
--world wro-2026-elementary --viewer
|
|
10
|
+
|
|
11
|
+
The driver shim replaces ``openbricks.drivers.tcs34725.TCS34725``
|
|
12
|
+
with a sim-aware class that raycasts down from the chassis camera
|
|
13
|
+
and returns the floor's material colour. From the script's point
|
|
14
|
+
of view, the same ``rgb()`` / ``ambient()`` API is in use.
|
|
15
|
+
|
|
16
|
+
Drive forward in 5 mm increments (open-loop wheel speed); poll the
|
|
17
|
+
sensor each chunk; stop when ``ambient()`` drops below 30 — typical
|
|
18
|
+
threshold for "we're over a dark zone".
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from openbricks.drivers.tcs34725 import TCS34725
|
|
22
|
+
from openbricks.drivers.jgb37_520 import JGB37Motor
|
|
23
|
+
from openbricks.robotics.drivebase import DriveBase
|
|
24
|
+
from machine import I2C
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
i2c = I2C(0, scl=22, sda=21, freq=400_000)
|
|
28
|
+
sensor = TCS34725(i2c=i2c, address=0x29)
|
|
29
|
+
|
|
30
|
+
m_left = JGB37Motor(in1=12, in2=14, pwm=27,
|
|
31
|
+
encoder_a=18, encoder_b=19)
|
|
32
|
+
m_right = JGB37Motor(in1=13, in2=15, pwm=26,
|
|
33
|
+
encoder_a=20, encoder_b=21)
|
|
34
|
+
db = DriveBase(m_left, m_right,
|
|
35
|
+
wheel_diameter_mm=60,
|
|
36
|
+
axle_track_mm=150)
|
|
37
|
+
db.settings(straight_speed=120)
|
|
38
|
+
|
|
39
|
+
print("starting RGB / ambient:", sensor.rgb(), sensor.ambient())
|
|
40
|
+
|
|
41
|
+
# Up to 30 hops × 5 mm = 150 mm forward, stopping if we hit a dark
|
|
42
|
+
# zone. On the empty / standalone world there's nothing dark to find,
|
|
43
|
+
# so the loop runs to completion; on the WRO mat it stops when the
|
|
44
|
+
# camera passes over a coloured mission area.
|
|
45
|
+
for hop in range(30):
|
|
46
|
+
db.straight(5)
|
|
47
|
+
r, g, b = sensor.rgb()
|
|
48
|
+
a = sensor.ambient()
|
|
49
|
+
print(f" hop {hop}: rgb=({r}, {g}, {b}) ambient={a}")
|
|
50
|
+
if a < 30:
|
|
51
|
+
print(" dark zone detected — stopping.")
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
print("ending pose:", robot.chassis_pose()) # noqa: F821
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Reset + retry — demonstrate ``robot.reset()`` for iterating on a
|
|
4
|
+
mission script in one sim process.
|
|
5
|
+
|
|
6
|
+
Run with::
|
|
7
|
+
|
|
8
|
+
openbricks-sim run examples/repeat_mission.py --no-shim --world empty
|
|
9
|
+
|
|
10
|
+
Drives the chassis forward 200 mm five times in a row, resetting
|
|
11
|
+
between attempts and printing the final position each time. Useful
|
|
12
|
+
template for tuning a mission: adjust your code, re-run the same
|
|
13
|
+
script — no need to relaunch the sim.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# noqa: F821 — ``robot``, ``drivebase``, ``left``, ``right`` come
|
|
17
|
+
# from openbricks-sim's ``run`` command (see cli.cmd_run).
|
|
18
|
+
|
|
19
|
+
NUM_ATTEMPTS = 5
|
|
20
|
+
DISTANCE_MM = 200.0
|
|
21
|
+
SPEED_MM_S = 100.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
for attempt in range(NUM_ATTEMPTS):
|
|
25
|
+
robot.reset() # noqa: F821
|
|
26
|
+
drivebase.straight(distance_mm=DISTANCE_MM, # noqa: F821
|
|
27
|
+
speed_mm_s=SPEED_MM_S)
|
|
28
|
+
robot.run_until(drivebase.is_done, timeout_s=5.0) # noqa: F821
|
|
29
|
+
x, y, yaw = robot.chassis_pose() # noqa: F821
|
|
30
|
+
print(f"attempt {attempt}: pose=({x:.0f}, {y:.0f}, {yaw:.1f})")
|
|
31
|
+
|
|
32
|
+
print("done.")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Drive into a target zone — scored.
|
|
4
|
+
|
|
5
|
+
Run with::
|
|
6
|
+
|
|
7
|
+
openbricks-sim run examples/scored_mission.py --no-shim --world empty
|
|
8
|
+
|
|
9
|
+
Demonstrates the mission-scoring helpers:
|
|
10
|
+
|
|
11
|
+
* ``robot.chassis_in_box(...)`` — axis-aligned membership.
|
|
12
|
+
* ``robot.chassis_in_circle(...)`` — circular membership.
|
|
13
|
+
* ``robot.distance_to(...)`` — Euclidean distance.
|
|
14
|
+
* ``robot.heading_aligned_with(...)`` — yaw within tolerance.
|
|
15
|
+
|
|
16
|
+
Sweeps over a few cruise speeds, scoring each attempt. Useful as a
|
|
17
|
+
template for tuning a real WRO mission against a stable rubric.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# noqa: F821 — ``robot``, ``drivebase`` come from openbricks-sim's
|
|
21
|
+
# ``run`` command (see cli.cmd_run).
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Target: a 100 mm × 100 mm box centred at (200, 0) — i.e. the
|
|
25
|
+
# robot should end up between 150 and 250 mm forward of spawn.
|
|
26
|
+
TARGET_X_MIN = 150.0
|
|
27
|
+
TARGET_X_MAX = 250.0
|
|
28
|
+
TARGET_Y_MIN = -50.0
|
|
29
|
+
TARGET_Y_MAX = 50.0
|
|
30
|
+
TARGET_CX = (TARGET_X_MIN + TARGET_X_MAX) / 2.0
|
|
31
|
+
TARGET_CY = (TARGET_Y_MIN + TARGET_Y_MAX) / 2.0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def score(robot):
|
|
35
|
+
"""Combined score: 100 if in the box AND aligned, otherwise
|
|
36
|
+
a falloff based on distance from the target centre."""
|
|
37
|
+
in_zone = robot.chassis_in_box(
|
|
38
|
+
TARGET_X_MIN, TARGET_Y_MIN, TARGET_X_MAX, TARGET_Y_MAX)
|
|
39
|
+
aligned = robot.heading_aligned_with(0.0, tolerance_deg=15.0)
|
|
40
|
+
if in_zone and aligned:
|
|
41
|
+
return 100
|
|
42
|
+
d = robot.distance_to(TARGET_CX, TARGET_CY)
|
|
43
|
+
return max(0, int(100 - d / 5)) # rough falloff: lose 1 point / 5 mm
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
for speed in (60.0, 80.0, 100.0, 120.0, 150.0):
|
|
47
|
+
robot.reset() # noqa: F821
|
|
48
|
+
drivebase.straight(distance_mm=200.0, speed_mm_s=speed) # noqa: F821
|
|
49
|
+
robot.run_until(drivebase.is_done, timeout_s=5.0) # noqa: F821
|
|
50
|
+
s = score(robot) # noqa: F821
|
|
51
|
+
x, y, yaw = robot.chassis_pose() # noqa: F821
|
|
52
|
+
print(f"speed={speed:5.0f} mm/s -> score={s:3d}, "
|
|
53
|
+
f"pose=({x:6.1f}, {y:5.1f}, {yaw:5.1f}°)")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Drive forward; stop on a wall — using the firmware HCSR04 driver.
|
|
4
|
+
|
|
5
|
+
Run with::
|
|
6
|
+
|
|
7
|
+
openbricks-sim run examples/wall_avoid.py \\
|
|
8
|
+
--world wro-2026-elementary --viewer
|
|
9
|
+
|
|
10
|
+
The driver shim replaces ``openbricks.drivers.hcsr04.HCSR04`` with a
|
|
11
|
+
sim-aware class that raycasts forward from the chassis ``chassis_dist``
|
|
12
|
+
site. From the script's POV the same firmware ``distance_mm()`` is
|
|
13
|
+
in use.
|
|
14
|
+
|
|
15
|
+
Polls the sensor every 50 ms (sim time, via ``time.sleep_ms`` which
|
|
16
|
+
the shim patches to step the runtime). Stops when distance drops
|
|
17
|
+
below 200 mm — typical "robot is about to hit a wall" threshold.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import time
|
|
21
|
+
|
|
22
|
+
from openbricks.drivers.hcsr04 import HCSR04
|
|
23
|
+
from openbricks.drivers.jgb37_520 import JGB37Motor
|
|
24
|
+
from openbricks.robotics.drivebase import DriveBase
|
|
25
|
+
from machine import Pin
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
sensor = HCSR04(trig=18, echo=19, timeout_us=30_000)
|
|
29
|
+
|
|
30
|
+
m_left = JGB37Motor(in1=12, in2=14, pwm=27,
|
|
31
|
+
encoder_a=20, encoder_b=21)
|
|
32
|
+
m_right = JGB37Motor(in1=13, in2=15, pwm=26,
|
|
33
|
+
encoder_a=22, encoder_b=23)
|
|
34
|
+
db = DriveBase(m_left, m_right,
|
|
35
|
+
wheel_diameter_mm=60,
|
|
36
|
+
axle_track_mm=150)
|
|
37
|
+
|
|
38
|
+
print("starting distance:", sensor.distance_mm())
|
|
39
|
+
|
|
40
|
+
# Drive forward at 100 mm/s open-loop; poll the sensor; stop when
|
|
41
|
+
# anything inside 200 mm shows up. The ``-1`` "no echo" result
|
|
42
|
+
# means we keep going.
|
|
43
|
+
m_left.run_speed(180.0)
|
|
44
|
+
m_right.run_speed(180.0)
|
|
45
|
+
|
|
46
|
+
for tick in range(200): # up to 10 s of sim time
|
|
47
|
+
time.sleep_ms(50)
|
|
48
|
+
d = sensor.distance_mm()
|
|
49
|
+
if d != -1 and d < 200:
|
|
50
|
+
print(f"wall at {d} mm — stopping.")
|
|
51
|
+
break
|
|
52
|
+
if tick % 5 == 0:
|
|
53
|
+
print(f" {tick * 50} ms: distance = {d}")
|
|
54
|
+
|
|
55
|
+
m_left.brake()
|
|
56
|
+
m_right.brake()
|
|
57
|
+
print("ending pose:", robot.chassis_pose()) # noqa: F821
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Tiny example: drive a square with the default chassis.
|
|
4
|
+
|
|
5
|
+
Run with::
|
|
6
|
+
|
|
7
|
+
openbricks-sim run examples/wander.py --world wro-2026-elementary --viewer
|
|
8
|
+
|
|
9
|
+
The CLI pre-builds a ``SimRobot`` and exposes ``robot``, ``drivebase``,
|
|
10
|
+
``left``, ``right`` in this script's globals — same names you'd use
|
|
11
|
+
on real hardware after::
|
|
12
|
+
|
|
13
|
+
from openbricks.robotics.drivebase import DriveBase
|
|
14
|
+
drivebase = DriveBase(left, right, wheel_diameter_mm=60, axle_track_mm=150)
|
|
15
|
+
|
|
16
|
+
A driver-shim layer that lets a single script target *both* targets
|
|
17
|
+
unmodified lands in Phase C3.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# noqa: F821 — ``robot``, ``drivebase``, ``left``, ``right`` come
|
|
21
|
+
# from openbricks-sim's ``run`` command (see cli.cmd_run).
|
|
22
|
+
|
|
23
|
+
# Sanity-print the starting pose so a user running with --viewer can
|
|
24
|
+
# see the script ran.
|
|
25
|
+
print("starting at:", robot.chassis_pose()) # noqa: F821
|
|
26
|
+
|
|
27
|
+
# Drive a 200×200 mm square, pausing briefly at each corner.
|
|
28
|
+
SIDE_MM = 200.0
|
|
29
|
+
TURN_DEG = 90.0
|
|
30
|
+
SPEED = 100.0
|
|
31
|
+
TURN_RATE = 90.0
|
|
32
|
+
|
|
33
|
+
for i in range(4):
|
|
34
|
+
drivebase.straight(distance_mm=SIDE_MM, speed_mm_s=SPEED) # noqa: F821
|
|
35
|
+
robot.run_until(drivebase.is_done, timeout_s=5.0) # noqa: F821
|
|
36
|
+
drivebase.turn(angle_deg=TURN_DEG, rate_dps=TURN_RATE) # noqa: F821
|
|
37
|
+
robot.run_until(drivebase.is_done, timeout_s=5.0) # noqa: F821
|
|
38
|
+
|
|
39
|
+
print("ended at: ", robot.chassis_pose()) # noqa: F821
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Drive a square — using the *firmware* APIs.
|
|
4
|
+
|
|
5
|
+
Run with::
|
|
6
|
+
|
|
7
|
+
openbricks-sim run examples/wander_hardware_style.py \\
|
|
8
|
+
--world wro-2026-elementary --viewer
|
|
9
|
+
|
|
10
|
+
This script imports from ``openbricks.drivers.*`` and
|
|
11
|
+
``openbricks.robotics.*`` exactly as a real-hardware ``main.py``
|
|
12
|
+
would. The driver-shim layer (``openbricks_sim.shim``, installed by
|
|
13
|
+
the ``run`` command) routes those imports through to MuJoCo:
|
|
14
|
+
|
|
15
|
+
* ``machine.Pin`` / ``machine.PWM`` are no-op fakes — the wiring
|
|
16
|
+
arguments below are ignored, but the imports succeed.
|
|
17
|
+
* ``openbricks._native.Servo`` + ``DriveBase`` bind to the default
|
|
18
|
+
chassis's MuJoCo motor / encoder slots (chassis_motor_l/r).
|
|
19
|
+
* ``time.sleep_ms`` is patched to step the sim, so the firmware
|
|
20
|
+
drivers' busy-wait loops on ``is_done`` advance physics each
|
|
21
|
+
iteration without changes.
|
|
22
|
+
|
|
23
|
+
Same source on hardware and in the sim — that's the point.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from openbricks.drivers.jgb37_520 import JGB37Motor
|
|
27
|
+
from openbricks.robotics.drivebase import DriveBase
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
m_left = JGB37Motor(in1=12, in2=14, pwm=27,
|
|
31
|
+
encoder_a=18, encoder_b=19,
|
|
32
|
+
counts_per_output_rev=1320)
|
|
33
|
+
m_right = JGB37Motor(in1=13, in2=15, pwm=26,
|
|
34
|
+
encoder_a=20, encoder_b=21,
|
|
35
|
+
counts_per_output_rev=1320)
|
|
36
|
+
|
|
37
|
+
db = DriveBase(m_left, m_right,
|
|
38
|
+
wheel_diameter_mm=60,
|
|
39
|
+
axle_track_mm=150)
|
|
40
|
+
db.settings(straight_speed=180, turn_rate=120)
|
|
41
|
+
|
|
42
|
+
for _ in range(4):
|
|
43
|
+
db.straight(150) # mm
|
|
44
|
+
db.turn(90) # body degrees, positive = left
|
|
45
|
+
|
|
46
|
+
print("done. chassis pose (mm, mm, deg):", robot.chassis_pose()) # noqa: F821
|