← all guides

A Raspberry Pi dashcam node

A small computer on the dash that spots vehicles as you drive, posts a plate-illegible crop with the GPS position it had at that moment, and lets the map's reviewers decide whether it was a patrol car. No phone in a mount, no screen to keep awake, nothing to tap.

You may not need thisA phone in a mount running driving mode already does all of this, with a live map and patrol alerts. Build a Pi when you want a camera that starts with the ignition and needs nobody in the car to touch it.
Nothing is recordedThe Pi keeps no video and no log of where you drove. Without --plates what leaves it is a crop at most 200 px wide, which cannot carry a readable plate. With --plates it reads the plate on the Pi and keeps it there; the text is handed over only after the map has published that vehicle as government. A private vehicle's plate never leaves the Pi. See reading plates.
You are never on the mapA dashcam is enrolled as a mobile camera. Mobile cameras get no watched road, no marker, and no home; only the vehicles it saw appear, at the spot it saw them.

What to buy

PartPickWhy
ComputerRaspberry Pi 5 (4 GB) Runs the vehicle detector at a usable rate. A Pi 4 works at about half the frame rate; a Zero 2 W is too slow.
Camera, to spot patrol carsAny USB webcam, 720p or better, with a suction or clip mount. Logitech C920-class is plenty. One cable, a mount you can aim. Enough to tell a patrol car from a car; not enough to read a plate on a moving vehicle.
Camera, to read platesReolink RLC-810A (4K, 12 V DC) or RLC-811A (adds optical zoom). Plus a 12 V barrel lead from the car and one Ethernet cable to the Pi. Four times the pixels on the plate, a real lens, and a manual shutter so a plate at 40 mph is not a smear. It runs off the car's 12 V directly; no PoE injector. The relay already speaks its RTSP.
GPSA USB GPS dongle (u-blox 7 or 8 based, "VK-172" style, under $15) Plugs in and shows up as /dev/ttyACM0. Put it on the dash where it can see the sky.
InternetYour phone's hotspot, or a USB LTE stick A crop every few seconds is a few kilobytes; a day of driving is well under 50 MB.
PowerA 12 V-to-USB-C adapter rated 5 V / 5 A for the Pi 5 (3 A for a Pi 4) A cheap 2 A phone charger browns the Pi out under load and corrupts the card. Buy the rated one.
StorageA 32 GB A2 microSDNothing big is stored.
Power, honestly. A Pi on a 12 V socket is only as safe as the socket. Cutting power mid-write corrupts SD cards; the setup below mounts the card read-only for the running system so a hard ignition-off is fine. If your socket is live with the engine off, unplug the Pi when you park for the night or a flat battery is on you, not us.

Set it up

1 Flash Raspberry Pi OS Lite (64-bit)

Use Raspberry Pi Imager. In its settings set a hostname, a user, your phone hotspot's Wi-Fi name and password, and turn SSH on. Boot the Pi once at home on the same hotspot and SSH in.

2 Install the three packages the relay needs, plus gpsd

sudo apt update
sudo apt install -y python3-opencv python3-numpy python3-pip gpsd gpsd-clients
pip3 install --break-system-packages onnxruntime
curl -fsSLo ~/relay.py https://map.sparrowmap.com/relay.py

That is the whole dependency list. The relay is one file that imports nothing else from this project; it fetches the 38 MB vehicle detector from the map the first time it runs and checks its hash every run.

3 Point gpsd at the dongle and check for a fix

sudo sed -i 's#^DEVICES=.*#DEVICES="/dev/ttyACM0"#; s#^GPSD_OPTIONS=.*#GPSD_OPTIONS="-n"#' /etc/default/gpsd
sudo systemctl enable --now gpsd
gpsmon            # wait for a 2D/3D fix; the first one outdoors can take a few minutes

If the dongle is a UART HAT on the GPIO header instead of USB, the device is /dev/serial0 and it needs sudo raspi-config → Interface → Serial (login shell off, port on) first.

4 Find the camera and try the relay without sending anything

ls /dev/video*                       # usually /dev/video0
python3 ~/relay.py --source /dev/video0 --gps gpsd://127.0.0.1:2947 --dry-run

Wave a toy car in front of it, or point it at the street. You should see lines like would send car (seen 7x) at LAT,LON. If it says no GPS fix, it will not post: a crop with no fix is dropped rather than pinned to the last place the fix was, because a patrol car on the wrong street is worse than a missing one.

5 Enrol it once, for real

