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.
Files changed (80) hide show
  1. openbricks-0.10.2/CHANGELOG.md +97 -0
  2. openbricks-0.10.2/MANIFEST.in +22 -0
  3. openbricks-0.10.2/PKG-INFO +144 -0
  4. openbricks-0.10.2/README.md +103 -0
  5. openbricks-0.10.2/examples/color_drive.py +54 -0
  6. openbricks-0.10.2/examples/repeat_mission.py +32 -0
  7. openbricks-0.10.2/examples/scored_mission.py +53 -0
  8. openbricks-0.10.2/examples/wall_avoid.py +57 -0
  9. openbricks-0.10.2/examples/wander.py +39 -0
  10. openbricks-0.10.2/examples/wander_hardware_style.py +46 -0
  11. openbricks-0.10.2/examples/wander_with_gyro.py +53 -0
  12. openbricks-0.10.2/native/_shared/drivebase_core.c +218 -0
  13. openbricks-0.10.2/native/_shared/drivebase_core.h +138 -0
  14. openbricks-0.10.2/native/_shared/motor_process_core.c +69 -0
  15. openbricks-0.10.2/native/_shared/motor_process_core.h +87 -0
  16. openbricks-0.10.2/native/_shared/observer_core.c +42 -0
  17. openbricks-0.10.2/native/_shared/observer_core.h +26 -0
  18. openbricks-0.10.2/native/_shared/servo_core.c +132 -0
  19. openbricks-0.10.2/native/_shared/servo_core.h +121 -0
  20. openbricks-0.10.2/native/_shared/trajectory_core.c +111 -0
  21. openbricks-0.10.2/native/_shared/trajectory_core.h +69 -0
  22. openbricks-0.10.2/native/openbricks_sim_native.c +714 -0
  23. openbricks-0.10.2/openbricks.egg-info/PKG-INFO +144 -0
  24. openbricks-0.10.2/openbricks.egg-info/SOURCES.txt +78 -0
  25. openbricks-0.10.2/openbricks.egg-info/dependency_links.txt +1 -0
  26. openbricks-0.10.2/openbricks.egg-info/entry_points.txt +2 -0
  27. openbricks-0.10.2/openbricks.egg-info/requires.txt +12 -0
  28. openbricks-0.10.2/openbricks.egg-info/top_level.txt +2 -0
  29. openbricks-0.10.2/openbricks_dev/__init__.py +4 -0
  30. openbricks-0.10.2/openbricks_dev/__main__.py +7 -0
  31. openbricks-0.10.2/openbricks_dev/_nus.py +157 -0
  32. openbricks-0.10.2/openbricks_dev/cli.py +274 -0
  33. openbricks-0.10.2/openbricks_dev/flash.py +177 -0
  34. openbricks-0.10.2/openbricks_dev/log.py +104 -0
  35. openbricks-0.10.2/openbricks_dev/run.py +260 -0
  36. openbricks-0.10.2/openbricks_dev/scan.py +99 -0
  37. openbricks-0.10.2/openbricks_dev/stop.py +53 -0
  38. openbricks-0.10.2/openbricks_dev/upload.py +117 -0
  39. openbricks-0.10.2/openbricks_sim/__init__.py +4 -0
  40. openbricks-0.10.2/openbricks_sim/__main__.py +7 -0
  41. openbricks-0.10.2/openbricks_sim/chassis.py +222 -0
  42. openbricks-0.10.2/openbricks_sim/cli.py +226 -0
  43. openbricks-0.10.2/openbricks_sim/robot.py +346 -0
  44. openbricks-0.10.2/openbricks_sim/runtime.py +563 -0
  45. openbricks-0.10.2/openbricks_sim/shim.py +618 -0
  46. openbricks-0.10.2/openbricks_sim/world.py +110 -0
  47. openbricks-0.10.2/pyproject.toml +100 -0
  48. openbricks-0.10.2/setup.cfg +4 -0
  49. openbricks-0.10.2/setup.py +116 -0
  50. openbricks-0.10.2/tests/test_chassis.py +91 -0
  51. openbricks-0.10.2/tests/test_cli.py +187 -0
  52. openbricks-0.10.2/tests/test_color_sensor.py +136 -0
  53. openbricks-0.10.2/tests/test_distance_sensor.py +163 -0
  54. openbricks-0.10.2/tests/test_flash.py +178 -0
  55. openbricks-0.10.2/tests/test_imu.py +94 -0
  56. openbricks-0.10.2/tests/test_log.py +137 -0
  57. openbricks-0.10.2/tests/test_native_drivebase.py +252 -0
  58. openbricks-0.10.2/tests/test_native_motor_process.py +84 -0
  59. openbricks-0.10.2/tests/test_native_observer.py +97 -0
  60. openbricks-0.10.2/tests/test_native_servo.py +159 -0
  61. openbricks-0.10.2/tests/test_native_trajectory.py +126 -0
  62. openbricks-0.10.2/tests/test_robot.py +266 -0
  63. openbricks-0.10.2/tests/test_run.py +232 -0
  64. openbricks-0.10.2/tests/test_runtime.py +229 -0
  65. openbricks-0.10.2/tests/test_scan.py +114 -0
  66. openbricks-0.10.2/tests/test_sdist_build.py +123 -0
  67. openbricks-0.10.2/tests/test_shim.py +269 -0
  68. openbricks-0.10.2/tests/test_sim_cli.py +199 -0
  69. openbricks-0.10.2/tests/test_stop.py +79 -0
  70. openbricks-0.10.2/tests/test_upload.py +192 -0
  71. openbricks-0.10.2/tests/test_world.py +78 -0
  72. openbricks-0.10.2/worlds/wro_2026_elementary_robot_rockstars/README.md +41 -0
  73. openbricks-0.10.2/worlds/wro_2026_elementary_robot_rockstars/mat.png +0 -0
  74. openbricks-0.10.2/worlds/wro_2026_elementary_robot_rockstars/world.xml +229 -0
  75. openbricks-0.10.2/worlds/wro_2026_junior_heritage_heroes/README.md +38 -0
  76. openbricks-0.10.2/worlds/wro_2026_junior_heritage_heroes/mat.png +0 -0
  77. openbricks-0.10.2/worlds/wro_2026_junior_heritage_heroes/world.xml +228 -0
  78. openbricks-0.10.2/worlds/wro_2026_senior_mosaic_masters/README.md +38 -0
  79. openbricks-0.10.2/worlds/wro_2026_senior_mosaic_masters/mat.png +0 -0
  80. 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