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 tickslayer from your computer (or ssh 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-apps is the camera CLI suite (rpicam-still, rpicam-vid, rpicam-hello). On older Raspberry Pi OS releases this was called libcamera-apps; that's now a transitional package — use rpicam-apps directly on current Trixie/Bookworm builds.
  • python3-picamera2 is the Python binding for libcamera. Always install picamera2 from apt, not pip. The pip path tries to build python-prctl from source and fails on libcap headers, 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-dev is here defensively in case anything else later wants to compile against libcap.
  • gpsd is 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.

LibrarySourcePurpose
picamera2apt (python3-picamera2)Camera capture (Pi Camera Module 3)
adafruit-circuitpython-pca9685pipPWM driver control (steering & throttle)
adafruit-circuitpython-ads1x15pipADC for battery voltage monitoring
adafruit-circuitpython-motorpipServo abstraction layer
pynmea2pipParse GPS NMEA sentences
pyserialpipSerial port communication with GPS
requestspipHTTP 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).