Convert pi-eyes.sh to Python#402
Conversation
Faithful port of the recently-rewritten Snake Eyes Bonnet installer (Trixie, Pi 3B/4/5). Uses adafruit_shell idioms: select_n for the screen menu, prompt for the feature questions, get_boot_config() for the boot config path, reconfig for config.txt edits, and write_text_file for the xorg.conf and systemd unit files. Preserves the existing behavior: dedicated venv at /opt/pi-eyes-venv, rpi-lgpio on Pi 5, local fbx2.c override if present (else the one from the downloaded Pi_Eyes), fbx2 compile, optional gpio-halt / ADC / USB gadget, and the pi-eyes / pi-eyes-fbx2 / gpio-halt systemd units.
There was a problem hiding this comment.
Pull request overview
This PR introduces a Python implementation of the existing pi-eyes.sh installer, aiming to keep the current Trixie-era behavior intact while moving the installer logic into a single pi-eyes.py script.
Changes:
- Adds
pi-eyes.py, a Python port of the Snake Eyes Bonnet installer (menus, package install, venv setup, Pi_Eyes download/build, boot config edits, and systemd units). - Implements cmdline token editing via a small
append_to_line()helper. - Sets up systemd autostart units (
pi-eyes, optionalpi-eyes-fbx2, optionalgpio-halt) and writes/etc/X11/xorg.conf.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pi_model = "" | ||
| if os.path.exists("/proc/device-tree/model"): | ||
| pi_model = shell.read_text_file("/proc/device-tree/model").replace("\x00", "") | ||
| is_pi5 = "Raspberry Pi 5" in pi_model |
There was a problem hiding this comment.
Done — switched to shell.is_pi5_or_newer() to match joy-bonnet.py/arcade-bonnet.py. (8edf896)
| print("Installing system packages...") | ||
| shell.run_command( | ||
| "apt-get install -y build-essential python3-venv python3-dev " | ||
| "python3-full libx11-dev libxext-dev git curl unzip" | ||
| ) |
There was a problem hiding this comment.
Reasonable suggestion, but the original pi-eyes.sh doesn't gate on xinit either, and xinit is pulled in by the X11 dependency chain this script installs. To keep this a faithful 1:1 port I'm leaving the install list as-is; an explicit Lite-detection bail-out would be a behavior change worth doing as a separate follow-up if desired.
| shell.run_command( | ||
| "curl -sLO https://github.com/adafruit/Pi_Eyes/archive/master.zip" | ||
| ) | ||
| shell.run_command("unzip -q master.zip") |
There was a problem hiding this comment.
Done — using curl -fsSLO here so HTTP failures fail immediately. (8edf896)
| shell.run_command( | ||
| "curl -sLO https://github.com/adafruit/Adafruit-GPIO-Halt/" | ||
| "archive/master.zip" | ||
| ) |
There was a problem hiding this comment.
Done — same curl -fsSLO fix applied here. (8edf896)
| append=False, | ||
| ) | ||
| shell.run_command("systemctl enable pi-eyes-fbx2.service") |
There was a problem hiding this comment.
Intentionally matching the original pi-eyes.sh, which enables all units first and runs a single systemctl daemon-reload at the very end (after the last unit is written). Since this is a 1:1 port of the recently-rewritten Trixie script, I'm preserving that ordering rather than reshuffling it. The final reload happens before the script exits, so the freshly-written units are visible to systemd before first boot.
| """, | ||
| append=False, | ||
| ) | ||
| shell.run_command("systemctl enable pi-eyes.service") |
There was a problem hiding this comment.
Same as above — the consolidated daemon-reload at the end of the script (matching the original .sh) covers this unit too.
| """, | ||
| append=False, | ||
| ) | ||
| shell.run_command("systemctl enable gpio-halt.service") |
There was a problem hiding this comment.
Same as above — covered by the single trailing daemon-reload, matching the original .sh.
| # Adafruit Snake Eyes Bonnet installer for Raspberry Pi OS Trixie (Debian 13). | ||
| # Supports Pi 3B, Pi 4, and Pi 5. | ||
| # |
There was a problem hiding this comment.
This wording is carried over verbatim from the original pi-eyes.sh. The Pi Zero only applies to the optional USB-Ethernet-gadget prompt, not eye rendering, so the supported-boards banner (3B/4/5) and the gadget option aren't actually in conflict. Preserving the original text here.
| print("Adafruit Snake Eyes Bonnet installer") | ||
| print("Raspberry Pi OS Trixie - Pi 3B / Pi 4 / Pi 5") | ||
| print("") |
There was a problem hiding this comment.
Same as the header note — carried over from the original .sh. The Pi Zero reference is scoped to the USB-gadget option only; keeping the banner text faithful to upstream.
- Replace /proc/device-tree/model string match with shell.is_pi5_or_newer() (matches joy-bonnet.py/arcade-bonnet.py). - Use curl -fsSLO for downloads so HTTP failures surface immediately instead of writing an HTML error page to master.zip.
makermelissa
left a comment
There was a problem hiding this comment.
Looks good and fair pushback on copilot's suggestions.
Converts
pi-eyes.shtopi-eyes.py.Unlike some of the older scripts in this repo,
pi-eyes.shwas recently rewritten for Trixie (Pi 3B / 4 / 5), so this is a faithful 1:1 port rather than a modernization — the bash already reflects current intent (venv-based pip installs, systemd autostart, X11 MIT-SHMfbx2,rpi-lgpioon Pi 5).What it does (unchanged from the .sh)
rpi-lgpioon Pi 5 (RP1 controller),RPi.GPIOotherwise./opt/pi-eyes-venv(--system-site-packages) with numpy / pi3d / svg.path / blinka / ads1x15./opt/Pi_Eyes, uses a localfbx2.cif present next to the script (else the downloaded one), compilesfbx2./etc/X11/xorg.conf, editsconfig.txt(gpu_mem, hdmi_*), addsvideo=HDMI-A-1:...tocmdline.txt, enables SPI/I2C as needed.pi-eyes,pi-eyes-fbx2, andgpio-haltsystemd units (rc.local is deprecated on Trixie).Implementation notes
select_n,prompt,get_boot_config(),reconfig,write_text_file,run_raspi_config,prompt_reboot.input()loop asjoy-bonnet.py/arcade-bonnet.py.append_to_linehelper handles the single-linecmdline.txttoken edits (thereconfig2behavior from the .sh).Testing
Hardware-tested on a Raspberry Pi 5 (Trixie, Desktop) — full install with the HDMI-only screen option (exercises the maximum logic path that doesn't require the SPI bonnet/displays):
EXIT=0. venv + pip installs succeeded;fbx2compiled to a valid ELF binary; Pi_Eyes downloaded./etc/pi-eyes/env(RADIUS=240),pi-eyes.serviceExecStart usescyclops.pyfor HDMI-only,pi-eyes-fbx2.servicecorrectly absent for HDMI-only,xorg.conf640x480 modeline,config.txtgpu_mem/hdmi_* lines,cmdline.txtvideo=HDMI-A-1:640x480@60e, default target → multi-user, service enabled.The SPI-screen branches (fbx2 service, spi1 overlays) mirror the validated HDMI path and require the physical Snake Eyes Bonnet + displays to fully exercise end-to-end.
ruff format+ruff checkclean.