A tracked robot controlled via Home Assistant or a local web app, served by a Raspberry Pi Zero 2 W. Two DC motors driven by an L298N H-bridge, live MJPEG camera stream via Pi Camera with pan-tilt from an ArduCam PCA9685 kit, battery powered by PiSugar 3.
| Part | Link |
|---|---|
| Raspberry Pi Zero 2 W | Amazon |
| Tracked robot chassis | Amazon |
| L298N motor driver | Amazon |
| PiSugar 3 battery | Amazon |
| ArduCam pan-tilt platform (PCA9685) | Amazon |
| ArduCam camera module | Amazon |
| ArduCam 30 cm ribbon cable | Amazon |
| Female-to-female breadboard jumpers | Amazon |
| Component | Details |
|---|---|
| Compute | Raspberry Pi Zero 2 W |
| OS | Raspberry Pi OS Lite 64-bit (Trixie) |
| Motors | 2× DC motors (tracked chassis) |
| Motor driver | L298N H-bridge |
| Camera | Pi Camera v1 (OV5647) |
| Pan-tilt | ArduCam pan-tilt kit SKU B0283 (PCA9685 16-ch PWM controller) |
| Battery | PiSugar 3 |
| L298N Pin | BCM GPIO | Physical Pin | Function |
|---|---|---|---|
| IN1 | GPIO 23 | Pin 16 | Right motor direction A |
| IN2 | GPIO 22 | Pin 15 | Right motor direction B |
| ENA | GPIO 18 | Pin 12 | Right motor PWM (speed) |
| IN3 | GPIO 17 | Pin 11 | Left motor direction A |
| IN4 | GPIO 27 | Pin 13 | Left motor direction B |
| ENB | GPIO 19 | Pin 35 | Left motor PWM (speed) |
| GND | GND | Pin 6 | Shared ground |
| L298N Output | Motor |
|---|---|
| OUT1 / OUT2 | Right motor |
| OUT3 / OUT4 | Left motor |
Power: Motor battery pack connects to the L298N VCC and GND screw terminals. Never power motors from the Pi 5V rail.
ENA/ENB: Remove the jumper caps and wire to Pi GPIO for PWM speed control. Leave jumpers on for always-on full speed.
Ground: Pi GND (pin 6) must be connected to L298N GND, otherwise GPIO signals have no reference and the motors will not respond.
| PCA9685 Wire | Physical Pin | Function |
|---|---|---|
| VCC | Pin 4 | 5 V |
| GND | Pin 6 | GND |
| SDA | Pin 3 | GPIO 2 (I2C SDA) |
| SCL | Pin 5 | GPIO 3 (I2C SCL) |
| PCA9685 Channel | Servo |
|---|---|
| Channel 0 | Tilt servo (up / down) |
| Channel 1 | Pan servo (left / right) |
I2C address: 0x40 (PCA9685 default). Configured in
config.tomlunder[camera_control].
Flash Raspberry Pi OS Lite 64-bit to a microSD card using Raspberry Pi Imager. See docs/first-boot.md for headless WiFi/SSH configuration.
After SSH-ing into the Pi:
git clone https://github.com/arudyk/sentinel.git
cd sentinel
bash setup.shsetup.sh will:
- Install system packages (
picamera2,RPi.GPIO,python3-smbus2,i2c-tools) - Enable I2C in
/boot/firmware/config.txt(required for pan-tilt) - Install and start
pisugar-serverfor battery monitoring - Create a Python virtualenv with
--system-site-packages - Install Flask
- Install and enable the
sentinelsystemd service
| Method | URL |
|---|---|
| Local network | http://192.168.1.138:8080 |
| mDNS hostname | http://sentinel.local:8080 |
For remote access, use the Home Assistant integration — the HA camera proxy and controls work from anywhere HA is reachable.
From your dev machine (pushes to GitHub then pulls on the Pi):
bash deploy.shOverride the target with env vars if needed:
SENTINEL_HOST=192.168.1.138 SENTINEL_USER=sentinel bash deploy.shsudo systemctl status sentinel # check status
sudo systemctl restart sentinel # restart
sudo journalctl -u sentinel -f # follow logspython3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python -m sentinel.main
# Open http://localhost:8080GPIO falls back to dry-run mode (logs pin changes to stdout). Camera falls back to a placeholder JPEG frame. All routes and controls work normally.
python -m pytest tests/ -v # run unit testsAll tunable parameters live in config.toml. Edit and restart the service to apply changes.
[motor]
in1 = 23 # Right motor dir A — BCM GPIO number
in2 = 22 # Right motor dir B — BCM GPIO number
ena = 18 # Right motor PWM — BCM GPIO number
in3 = 17 # Left motor dir A — BCM GPIO number
in4 = 27 # Left motor dir B — BCM GPIO number
enb = 19 # Left motor PWM — BCM GPIO number
default_speed = 75 # 0–100
pwm_frequency = 1000
[camera]
width = 640
height = 480
framerate = 15
jpeg_quality = 70
rotation = 180 # 0, 90, 180, or 270
[camera_control]
i2c_bus = 1
i2c_addr = 64 # 0x40 — PCA9685 default
[server]
host = "0.0.0.0"
port = 8080
debug = falsePin numbers: values are BCM GPIO numbers, not physical pin numbers. Use the wiring table above to cross-reference.
| Key | Action |
|---|---|
↑ |
Forward |
↓ |
Reverse |
← |
Turn left |
→ |
Turn right |
Space |
Stop |
Hold the key to keep moving — releasing stops the robot.
Use the on-screen D-pad. Touch and hold to move, release to stop.
Adjust the speed slider (0–100%) before or during movement.
| Key | Action |
|---|---|
I |
Tilt up |
K |
Tilt down |
J |
Pan left |
L |
Pan right |
Hold to move continuously; release to stop at the current position.
Tap and hold the transparent arrow buttons overlaid on the camera stream.
| Method | Route | Description |
|---|---|---|
GET |
/ |
Web UI |
GET |
/stream |
MJPEG live camera stream |
POST |
/command |
Send a drive command |
POST |
/pan_tilt |
Set camera pan and/or tilt angle |
GET |
/status |
Current state (JSON) |
curl -X POST http://sentinel.local:8080/command \
-H "Content-Type: application/json" \
-d '{"action": "forward", "speed": 75}'Valid actions: forward, reverse, turn_left, turn_right, stop, brake
curl -X POST http://sentinel.local:8080/pan_tilt \
-H "Content-Type: application/json" \
-d '{"pan": 90, "tilt": 60}'Both fields are optional — send only pan or only tilt to move one axis. Angles are 0–180°, center is 90°.
{
"action": "forward",
"speed": 75,
"camera_ok": true,
"pan": 90,
"tilt": 90,
"uptime_s": 120,
"battery_pct": 85.0,
"battery_v": 4.05,
"battery_plugged": false,
"battery_charging": false
}The ha-integration/ directory contains a custom HA integration that exposes Sentinel as a first-class Home Assistant device. This is the recommended way to control the robot remotely — all traffic is proxied through HA so no direct network access to the Pi is required.
| Entity | Description |
|---|---|
camera.sentinel_camera |
Live MJPEG stream (proxied through HA) |
button.sentinel_forward/reverse/turn_left/turn_right |
Drive commands |
button.sentinel_stop / button.sentinel_brake |
Stop / active brake |
number.sentinel_speed |
Speed setting (0–100 %) |
number.sentinel_pan |
Camera pan angle (0–180°) |
number.sentinel_tilt |
Camera tilt angle (0–180°) |
sensor.sentinel_battery |
Battery percentage |
sensor.sentinel_battery_voltage |
Battery voltage (V) |
sensor.sentinel_speed |
Current motor speed |
sensor.sentinel_uptime |
Pi uptime (seconds) |
binary_sensor.sentinel_camera |
Camera health |
binary_sensor.sentinel_plugged_in |
External power connected |
binary_sensor.sentinel_charging |
Battery charging |
- Copy
ha-integration/custom_components/sentinel/into your HAconfig/custom_components/directory. - Copy
ha-integration/www/sentinel-card.jsinto your HAconfig/www/directory. - Restart Home Assistant.
- Go to Settings → Devices & Services → Add Integration, search for Sentinel.
- Enter the robot's IP address and port (default
8080).
Add the Sentinel card to any dashboard with:
type: custom:sentinel-card
entity_prefix: sentinel # default, omit if unchangedThe card shows the live camera feed, a D-pad for driving, a speed slider, and transparent pan-tilt arrows overlaid on the camera stream. Controls:
| Input | Action |
|---|---|
| Arrow keys | Drive (↑ forward, ↓ reverse, ← / → turn) |
Space |
Stop |
I / K / J / L |
Tilt up / down / pan left / right |
| D-pad buttons | Touch / click and hold to drive, release to stop |
| Camera overlay arrows | Touch / click and hold to pan-tilt, release to stop |
- Sentinel must be reachable from the HA host on the local network.
- The
www/resource must be registered in HA. Go to Settings → Dashboards → Resources → Add resource, set URL to/local/sentinel-card.jsand type to JavaScript module.
sentinel/
├── config.toml Central configuration (GPIO pins, camera, server)
├── requirements.txt Python dependencies (Flask only)
├── setup.sh One-shot Pi setup script
├── deploy.sh Deploy to robot (git push + pull on Pi + restart)
│
├── sentinel/ Python package
│ ├── main.py Flask app and route definitions
│ ├── motor_controller.py L298N GPIO/PWM driver
│ ├── camera_stream.py picamera2 MJPEG streaming
│ ├── camera_control.py PCA9685 pan-tilt servo driver (I2C via smbus2)
│ ├── battery_monitor.py PiSugar 3 battery reader
│ └── config.py Loads config.toml into dataclasses
│
├── web/ Frontend (no build step)
│ ├── index.html
│ ├── style.css
│ └── app.js
│
├── systemd/
│ └── sentinel.service Systemd unit file
│
├── tests/
│ ├── test_motor_controller.py
│ └── test_config.py
│
└── docs/
├── wiring.md Detailed wiring reference
└── first-boot.md SD card and headless setup


