Raspberry Pi Projects skill for Codex
Pi projects that boot, restart and survive power cuts. Codex writes Raspberry Pi code for current Raspberry Pi OS instead of 2019 tutorials: gpiozero rather than RPi.GPIO on the Pi 5, /boot/firmware paths, venvs that can still see apt hardware libraries, and a systemd unit that restarts the app without running it as root. It checks throttling and power before blaming the code.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- raspberry-pi Installs to ~/.agents/skills/raspberry-pi/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install raspberry-pi The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project raspberry-pi Installs to .agents/skills/raspberry-pi/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/raspberry-pi.zipunzip -o raspberry-pi.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) raspberry-pi PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $raspberry-pi, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds Raspberry Pi projects on Raspberry Pi OS Bookworm - GPIO with gpiozero and lgpio (Pi 5 safe), I2C/SPI enabled in /boot/firmware/config.txt, Python in a venv under PEP 668, systemd services that start on boot and restart on failure, and SD-card-friendly logging. Use when a Pi script fails on a Pi 5, RPi.GPIO errors, a sensor is missing on I2C, a program must run at boot, the Pi throttles or reboots, or the SD card keeps corrupting.
Use it when
- Porting GPIO code to a Pi 5 or Raspberry Pi OS Bookworm
- A Python program on the Pi needs to run at boot as a service
- An I2C or SPI device is not detected on the Pi
- The Pi throttles, reboots under load or corrupts its SD card
Use something else when
- The board is a Raspberry Pi Pico microcontroller running MicroPython: use micropython
- You are hardening a Pi as an internet-facing server: use linux-server-hardening
How to check the result
Reboot the Pi and confirm the service is active, kill the process to see systemd restart it, and check vcgencmd get_throttled returns 0x0 under load.
The mistake it prevents
Running the app with sudo to get GPIO access; add the service user to the gpio and i2c groups instead.
Done well means: the project installs on a fresh Raspberry Pi OS image from a script, runs as a supervised systemd service under a non-root user, survives power loss without a corrupted card, and works on both Pi 4 and Pi 5 unless the user said otherwise.
Inspect first
Ask the user to run these on the Pi (Codex usually works on a laptop clone of the repo) and paste the output, or run them yourself if the session is on the Pi:
cat /proc/device-tree/model; echo
cat /etc/os-release | grep VERSION_CODENAME # bookworm is the current baseline
uname -m # aarch64 vs armv7l
vcgencmd get_throttled # 0x0 is healthy
ls /dev/i2c-* /dev/spidev* 2>/dev/null
groups # needs gpio, i2c, spi for non-root access
Read AGENTS.md and any install script before changing dependencies.
Decisions that changed with Bookworm and the Pi 5
| Old habit | Current answer |
|---|---|
import RPi.GPIO | Does not work on Pi 5. Use gpiozero (default) or lgpio; rpi-lgpio is a drop-in shim for legacy code |
/boot/config.txt | /boot/firmware/config.txt |
sudo pip install | Refused (PEP 668). Use a venv; apt install python3-xyz for system packages |
raspi-gpio | pinctrl get 17, pinctrl set 17 op dh |
libcamera-still, picamera | rpicam-still, Picamera2 |
dhcpcd.conf static IP | NetworkManager: nmcli connection modify ... |
Hard-coded /dev/gpiochip0 or gpiochip4 | Let gpiozero pick; on the CLI find the chip with gpiodetect and gpioinfo |
Python environment
sudo apt install -y python3-venv python3-lgpio python3-gpiozero # network
python3 -m venv --system-site-packages ~/app/.venv # sees apt-installed lgpio
~/app/.venv/bin/pip install -r requirements.txt
--system-site-packages lets the venv use the apt builds of hardware libraries, which are
compiled for the Pi; installing those through pip often fails without build headers.
Package installs need network access; in a Codex sandbox, request approval for that step.
GPIO with gpiozero
from signal import pause
from gpiozero import Button, LED, PWMLED
led = LED(17) # BCM numbering, physical pin 11
button = Button(27, pull_up=True, bounce_time=0.05)
fan = PWMLED(18, frequency=25000)
button.when_pressed = led.toggle # callbacks run on a background thread
fan.value = 0.4
pause()
- BCM numbers, not physical pins. Put a pin map comment next to each assignment.
- 3.3 V logic only. A 5 V signal on a GPIO pin damages the SoC; use a divider or level shifter. GPIO pins source a few mA safely; drive relays and motors through a transistor or driver board with a flyback diode.
- Callback code runs on another thread: guard shared state with a lock or hand events to a
queue.Queue. - Tests on a laptop:
GPIOZERO_PIN_FACTORY=mockwithfrom gpiozero import Device; from gpiozero.pins.mock import MockFactory.
I2C and SPI
sudo raspi-config nonint do_i2c 0 # 0 means enable
sudo raspi-config nonint do_spi 0
sudo reboot
i2cdetect -y 1 # from i2c-tools
Or add to /boot/firmware/config.txt: dtparam=i2c_arm=on, dtparam=spi=on; extra buses
and devices come from overlays (dtoverlay=i2c-rtc,ds3231). Read
/boot/firmware/overlays/README for the parameters of each overlay; do not guess them.
Run at boot with systemd
# /etc/systemd/system/plant-monitor.service
[Unit]
Description=Plant monitor
After=network-online.target
Wants=network-online.target
[Service]
User=pi
Group=pi
SupplementaryGroups=gpio i2c
WorkingDirectory=/home/pi/app
ExecStart=/home/pi/app/.venv/bin/python -u main.py
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now plant-monitor
journalctl -u plant-monitor -f
- Never run the app as root to “fix” GPIO permissions; add the user to the groups.
- Replace the user name with the real account; recent images do not create
piby default. - Use a systemd timer instead of cron for periodic jobs, so logs land in the journal.
- GUI apps that need the desktop session belong in a user service or autostart entry, not a system unit.
Power, heat and storage
vcgencmd get_throttlednon-zero: bit 0 undervoltage now, bit 16 undervoltage has occurred, bits 2 and 18 throttling. Fix the supply (Pi 5 wants 5 V 5 A for full USB current) before debugging software.vcgencmd measure_tempabove about 80 C throttles; add a heatsink or the active cooler.- SD wear: reduce writes. Set
Storage=volatilein/etc/systemd/journald.conffor appliances, write data to a database with batched commits, and consider the overlay file system (raspi-config, Performance Options) for read-only deployments. - For anything important, boot from an SSD (USB or the Pi 5 PCIe) instead of SD.
Exposing it on a network
Change the default password, keep SSH key-only, and do not port-forward the Pi. For remote
access use a VPN or tunnel the user already runs. Server hardening detail lives in
linux-server-hardening.
Gotchas
time.sleeppolling loops for buttons miss presses; use gpiozero edge callbacks.- Two processes cannot own the same pin; a leftover service holds it. Check
systemctl list-units --type=servicebefore debugging “GPIO busy”. - The 1-Wire, UART and I2C overlays change pin functions; check
pinctrl getwhen a pin “does nothing”. - The primary UART on Pi 3/4 is used by Bluetooth; use
/dev/serial0and enable the UART in raspi-config rather than hard-codingttyAMA0.
Verification
- Fresh-image install script runs end to end without manual steps
- Service starts after
sudo rebootandsystemctl statusshows active - Killing the process (
sudo kill -9 <pid>) results in an automatic restart -
vcgencmd get_throttledreturns0x0under full load - Pulling power mid-run and rebooting leaves the app and data intact
Report
**Hardware:** Raspberry Pi 5 4 GB, Bookworm 64-bit, BME280 at 0x76 on I2C-1
**Change:** RPi.GPIO -> gpiozero; venv with system site packages; systemd unit with restart
**Verified:** survives reboot and kill -9; throttled=0x0; user log pasted from journalctl
**Risks:** still on SD card; journald set to volatile, data written once a minute What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Raspberry Pi Projects"
short_description: "Pi projects that boot, restart and survive power cuts"
default_prompt: "$raspberry-pi This script used RPi.GPIO and fails on my Pi 5, port it" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.