The Pi (hermes-bot) is flashed, upgraded and running ROS 2 Jazzy. The ST7789 works through Blinka on lgpio: pass cs=None (the kernel owns CE0), and the glass sits at y_offset=80 in the controller RAM for every rotation, since the driver rotates in software. The mounted pose needs rotation=270. hermes_face is a numpy port of the flat face from docs/face-prototype.html, with phosphor and vector styles, plus a new `sleeping` pose for "no agent connected". The renderer has no ROS or hardware imports. The tone map is a lookup table and the glow runs at half resolution, so the face reaches ~28 fps on the panel at 24% of a core (20 fps) to 40% (30 fps) with the CPU at full clock. CONTEXT.md now records the Pi, the cloud-init first-boot-only gotcha, and what is left. pi/50-cloud-init.yaml is a gitignored Wi-Fi template. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AFN8nRRCiDiMN7ChBpHJhs
6.0 KiB
Current state
Last updated 2026-10-04. HANDOFF.md is the brief; this file tracks where the work stands.
Hardware on hand
Raspberry Pi 4 (8 GB), BrainCraft HAT, Arducam UC-346 camera. Not yet: servos, USB-C splitter, printed enclosure, speakers. Work until those arrive is limited to what the Pi, HAT and camera can do.
The Pi (hermes-bot)
- Ubuntu 24.04.5 server, kernel
6.8.0-1065-raspi(fully upgraded), flashed fromtalos. Userhermes(key-only; passwordless sudo), SSH keys forkyle@vade(talos) andkyle@straylight. - On Wi-Fi at
192.168.88.152(MACd8:3a:dd:0d:66:f5);eth0is not cabled. The owner says it has a static IP on the rack switch; that address is not recorded here yet.avahi-daemonis installed, but mDNS is off insystemd-resolvedon talos, sohermes-bot.localdoesn't resolve there; use the IP. - cloud-init applies
network-configanduser-dataon the first boot only. To change Wi-Fi later, edit/etc/netplan/50-cloud-init.yamlon the Pi, or bumpinstance_idinmeta-dataonsystem-boot.pi/50-cloud-init.yamlis a template for the Wi-Fi block; it is gitignored because it holds credentials. - Installed: ROS 2 Jazzy (
ros-base,ros-dev-tools, colcon,robot_state_publisher,xacro,camera_ros);python3-numpy/pil/spidev/lgpio/rpi-lgpio; a venv~/venv(system site packages visible) withadafruit-circuitpython-rgb-displayand Blinka. Workspace:~/ros_ws, source synced fromros/withrsync -a --delete --exclude __pycache__ ros/ hermes@<ip>:~/ros_ws/src/, thencolcon build --symlink-install. - CPU governor is
ondemand; at full clock the face costs about half as much CPU. Setperformancein the installer (package 5). It is not persistent yet.
Package 0a (platform proof on Ubuntu 24.04)
- Step 1 done:
host/check-pi-platform.shpasses. - Step 2 done: flashed, booted, upgraded; ROS installed;
ros2 topic listworks. - Step 5 (display) done. ST7789 works on
lgpiothrough Blinka (rpi-lgpio, noRPi.GPIO). Two gotchas: passcs=None(the kernel drives CE0; claiming GPIO8 raises "GPIO busy"), and the 240x240 glass sits aty_offset=80in the controller's RAM, for every rotation (the driver rotates in software and leaves the scan direction fixed). Mounted pose (microSD end up, USB-C edge on the left) needs content turned 90 degrees clockwise:rotation=270. Full-frame push is ~23 fps through the driver; the face pushes raw RGB565 with_blockand reaches ~27-28 fps. - Not done: audio (
wm8960-soundcardoverlay + mixer), camera (cam -l,camera_ros), PWM (waits for servos). Ask before rebooting the Pi.
Face (ros/hermes_face)
render.py: numpy port ofdocs/face-prototype.html, flat face only (head and lowpoly not ported). Phosphor and vector styles. States:sleeping idle listening thinking looking speaking.sleeping(new) is the "no agent connected" pose: eyes closed to thin lines, mouth short, dim and slowly breathing.lookingreturns to the state it interrupted. Tone mapping is a lookup table (RGB and RGB565 tables), glow is computed at half resolution, rotation is a freenp.rot90.display.py: ST7789 driver plus an async pusher (render overlaps the SPI transfer).face_node.py: nodehermes_face; subscribes/hermes/state(std_msgs/String) and/joint_states(pan_joint,tilt_joint, radians); starts ininitial_state(sleeping), so it runs with nothing else up. Parameters:rotation(270),x_offset(0),y_offset(80),baudrate,fps,output(st7789ornone),phosphor(P7),style,persistence_ms,focus,jitter,intensity,initial_state.demo.py: no ROS.python -m hermes_face.demo --sequencecycles the states on the panel;--png-dir DIRwrites a PNG per state without hardware.- Tests:
python3 -m pytest testinros/hermes_face(23 pass on the Pi). - Measured on the Pi 4: ~9 ms per frame in isolation. With the CPU at full clock: 24% of a core at
20 fps and 40% at 30 fps (target was under 40% at 20 fps or more). Under
ondemandthe same load reads 40% at 20 fps and ~67% at 30 fps. No C++ rewrite was needed. - Not yet: audio levels for the mouth (
/audio/levels),hermes_mcppublishing states, launch file inhermes_bringup, head/lowpoly shapes, DotStar LEDs, button mute. - Open: IPS black level. The backlight is on at full brightness, so black reads dark gray on camera. Dimming it needs a software PWM on GPIO26 (not a hardware PWM pin).
Next
- Package 0b: USB gadget Ethernet (
dwc2peripheral, NCM + ACM,usb0at10.55.0.2/30). Needs a reboot and a USB-C data cable to a host; the splitter has not arrived, so a bench test would power the Pi from the host's port (fine without servos). Keep Wi-Fi up throughout. - Audio (package 1), camera (package 3), then the rest of the packages in
HANDOFF.md.
Repository
Branch master. .gitignore covers .remember/, caches, .venv/, *.orig, *.rej and
pi/50-cloud-init.yaml. tools/check-pi-platform.sh duplicates host/check-pi-platform.sh and can
probably go.
Security review notes (parked until the USB link is up)
An automated review of the initial commit flagged three items. All stem from the code predating the USB-gadget design (HANDOFF.md package 0b); none is exposed until the Pi runs the services.
pi/robot_mcp_server.py: plain HTTP,ROBOT_MCP_BINDdefaults to0.0.0.0. Plan: default to10.55.0.2, start afterusb0.pi/pipewire/50-network-server.conf: TCP 4713 on all interfaces, unrestricted. Plan: keep, comment that the firewall is the protection.pi/nftables-robot.nft: still the Pi Zero version (single host IP, any interface). Package 0b rewrites it to accept only on the USB interface from10.55.0.1.pi/install.shpermissions looked fine (/etc/robot-mcp.envisroot:$USER, 640).
The owner wants to confirm the real IPs on the link before changing any of this. If the USB link doesn't work, revisit: TLS or a Tailscale-only bind for MCP and audio.