diff --git a/README.md b/README.md
index 8a45a97..d71a46e 100644
--- a/README.md
+++ b/README.md
@@ -170,10 +170,11 @@ unmodified. The sim dependencies live in a separate pixi environment so the
robot install stays lean:
```bash
-pixi run -e sim sim # headless gz + robot + controllers
+pixi run sim # headless gz + robot + controllers
+pixi run teleop # drive it around
+# Ad-hoc commands need the sim environment named explicitly:
pixi run -e sim -- ros2 launch mote_bringup slam_launch.py use_sim_time:=true
pixi run -e sim -- gz sim -g # optional: attach the Gazebo GUI
-pixi run teleop # drive it around
```
The world (`mote_bringup/worlds/mote_world.sdf`) is a simple walled room with
@@ -192,12 +193,10 @@ For now, I currently develop on a workstation and push to the Pi with rsync. The
[`pixi.toml`](pixi.toml) `[tasks]` `sync` entry to match your Pi, then:
```bash
-pixi run sync
+pixi run sync # one-shot push
+pixi run sync-watch # keep pushing on every save (needs the dev env)
```
-I want to try [pixi pack](https://pixi.prefix.dev/latest/deployment/pixi_pack/)
-for this eventually but haven't had a chance yet.
-
## SO-101 Follower Arm

diff --git a/design/ASSEMBLY.md b/design/ASSEMBLY.md
new file mode 100644
index 0000000..f66a99f
--- /dev/null
+++ b/design/ASSEMBLY.md
@@ -0,0 +1,79 @@
+# Printing & Assembly
+
+How to print the parts and put Mote together. Wiring is covered separately in
+[WIRING.md](WIRING.md); the part files live in
+[`step/`](step/) (editable in CAD), [`stl/`](stl/) (mesh) and
+[`3mf/`](3mf/) (mesh + print setup).
+
+> ⚠️ Print settings below are sensible starting points, not measured-optimal —
+> confirm against your printer/filament. The `.3mf` files carry their own plate
+> setup; prefer them if your slicer reads 3mf.
+
+## Printing
+
+All parts fit a 256 mm bed (the chassis is 235 mm — see
+[design rationale](README.md#chassis-diameter-235mm)).
+
+Suggested defaults: **PLA**, 0.2 mm layer height, 3 walls, 15–20% infill, brim on
+the large flat plates to prevent corner lift.
+
+| Part (qty) | Material |
+| --------------------------- | -------------- |
+| Chassis Base (1) | PLA |
+| Chassis Top (1) | PLA |
+| Motor Support (2) | PLA |
+| Pi Bottom + Pi Top (1 each) | PLA |
+| Waveshare Mount (1) | PLA |
+| C1 Lidar Mount (1) | PLA |
+| Camera Mount (1) | PLA |
+| Battery Mount (1) | PLA |
+| Wheel Inner (2) | PLA |
+| Wheel Tyre (2) | **TPU** (grip) |
+| Caster (1) | PLA |
+| SO Base ORP (1, optional) | PLA |
+
+Notes:
+
+- **Wheels are two-part**: a rigid `Wheel Inner` hub plus a `Wheel Tyre` printed
+ in **TPU** for traction.
+- **Caster is unresolved** — printed and several off-the-shelf options have all
+ been unsatisfactory so far; treat this part as provisional. A bought ball
+ caster of the right height may be preferable (see Assembly step 6).
+
+## Assembly
+
+**Hardware:** the M3 button-head hex set from the [BOM](BOM.md) (screws, nuts)
+covers all fasteners. Most joints take **M3×12**: the screw passes through a ~6
+mm plate into a part with a ~6 mm captive-nut pocket. Use **M3×10** for thinner
+stacks.
+
+> **Note:** I am not convinced yet that screws and nuts are the optimal choice
+> here. Nuts have a tendency to loosen up due to vibrations while driving around
+> so we may want something more reliable.
+
+Suggested order:
+
+1. **Servos.** Mount each STS3215 to the `Motor Support`, then press a
+ `Wheel Inner` onto the servo horn and fit the `Wheel Tyre` over it. Wheels
+ are centred and inset so the footprint stays circular. It's easiest to set
+ the servo ID's before going further. Left servo is **ID 7**, right is **ID
+ 9** (assign with `pixi run setup-ids` — see the
+ [README](../README.md#4-configure-the-servos)).
+2. **Lower plate.** Fasten the `Motor Support`/servos, the `Battery Mount`, the
+ `Waveshare Mount`, and the `C1 Lidar Mount`, to the `Chassis Base`.
+3. **Caster.** Fit the front `Caster` to the underside of the base for the third
+ contact point (provisional — see Printing notes).
+4. **Sensors.** Mount the camera in the `Camera Mount`.
+5. **Top plate.** Attach the `Pi Bottom` holder and the `Camera/Camera Mount` to
+ the `Chassis Top`.
+6. **Close it up.** Fix the `Chassis Top` onto the standoffs.
+7. **Power bank.** Seat the power bank in the `Battery Mount` between the
+ plates.
+8. **Wire it.** Follow [WIRING.md](WIRING.md): bank Out1 → servo board (barrel),
+ bank Out2 → Pi, servo board USB → Pi, lidar/camera USB → Pi. Route cables
+ through the standoff gap and keep the lidar's 360° view clear.
+9. **(Optional) SO-101 arm.** The `SO Base ORP` adapter mounts the
+ [SO-101 follower arm](https://github.com/TheRobotStudio/SO-ARM100) on the ORP
+ grid.
+
+Then bring the software up per the [main README](../README.md#5-launch).
diff --git a/design/README.md b/design/README.md
index 50585d0..e5364c5 100644
--- a/design/README.md
+++ b/design/README.md
@@ -51,3 +51,8 @@ Out1 to stop them stalling. The Pi runs fine on the 45W port.
## Bill of Materials
See [BOM.md](BOM.md).
+
+## Build guides
+
+- [WIRING.md](WIRING.md) — connections, power topology and budget, IMU pinout.
+- [ASSEMBLY.md](ASSEMBLY.md) — print settings per part and assembly steps.
diff --git a/design/WIRING.md b/design/WIRING.md
new file mode 100644
index 0000000..8bd4950
--- /dev/null
+++ b/design/WIRING.md
@@ -0,0 +1,80 @@
+# Wiring & Power
+
+How the Mote electronics connect. Everything runs from a single USB-C power
+bank at **5 V** (see [the 5 V-only rationale](README.md#power-5v-only)). Device
+names in the launch stack (`/dev/mote_*`) are created by the udev rules in
+[`mote_bringup/udev/`](../mote_bringup/udev/).
+
+Every data link is plain USB (standard male plugs into the device ports), so
+there's no special connector wiring — just the cables in the [BOM](BOM.md).
+A few electrical details still want bench confirmation; those are marked **Verify**.
+
+## Diagram
+
+```mermaid
+flowchart LR
+ BANK["UGREEN 140W power bank"]
+ BANK -->|"Out1 high-current · USB-C to DC barrel · 5V"| MCB["Waveshare Serial Bus
Servo Driver Board"]
+ BANK -->|"Out2 · USB-C to USB-C · 5V"| PI["Raspberry Pi 5"]
+
+ MCB ==>|"3-pin bus: 5V + serial"| SL["Left STS3215
ID 7, inverted"]
+ MCB ==>|"3-pin bus: 5V + serial"| SR["Right STS3215
ID 9"]
+
+ MCB -->|"USB serial to /dev/mote_servos"| PI
+ LIDAR["RPLIDAR C1"] -->|"USB to /dev/mote_lidar"| PI
+ CAM["USB webcam"] -->|"USB to /dev/mote_camera"| PI
+ IMU["BNO085 IMU
in testing"] -.->|"I2C to GPIO"| PI
+
+ classDef power fill:#cde,stroke:#369;
+ class BANK,MCB,PI power;
+```
+
+Solid lines = power and/or wired data. The lidar, camera and IMU are powered by
+the Pi over their data connection (USB / GPIO), not from the bank directly.
+
+## Connections
+
+### Power
+
+| From (port) | Cable | To | Carries |
+| --------------------------- | ------------------------------------- | ------------------------ | ------- |
+| Bank **Out1** (100 W USB-C) | USB-C → DC 5.5×2.1 mm barrel, **5 V** | Servo board DC input | 5 V |
+| Bank **Out2** (45 W USB-C) | USB-C ↔ USB-C, 0.3 m | Pi 5 USB-C power in | 5 V |
+| Servo board | 3-pin servo lead | Left STS3215 (**ID 7**) | 5 V |
+| Servo board | 3-pin servo lead | Right STS3215 (**ID 9**) | 5 V |
+
+### Data
+
+| From | Cable | To | Device node | Notes |
+| ------------------------ | ------------------------------ | -------- | ------------------ | ----------------------------- |
+| Servo board (USB-C data) | USB-A ↔ USB-C, 0.3 m | Pi USB-A | `/dev/mote_servos` | CH343 USB-serial, 1 Mbaud |
+| RPLIDAR C1 | USB (cable supplied with unit) | Pi USB-A | `/dev/mote_lidar` | 460800 baud; powered over USB |
+| USB webcam | USB-A (captive) | Pi USB-A | `/dev/mote_camera` | UVC; powered over USB |
+
+The Pi 5 has 4 USB ports (2× USB-3, 2× USB-2). Servo board, lidar and camera
+take three of them.
+
+## Power notes (the fiddly bit)
+
+Three things make the 5 V budget non-obvious:
+
+1. **Servos run at 5 V** The STS3215's
+ [datasheet](https://www.feetechrc.com/Data/feetechrc/upload/file/20260622/6391772523943436695270694.pdf)
+ operating range is **4 V - 7.4V**, with the expected value of 6V or 7.4V.
+ Mote feeds the servo board **5 V** and this seems to work well. Just don't
+ expect the datasheet torque numbers, since those are quoted at the higher
+ voltage.
+
+2. **A "100 W" USB-C port is not 100 W at 5 V.** USB-C PD allows devices to
+ negotiate different voltage/current depending on need. Focusing on 5V, the
+ spec allows requesting up to 3A, however it is common for supplies to allow
+ up to 5A. The Pi explicitly requests 5V/5A = 25W. The servos only need 500mA.
+
+### Rough 5 V budget
+
+| Load | Rail | Typical | Peak | Fed from |
+| -------------------- | -------------- | --------------------- | -------------- | -------- |
+| Raspberry Pi 5 | 5 V | 5–10 W | 25 W (5 V/5 A) | Out2 |
+| 2× STS3215 (driving) | 5 V via board | 1-2 W (130mA no load) | 10W (2A stall) | Out1 |
+| RPLIDAR C1 | 5 V via Pi USB | 1.15 W (230 mA) | — | Pi |
+| USB webcam | 5 V via Pi USB | ~0.5–1 W | — | Pi |
diff --git a/pixi.lock b/pixi.lock
index 4a96cb4..ae7b0aa 100644
--- a/pixi.lock
+++ b/pixi.lock
@@ -1908,6 +1908,7 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-64/vtk-9.5.2-py312h244374b_7.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/vtk-base-9.5.2-py312h6fba518_7.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/vtk-io-ffmpeg-9.5.2-py312hcdbd8b1_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/watchexec-2.5.1-hb17b654_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.25.0-hd6090a7_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/wrapt-2.2.1-py312h4c3975b_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/x264-1!164.3095-h166bdaf_2.tar.bz2
@@ -2952,6 +2953,7 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/vtk-9.5.2-py312h4954c87_7.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/vtk-base-9.5.2-py312h90a26f6_7.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/vtk-io-ffmpeg-9.5.2-py312haba1314_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/watchexec-2.5.1-h069e38c_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/wayland-1.25.0-h4f8a99f_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/wrapt-2.2.1-py312hcd1a082_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/x264-1!164.3095-h4e544f5_2.tar.bz2
@@ -3544,6 +3546,95 @@ environments:
- conda: https://conda.anaconda.org/robostack-jazzy/linux-aarch64/ros-jazzy-zstd-vendor-0.26.9-np2py312h61f2ce4_16.conda
- conda: https://conda.anaconda.org/robostack-jazzy/linux-aarch64/ros2-distro-mutex-0.14.0-jazzy_16.conda
- conda: https://prefix.dev/mote/linux-aarch64/scservo-linux-1.0.0-he8cfe8b_0.conda
+ lint:
+ channels:
+ - url: https://prefix.dev/mote/
+ - url: https://conda.anaconda.org/robostack-jazzy/
+ - url: https://conda.anaconda.org/conda-forge/
+ packages:
+ linux-64:
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_100_cp314.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py314h9891dd4_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.3-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.0-pyha770c72_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.4.2-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.4.3-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda
+ linux-aarch64:
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py314h0bd77cf_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.8.1-hfae3067_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_19.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_19.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.3-he30d5cf_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.53.2-h10b116e_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_19.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42.1-h1022ec0_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.6-hf8d1292_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.3-h546c87b_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.6-hc679e19_100_cp314.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py314hd7d8586_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.3-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.0-pyha770c72_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.4.2-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.4.3-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda
sim:
channels:
- url: https://prefix.dev/mote/
@@ -5926,6 +6017,20 @@ packages:
license_family: MIT
size: 295716
timestamp: 1761202958833
+- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda
+ sha256: c6339858a0aaf5d939e00d345c98b99e4558f285942b27232ac098ad17ac7f8e
+ md5: cf45f4278afd6f4e6d03eda0f435d527
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libffi >=3.5.2,<3.6.0a0
+ - libgcc >=14
+ - pycparser
+ - python >=3.14,<3.15.0a0
+ - python_abi 3.14.* *_cp314
+ license: MIT
+ license_family: MIT
+ size: 300271
+ timestamp: 1761203085220
- conda: https://conda.anaconda.org/conda-forge/linux-64/cfitsio-4.6.3-ha0b56bc_0.conda
sha256: 31beae4b063f5e39ade020d3ee1ade0fdc8d5556fafac4d004ebe74ffbad44dd
md5: 0fb449e4da07934a40db021c84773ed0
@@ -9422,6 +9527,16 @@ packages:
license_family: LGPL
size: 287897
timestamp: 1779091106723
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda
+ sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843
+ md5: 2c21e66f50753a083cbe6b80f38268fa
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ license: BSD-2-Clause
+ license_family: BSD
+ size: 92400
+ timestamp: 1769482286018
- conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.3-nompi_hbf2fc22_104.conda
sha256: cae2f8fe5258fc1a1d2b61cbc9190ed2c0a1b7cdf5d4aac98da071ade6dac152
md5: a2956b63b1851e9d5eb9f882d02fa3a9
@@ -11598,6 +11713,33 @@ packages:
license: Python-2.0
size: 31608571
timestamp: 1772730708989
+- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_100_cp314.conda
+ build_number: 100
+ sha256: 6d28ac2b061179deb434d3d57afa98ffd20ec3c5d44ab8048a1ca33424b22d38
+ md5: 0b9b2f83b5b600e1ac38becde8d0dd44
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - bzip2 >=1.0.8,<2.0a0
+ - ld_impl_linux-64 >=2.36.1
+ - libexpat >=2.8.1,<3.0a0
+ - libffi >=3.5.2,<3.6.0a0
+ - libgcc >=14
+ - liblzma >=5.8.3,<6.0a0
+ - libmpdec >=4.0.0,<5.0a0
+ - libsqlite >=3.53.2,<4.0a0
+ - libuuid >=2.42.1,<3.0a0
+ - libzlib >=1.3.2,<2.0a0
+ - ncurses >=6.6,<7.0a0
+ - openssl >=3.5.7,<4.0a0
+ - python_abi 3.14.* *_cp314
+ - readline >=8.3,<9.0a0
+ - tk >=8.6.13,<8.7.0a0
+ - tzdata
+ - zstd >=1.5.7,<1.6.0a0
+ license: Python-2.0
+ size: 36717183
+ timestamp: 1781255094700
+ python_site_packages_path: lib/python3.14/site-packages
- conda: https://conda.anaconda.org/conda-forge/linux-64/python-orocos-kdl-1.5.3-py312h1289d80_2.conda
sha256: 481d724d6f7bbfa931f6f33a7940d3637b7289139526a5952d2289c114c5f5b6
md5: 30d805f6312812a6abef0933e56deefa
@@ -11627,6 +11769,19 @@ packages:
license_family: MIT
size: 198293
timestamp: 1770223620706
+- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda
+ sha256: b318fb070c7a1f89980ef124b80a0b5ccf3928143708a85e0053cde0169c699d
+ md5: 2035f68f96be30dc60a5dfd7452c7941
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ - python >=3.14,<3.15.0a0
+ - python_abi 3.14.* *_cp314
+ - yaml >=0.2.5,<0.3.0a0
+ license: MIT
+ license_family: MIT
+ size: 202391
+ timestamp: 1770223462836
- conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda
sha256: 776363493bad83308ba30bcb88c2552632581b143e8ee25b1982c8c743e73abc
md5: 353823361b1d27eb3960efb076dfcaf6
@@ -12218,6 +12373,20 @@ packages:
license_family: BSD
size: 71525
timestamp: 1776960300375
+- conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py314h9891dd4_0.conda
+ sha256: c84034056dc938c853e4f61e72e5bd37e2ec91927a661fb9762f678cbea52d43
+ md5: 5d3c008e54c7f49592fca9c32896a76f
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - cffi
+ - libgcc >=14
+ - libstdcxx >=14
+ - python >=3.14,<3.15.0a0
+ - python_abi 3.14.* *_cp314
+ license: MIT
+ license_family: MIT
+ size: 15004
+ timestamp: 1769438727085
- conda: https://conda.anaconda.org/conda-forge/linux-64/uncrustify-0.83.0-h54a6638_0.conda
sha256: 556ea427c375c2b9eb3122b19a1ce0891d535ad51a196f25c73786bb1d7c5156
md5: 29f7fdf10a9604e18924eebbde71ce62
@@ -12395,6 +12564,18 @@ packages:
license_family: BSD
size: 108379
timestamp: 1768718074243
+- conda: https://conda.anaconda.org/conda-forge/linux-64/watchexec-2.5.1-hb17b654_0.conda
+ sha256: 5f2140f7b6a18138cad7a305b4185bea8499a4e044afb91e3fad72a130255d3b
+ md5: e5b552a8e3cc6084bce363770ae18c46
+ depends:
+ - libgcc >=14
+ - __glibc >=2.17,<3.0.a0
+ constrains:
+ - __glibc >=2.17
+ license: Apache-2.0
+ license_family: APACHE
+ size: 3311519
+ timestamp: 1774855010015
- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.25.0-hd6090a7_0.conda
sha256: ea374d57a8fcda281a0a89af0ee49a2c2e99cc4ac97cf2e2db7064e74e764bdb
md5: 996583ea9c796e5b915f7d7580b51ea6
@@ -13622,6 +13803,19 @@ packages:
license_family: MIT
size: 315113
timestamp: 1761203960926
+- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py314h0bd77cf_1.conda
+ sha256: 728e55b32bf538e792010308fbe55d26d02903ddc295fbe101167903a123dd6f
+ md5: f333c475896dbc8b15efd8f7c61154c7
+ depends:
+ - libffi >=3.5.2,<3.6.0a0
+ - libgcc >=14
+ - pycparser
+ - python >=3.14,<3.15.0a0
+ - python_abi 3.14.* *_cp314
+ license: MIT
+ license_family: MIT
+ size: 318357
+ timestamp: 1761203973223
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cfitsio-4.6.3-hf475483_0.conda
sha256: dca79e594c6326751291e1c0b47a92ef0bd2c0ad067105b0971fec2c73d3cf72
md5: fcc37a4d1fde95e2106059dd7ad3ec13
@@ -16979,6 +17173,15 @@ packages:
license_family: LGPL
size: 296558
timestamp: 1779091126550
+- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda
+ sha256: 57c0dd12d506e84541c4e877898bd2a59cca141df493d34036f18b2751e0a453
+ md5: 7b9813e885482e3ccb1fa212b86d7fd0
+ depends:
+ - libgcc >=14
+ license: BSD-2-Clause
+ license_family: BSD
+ size: 114056
+ timestamp: 1769482343003
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnetcdf-4.9.3-nompi_h06de00c_104.conda
sha256: bf4d494f37744f27c474cfcaafe65e8680c117137ed45fc4866dfe82ad9aa4dc
md5: 76ed52bab2a733e0eb5f08b8ec3da215
@@ -18990,6 +19193,32 @@ packages:
license: Python-2.0
size: 13757191
timestamp: 1772728951853
+- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.6-hc679e19_100_cp314.conda
+ build_number: 100
+ sha256: dd56fd95db3cb49a69fbe41df80afc8bd5214daa829bcd3930de80f0408ba5eb
+ md5: 416c74941d13d9f2b9e68b1a900f7f50
+ depends:
+ - bzip2 >=1.0.8,<2.0a0
+ - ld_impl_linux-aarch64 >=2.36.1
+ - libexpat >=2.8.1,<3.0a0
+ - libffi >=3.5.2,<3.6.0a0
+ - libgcc >=14
+ - liblzma >=5.8.3,<6.0a0
+ - libmpdec >=4.0.0,<5.0a0
+ - libsqlite >=3.53.2,<4.0a0
+ - libuuid >=2.42.1,<3.0a0
+ - libzlib >=1.3.2,<2.0a0
+ - ncurses >=6.6,<7.0a0
+ - openssl >=3.5.7,<4.0a0
+ - python_abi 3.14.* *_cp314
+ - readline >=8.3,<9.0a0
+ - tk >=8.6.13,<8.7.0a0
+ - tzdata
+ - zstd >=1.5.7,<1.6.0a0
+ license: Python-2.0
+ size: 34900936
+ timestamp: 1781254861576
+ python_site_packages_path: lib/python3.14/site-packages
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-orocos-kdl-1.5.3-py312h1ab2c47_2.conda
sha256: 6a5a07185a4509ab13dcef98594d041081dd70d26b23651f4c86cc289f6217f6
md5: 574358b8f203748e7c46ff70a908ee76
@@ -19019,6 +19248,19 @@ packages:
license_family: MIT
size: 192182
timestamp: 1770223431156
+- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda
+ sha256: 496b5e65dfdd0aaaaa5de0dcaaf3bceea00fcb4398acf152f89e567c82ec1046
+ md5: 9ae2c92975118058bd720e9ba2bb7c58
+ depends:
+ - libgcc >=14
+ - python >=3.14,<3.15.0a0
+ - python >=3.14,<3.15.0a0 *_cp314
+ - python_abi 3.14.* *_cp314
+ - yaml >=0.2.5,<0.3.0a0
+ license: MIT
+ license_family: MIT
+ size: 195678
+ timestamp: 1770223441816
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/qhull-2020.2-h70be974_5.conda
sha256: 49f777bdf3c5e030a8c7b24c58cdfe9486b51d6ae0001841079a3228bdf9fb51
md5: bb138086d938e2b64f5f364945793ebf
@@ -19559,6 +19801,20 @@ packages:
license_family: BSD
size: 73779
timestamp: 1776960577002
+- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py314hd7d8586_0.conda
+ sha256: 0a7efe469d7e2a34a1d017bc51cf6fb9a51436730256983694c5ad74a33bd4e0
+ md5: f3a967efabf9cf450b7741487318ea6e
+ depends:
+ - cffi
+ - libgcc >=14
+ - libstdcxx >=14
+ - python >=3.14,<3.15.0a0
+ - python >=3.14,<3.15.0a0 *_cp314
+ - python_abi 3.14.* *_cp314
+ license: MIT
+ license_family: MIT
+ size: 15756
+ timestamp: 1769438772414
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/uncrustify-0.83.0-h7ac5ae9_0.conda
sha256: e2b3719965204cc8c0fecaa685c96b527da168f97eb13a7e36fc7d8a6876c2b0
md5: 6a3d6acb5c7d4c203a33b14b8925c6c7
@@ -19731,6 +19987,17 @@ packages:
license_family: BSD
size: 106747
timestamp: 1768718080474
+- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/watchexec-2.5.1-h069e38c_0.conda
+ sha256: 597470409a6cb80d85a2c433d30b91fbc542ccb8096a3aadcf084955e5a883ac
+ md5: 7e089d2d3ac6cc22b9ee91563116a028
+ depends:
+ - libgcc >=14
+ constrains:
+ - __glibc >=2.17
+ license: Apache-2.0
+ license_family: APACHE
+ size: 3164356
+ timestamp: 1774855038268
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/wayland-1.25.0-h4f8a99f_0.conda
sha256: 3cc479df517b0ce110835a1256f91ca568581cb6dfe1c53a0786f0a226039a45
md5: 0a7a9548726f98d5869fd4c43e110f0f
@@ -20342,6 +20609,15 @@ packages:
license_family: BSD
size: 54106
timestamp: 1757558592553
+- conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda
+ sha256: aa589352e61bb221351a79e5946d56916e3c595783994884accdb3b97fe9d449
+ md5: 381bd45fb7aa032691f3063aff47e3a1
+ depends:
+ - python >=3.10
+ license: MIT
+ license_family: MIT
+ size: 13589
+ timestamp: 1763607964133
- conda: https://conda.anaconda.org/conda-forge/noarch/colcon-argcomplete-0.3.3-pyhd8ed1ab_1.conda
sha256: 05ccb85cad9ca58be9dcb74225f6180a68907a6ab0c990e3940f4decc5bb2280
md5: bde6042a1b40a2d4021e1becbe8dd84f
@@ -20641,6 +20917,16 @@ packages:
license_family: APACHE
size: 303675
timestamp: 1780988738861
+- conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda
+ sha256: e2753997b8bd34205f42be01b8bab8037423dc30c02a1ec12de23e5b4c0b0a2e
+ md5: 58638f77697c4f6726753eb8be34818b
+ depends:
+ - python >=3.10
+ - python
+ license: Apache-2.0
+ license_family: APACHE
+ size: 303705
+ timestamp: 1781320269259
- conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda
sha256: 5603c7d0321963bb9b4030eadabc3fd7ca6103a38475b4e0ed13ed6d97c86f4e
md5: 0a2014fd9860f8b1eaa0b1f3d3771a08
@@ -20838,6 +21124,16 @@ packages:
license_family: MIT
size: 73563
timestamp: 1733928021866
+- conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda
+ sha256: 381cedccf0866babfc135d65ee40b778bd20e927d2a5ec810f750c5860a7c5b8
+ md5: 84a3233b709a289a4ddd7a2fd27dd988
+ depends:
+ - python >=3.10
+ - ukkonen
+ license: MIT
+ license_family: MIT
+ size: 79757
+ timestamp: 1776455344188
- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.15-pyhcf101f3_0.conda
sha256: 3d25f9f6f7ab3e1ce6429fc8c8aae0335cf446692e715068488536d220cc43de
md5: 1b9083b7f00609605d1483dbc6071a81
@@ -21002,6 +21298,16 @@ packages:
license_family: Apache
size: 15851
timestamp: 1749895533014
+- conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda
+ sha256: 4fa40e3e13fc6ea0a93f67dfc76c96190afd7ea4ffc1bac2612d954b42cdc3ee
+ md5: eb52d14a901e23c39e9e7b4a1a5c015f
+ depends:
+ - python >=3.10
+ - setuptools
+ license: BSD-3-Clause
+ license_family: BSD
+ size: 40866
+ timestamp: 1766261270149
- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda
sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890
md5: 4c06a92e74452cfa53623a81592e8934
@@ -21022,6 +21328,16 @@ packages:
license_family: MIT
size: 19044
timestamp: 1667916747996
+- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda
+ sha256: 9e5e1fd3506ccfc4d444fc4d2d39b0ed097d5d0e3bd3d4bdf6bcc81aaf66860d
+ md5: 2c5ef45db85d34799771629bd5860fd7
+ depends:
+ - python >=3.10
+ - python
+ license: MIT
+ license_family: MIT
+ size: 26308
+ timestamp: 1779972894916
- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda
sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e
md5: d7585b6550ad04c8c5e21097ada2888e
@@ -21048,6 +21364,20 @@ packages:
license_family: OTHER
size: 2348171
timestamp: 1675353652214
+- conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.0-pyha770c72_0.conda
+ sha256: 716960bf0a9eb334458a26b3bdcb17b8d0786062138a4f48c7f335c8418c5d0b
+ md5: 7859736b4f8ebe6c8481bf48d91c9a1e
+ depends:
+ - cfgv >=2.0.0
+ - identify >=1.0.0
+ - nodeenv >=0.11.1
+ - python >=3.10
+ - pyyaml >=5.1
+ - virtualenv >=20.10.0
+ license: MIT
+ license_family: MIT
+ size: 201606
+ timestamp: 1776858157327
- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.3-pyhfe8187e_0.conda
sha256: 71a9524f44d6ac6304feae71e2bbe8d8ce0816f0be7a0271c15681ad1040965d
md5: e0f4549ccb507d4af8ed5c5345210673
@@ -21238,6 +21568,18 @@ packages:
license_family: APACHE
size: 233310
timestamp: 1751104122689
+- conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.4.2-pyhcf101f3_0.conda
+ sha256: 6914da740f6e3ec44ffb2f687dbc9c33abf084e42f34e3a8bb8235e475850619
+ md5: 7a9095c9300d1b50b1785ca9bc4cadae
+ depends:
+ - python >=3.10
+ - filelock >=3.15.4
+ - platformdirs <5,>=4.3.6
+ - python
+ license: MIT
+ license_family: MIT
+ size: 35514
+ timestamp: 1781257630962
- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda
build_number: 8
sha256: 80677180dd3c22deb7426ca89d6203f1c7f1f256f2d5a94dc210f6e758229809
@@ -21248,6 +21590,16 @@ packages:
license_family: BSD
size: 6958
timestamp: 1752805918820
+- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda
+ build_number: 8
+ sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5
+ md5: 0539938c55b6b1a59b560e843ad864a4
+ constrains:
+ - python 3.14.* *_cp314
+ license: BSD-3-Clause
+ license_family: BSD
+ size: 6989
+ timestamp: 1752805904792
- conda: https://conda.anaconda.org/conda-forge/noarch/rosdistro-1.0.1-pyhd8ed1ab_0.conda
sha256: bff3b2fe7afe35125669ffcb7d6153db78070a753e1e4ac3b3d8d198eb6d6982
md5: b7ed380a9088b543e06a4f73985ed03a
@@ -21282,6 +21634,15 @@ packages:
license_family: MIT
size: 787541
timestamp: 1745484086827
+- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda
+ sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1
+ md5: 8e194e7b992f99a5015edbd4ebd38efd
+ depends:
+ - python >=3.10
+ license: MIT
+ license_family: MIT
+ size: 639697
+ timestamp: 1773074868565
- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda
sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d
md5: 3339e3b65d58accf4ca4fb8748ab16b3
@@ -21381,6 +21742,22 @@ packages:
license: LicenseRef-Public-Domain
size: 119135
timestamp: 1767016325805
+- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.4.3-pyhcf101f3_0.conda
+ sha256: 72ac3da92b6417ad9667009b881ee75e0ca06ef5850f8781df54b27bd6f8be3d
+ md5: 84270b1d45569be1f9d2c8fe674993a4
+ depends:
+ - python >=3.10
+ - distlib >=0.3.7,<1
+ - filelock <4,>=3.24.2
+ - importlib-metadata >=6.6
+ - platformdirs >=3.9.1,<5
+ - python-discovery >=1.4.2
+ - typing_extensions >=4.13.2
+ - python
+ license: MIT
+ license_family: MIT
+ size: 5176090
+ timestamp: 1781267575706
- conda: https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.47-hd8ed1ab_0.conda
sha256: 9ab2c12053ea8984228dd573114ffc6d63df42c501d59fda3bf3aeb1eaa1d23e
md5: 7da1571f560d4ba3343f7f4c48a79c76
diff --git a/pixi.toml b/pixi.toml
index 3c1b184..19c994f 100644
--- a/pixi.toml
+++ b/pixi.toml
@@ -73,9 +73,14 @@ ros-jazzy-ament-cmake-gtest = ">=2.5"
[feature.dev.dependencies]
ros-jazzy-desktop = ">=0.11.0"
ros-jazzy-nav2-bringup = ">=1.3.11,<2"
+watchexec = ">=2.5,<3"
[feature.dev.tasks]
rviz = "ros2 launch mote_bringup rviz_launch.py"
+# Continuously rsync to the Pi on save (watchexec honours .gitignore, so
+# build/install/log/.pixi are skipped). Edits to launch/config/Python go live
+# without a rebuild thanks to colcon --symlink-install; only C++ needs `build`.
+sync-watch = "watchexec --debounce 1s -- pixi run sync"
[feature.sim.tasks]
sim = "ros2 launch mote_bringup sim_launch.py"