python3 ~/relay.py --source /dev/video0 --gps gpsd://127.0.0.1:2947 --enroll "Dashcam"

The first run waits for a fix, registers the camera with the map as a mobile node, and remembers its id and token in ~/.sparrowmap/node.json. Every later run needs only --source and --gps. The token is the camera's identity: keep the file, and if the card is ever lost, enrol again with a new name.

6 Start with the ignition

sudo tee /etc/systemd/system/sparrow-dashcam.service >/dev/null <<'EOF'
[Unit]
Description=SparrowMap dashcam relay
After=network-online.target gpsd.service
Wants=network-online.target

[Service]
User=%i
ExecStart=/usr/bin/python3 /home/USER/relay.py --source /dev/video0 --gps gpsd://127.0.0.1:2947
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
sudo sed -i "s#%i#$USER#; s#/home/USER#$HOME#" /etc/systemd/system/sparrow-dashcam.service
sudo systemctl enable --now sparrow-dashcam
journalctl -u sparrow-dashcam -f          # watch it post

Now it boots when the car does, reconnects to the hotspot on its own, and keeps going when the camera or GPS drops out for a moment.

7 Make the card survive ignition-off

sudo raspi-config     # Performance Options -> Overlay File System -> enable, and make /boot read-only

With the overlay on, nothing writes to the card while driving, so pulling power is harmless. Turn it off again (same menu) when you want to change anything, then back on.

Reading plates (the Reolink build)

This is what the system was designed to do: read every plate at the camera, keep the government ones, and never store a private one. The Pi does it the safe way round: it reads at capture, holds the read for an hour, and sends it only when the map asks for the good picture of a vehicle it has already published as government.

A Wire the Reolink to the Pi

Power the camera from the car's 12 V (a barrel lead to a fused socket). Run Ethernet from the camera to the Pi and give the Pi's port a fixed address so the two can talk without a router:

sudo nmcli con add type ethernet ifname eth0 con-name cam ip4 192.168.2.1/24
sudo nmcli con up cam

In the Reolink app (over the camera's Wi-Fi setup, or plugged into your home router first) give the camera the static address 192.168.2.10, set a password, and turn RTSP on. Then in Display → Exposure choose Manual and a shutter of 1/1000 s or faster: motion blur, not resolution, is what makes a plate on a moving car unreadable.

B Install the two plate models and run with --plates

pip3 install --break-system-packages fast-plate-ocr open-image-models
python3 ~/relay.py --source "rtsp://admin:YOURPASS@192.168.2.10:554/h264Preview_01_main"     --gps gpsd://127.0.0.1:2947 --plates --dry-run

Both models are ONNX and run on the Pi's CPU; a plate read takes about a second, once per vehicle. When it works, drop --dry-run, add --plates to the ExecStart line in the service from step 6, and sudo systemctl restart sparrow-dashcam.

A read only counts when it is plate-shaped (not the word POLICE off a door), at least three characters, and the recogniser is at least 50 % sure of its least certain character. Every vehicle still goes out as the ordinary 200 px crop; the read waits on the Pi.

What leaves the Pi with --plates, stated plainly. For every vehicle: the same 200 px crop as before, the class it saw, and the GPS position. Nothing else, and no text. The plate read and a crop of the vehicle up to 900 px wide stay on the Pi for one hour. If, in that hour, the map's classifier and a reviewer publish that vehicle as a government one, the map asks this camera for the good picture and the Pi answers with the picture and the read; the map records the plate on that already-public row. For every other vehicle the read is simply forgotten. The route that accepts it refuses any row that is not already public, so a private plate cannot be stored even by a modified Pi.

What you will see on the map

Nothing, at first. A dashcam crop goes into the review queue like a phone's does; a reviewer marks it as a patrol vehicle or not, and only confirmed government vehicles are published, at the position your Pi reported. Your route is never stored and your camera has no marker. Reviewing is open to anyone, so the fastest way to see your own catches land is to review.

If it does not work

could not open the stream Another program has the camera, or it is /dev/video1. v4l2-ctl --list-devices shows which.
no GPS fix for more than a few minutes The dongle cannot see the sky, or gpsd is not running: systemctl status gpsd, then gpsmon.
HTTP 429 The map is rate-limiting enrolments. Wait an hour; a saved node.json never needs to enrol again.
It posts but nothing ever appears Expected until a reviewer confirms a catch. Cars that are not government vehicles are never published.

The relay is one readable file. If you want to check what leaves the Pi, read it; the crop cap, the no-fix rule and the plate contract are all in there in plain sight.