build-guides
Guide 2: Raspberry Pi Setup
Pi OS bootstrap — apt packages, Python venv with Adafruit libraries, hardware interfaces (I2C/SPI/UART/Camera), project directory structure
Build Guide 2: Raspberry Pi Setup
Type: Build Guide
Now that every component checks out on the bench, we'll set up the Pi as a proper development environment. By the end of this guide you'll have a Pi that's ready to control hardware, capture images, read GPS, and talk to your web app.
What You Need
- Raspberry Pi 4 set up headlessly with Raspberry Pi OS Lite 64-bit (from Guide 1, Step 3)
- Working
ssh tickslayerfrom your computer (orssh todd@tickslayer.local) - An internet connection on the Pi (it's going to download a few hundred MB of system packages and Python libraries)
Step 1: System Updates and Essentials
SSH in:
ssh tickslayer
Get everything current. The first apt upgrade after a fresh flash is usually 50–100 packages because the image was built weeks before you flashed it:
sudo apt update && sudo apt upgrade -y
This takes 5–10 minutes on a Pi 4 over WiFi. Go grab water.
Install the system packages we need throughout the build:
sudo apt install -y \
python3-pip python3-venv \
i2c-tools \
git \
rpicam-apps \
python3-picamera2 libcap-dev \
gpsd gpsd-clients
A few notes on the package list, because some of these have non-obvious history:
rpicam-appsis the camera CLI suite (rpicam-still,rpicam-vid,rpicam-hello). On older Raspberry Pi OS releases this was calledlibcamera-apps; that's now a transitional package — userpicam-appsdirectly on current Trixie/Bookworm builds.python3-picamera2is the Python binding for libcamera. Always install picamera2 from apt, not pip. The pip path tries to buildpython-prctlfrom source and fails onlibcapheaders, and even if you fix that, you end up with a worse-integrated install. The apt package is what the Raspberry Pi Foundation actually supports.libcap-devis here defensively in case anything else later wants to compile againstlibcap.gpsdis the GPS daemon. It runs as a system service and brokers GPS data so multiple programs can read the device without fighting over the serial port. We'll configure it in Step 5.
Step 2: Python Environment
We're going to create a Python virtual environment for the rover code, but with one critical twist: it has to be created with --system-site-packages so the venv can see picamera2 (which lives at the system level because it's apt-installed).
mkdir -p ~/rover
cd ~/rover
python3 -m venv --system-site-packages venv
source venv/bin/activate
Verify picamera2 is visible from inside the venv:
python -c "import picamera2; print('picamera2 OK')"
If that fails, the venv was created without --system-site-packages — delete ~/rover/venv and recreate it with the flag.
Now upgrade pip and install the libraries that do belong in pip (everything except picamera2):
pip install --upgrade pip
pip install \
adafruit-circuitpython-pca9685 \
adafruit-circuitpython-ads1x15 \
adafruit-circuitpython-motor \
pynmea2 \
pyserial \
requests
This pulls in Adafruit-Blinka and a small mountain of CircuitPython hardware abstraction layers. Takes 2–4 minutes on a Pi 4.
| Library | Source | Purpose |
|---|---|---|
picamera2 | apt (python3-picamera2) | Camera capture (Pi Camera Module 3) |
adafruit-circuitpython-pca9685 | pip | PWM driver control (steering & throttle) |
adafruit-circuitpython-ads1x15 | pip | ADC for battery voltage monitoring |
adafruit-circuitpython-motor | pip | Servo abstraction layer |
pynmea2 | pip | Parse GPS NMEA sentences |
pyserial | pip | Serial port communication with GPS |
requests | pip | HTTP calls to the Convex backend |
Verify every library imports cleanly:
python -c "
import board, busio
import adafruit_pca9685
import adafruit_ads1x15.ads1115 as ADS
from adafruit_motor import servo
import picamera2
import pynmea2
import serial
import requests
print('all imports OK')
"
You should see all imports OK. If you get ModuleNotFoundError for picamera2, see the venv note above. If you get it for board, the Adafruit-Blinka install bailed — re-run the pip install and check the output.
Add the venv activation to your .bashrc so every interactive SSH session starts with the venv active:
echo 'source ~/rover/venv/bin/activate' >> ~/.bashrc
Heads up on non-interactive SSH: the .bashrc activation only runs for interactive shells. If you do ssh tickslayer 'one-line-command' in a script, that command runs in a non-interactive shell, which doesn't source .bashrc, which means the venv is not active and python resolves to the system /usr/bin/python instead of the venv. Either source the activate script explicitly in your scripts, or use the full venv python path (~/rover/venv/bin/python).
