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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ logs/
src/pi/config.yaml
src/pi/ptyserver.service

# Python bytecode (a stale .pyc will happily hide a source change)
__pycache__/
*.py[cod]

# Compiled output
*.o
*.out
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,35 @@ rule watches for.
> work. Headers are only pre-soldered on the "H" variants; this project needs GP4,
> GP5 and GND, so a plain board means soldering.

#### Or let the Pi do step 1 for you (auto-flash)

`src/pi/PicoFirmwareFlasher.py` does the copy above by itself when a board in
BOOTSEL mode is plugged in. It is opt-in and off by default. To enable it on a Pi:

```bash
# 1. cache the firmware, named exactly like this (the script never downloads)
sudo mkdir -p /home/project/firmware
sudo cp RPI_PICO_W-<version>.uf2 /home/project/firmware/RPI_PICO_W.uf2
sudo cp RPI_PICO2_W-<version>.uf2 /home/project/firmware/RPI_PICO2_W.uf2 # if you have Pico 2 W boards

# 2. install the udev rule that starts the script
sudo cp src/pi/98-pico-bootsel.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules

# 3. the kill switch: nothing is ever flashed while this file is absent
sudo touch /home/project/firmware/autoflash-enabled
```

Then plug in a board in BOOTSEL mode (a fresh one is already in it; otherwise hold
**BOOTSEL** while plugging in, and let go once it is in). Within about fifteen
seconds it reboots as MicroPython and the existing `99-pico.rules` takes over.
Watch it with `tail -f /tmp/deployer.log` or `journalctl -f -u 'pico-flash-*'`.
Remove `autoflash-enabled` to switch it off again.

The script only touches a volume labelled `RPI-RP2` or `RP2350`, and it treats
the board vanishing mid-copy as success, because that is the board rebooting. To
run it by hand for a specific device: `sudo python3 src/pi/PicoFirmwareFlasher.py /dev/sda1`.

### Step 2: Plug it into the Pi

With MicroPython on board, just plug the Pico into the Pi's USB port. The udev
Expand Down
41 changes: 41 additions & 0 deletions src/pi/98-pico-bootsel.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 98-pico-bootsel.rules -- issue #19 Phase A.
# Tested 2026-09-08 on a Pi 4 Model B Rev 1.1 running Raspberry Pi OS Lite 64-bit
# (trixie): a Pico W in BOOTSEL was flashed with no manual intervention. Not yet
# installed by the installer; see the install steps below.
#
# Companion to 99-pico.rules. That rule matches 2e8a:0005, the serial interface
# a board presents once MicroPython is running. This one matches the step
# before that: a Pico whose flash is blank (or held in BOOTSEL) enumerates as a
# USB mass-storage device instead, and needs MicroPython copied onto it first.
#
# blank board --(this rule + PicoFirmwareFlasher.py)--> MicroPython
# --(99-pico.rules + PicoScriptDeployer.py)--> main.py
#
# Why systemd-run rather than calling the script straight from RUN+=:
# udev kills RUN+= commands that outlive its event timeout and runs them in a
# stripped-down environment. A UF2 copy plus filesystem-readiness retries can
# take longer than that, and udisksctl wants a normal environment. Handing
# off to a transient unit sidesteps both, and the flash then shows up in
# `journalctl -u 'pico-flash-*'` as well as /tmp/deployer.log.
#
# Install (manual for now; the installer does not know about this yet):
# sudo mkdir -p /home/project/firmware
# sudo cp RPI_PICO_W-<ver>.uf2 /home/project/firmware/RPI_PICO_W.uf2
# sudo cp RPI_PICO2_W-<ver>.uf2 /home/project/firmware/RPI_PICO2_W.uf2
# sudo cp src/pi/98-pico-bootsel.rules /etc/udev/rules.d/
# sudo udevadm control --reload-rules
# sudo touch /home/project/firmware/autoflash-enabled # kill switch: absent = do nothing
#
# Watch it:
# tail -f /tmp/deployer.log
# journalctl -f -u 'pico-flash-*'
#
# Matches the partition node (/dev/sda1), which is where the RPI-RP2 / RP2350
# filesystem lives and what udisksctl mounts. Confirmed on hardware: the
# bootloader volume is a partition (DEVTYPE=partition), not the whole disk.
# 0003 is the RP2040 bootloader, 000f the RP2350 bootloader; 2e8a is Raspberry
# Pi. ID_MODEL_ID is passed as an argument because systemd-run does not forward
# udev's environment into the unit. System python3 is enough: the script uses
# only the standard library, so the rshell venv is not needed here.

ACTION=="add", SUBSYSTEM=="block", ENV{DEVTYPE}=="partition", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003|000f", RUN+="/usr/bin/systemd-run --no-block --collect --unit=pico-flash-%k /usr/bin/python3 /home/project/remote-serial-pico/src/pi/PicoFirmwareFlasher.py %E{DEVNAME} %E{ID_MODEL_ID}"
Loading