Self-hosted security camera — from bare silicon to browser in one repo.
ESP32-S3 firmware → Raspberry Pi backend → live MJPEG stream in your browser. No cloud. No subscription.
Add a screenshot here — take one of your running dashboard and save it as
docs/assets/dashboard.png.
Seedocs/assets/for instructions on adding screenshots and demo GIFs.
Commercial IP cameras require cloud accounts, subscriptions, and send your footage to third-party servers. This project replaces all of that with a $15 microcontroller and a Raspberry Pi you already own. Everything runs on your LAN; Caddy handles HTTPS so the dashboard is still secure.
Browser (HTTPS via Caddy)
│
▼
Flask + SocketIO (:5001)
│ REST / WebSocket (Socket.io)
▼
WebSocket Server (:8765)
│ WS frames [V=video | A=audio]
▼
ESP32-S3 ── OV2640 camera
── PDM microphone
── HC-SR501 PIR
Dual-core FreeRTOS design: core 0 streams frames (no WDT), core 1 handles control & WiFi (WDT active). A wsMutex semaphore serialises all WebSocket calls between cores.
Full details → docs/ARCHITECTURE.md
| Feature | Details |
|---|---|
| Live MJPEG stream | Browser-native, no plugin required |
| Real-time audio | PCM 16 kHz → Web Audio API (iOS & desktop) |
| Motion detection | PIR sensor with 60 s warmup and 5-sample debounce filter |
| Auto recording | Motion-triggered recording with audio mux (ffmpeg → .mp4) |
| Timelapse | Server-side frame capture at configurable intervals |
| Video library | List, play, and download all recordings from the dashboard |
| SHA-256 login | Password hashed client-side via crypto.subtle (HTTPS required) |
| HTTPS / TLS | Caddy handles TLS termination; HSTS enforced |
| JWT sessions | 24-hour signed tokens; rate-limited login (10 req/min) |
| Thread-safe streaming | FreeRTOS mutex protects all WebSocket calls across cores |
| Per-core watchdog | WDT on loop core only; stream core exempt from resets |
| Dark-mode dashboard | Responsive SPA; no framework dependencies |
| Layer | Technologies |
|---|---|
| Firmware | C++, Arduino ESP32 v3, FreeRTOS, esp-camera (OV2640), WebSocketsClient, ArduinoJson |
| Protocols | WebSocket, MQTT TLS, MJPEG over HTTP, PCM audio (single-byte frame prefix) |
| Backend | Python 3.9+, Flask 3, Flask-SocketIO, asyncio, OpenCV (headless), ffmpeg |
| Frontend | HTML5, vanilla JS, Web Audio API, Socket.io 4, crypto.subtle SHA-256 |
| Security | SHA-256 hashing, JWT (PyJWT), HTTPS/TLS, HSTS, Caddy reverse proxy |
| Infrastructure | Raspberry Pi (any 3B+/4/5), systemd service, Caddy, python-dotenv |
| Component | Model | Notes |
|---|---|---|
| Camera node | Seeed XIAO ESP32-S3 Sense | OV2640 + PDM mic expansion board |
| Motion sensor | HC-SR501 PIR | Wired to GPIO 2 |
| Server | Raspberry Pi 3B+ / 4 / 5 | Any model with USB-C or micro-USB power |
| Storage | MicroSD 32 GB+ | For Pi OS + video recordings |
| Power (ESP32) | USB-C 5V/2A | |
| Power (Pi) | USB-C 5V/3A |
-
Clone:
git clone https://github.com/alinjfz/xiao-esp32s3-camera.git
-
Configure firmware: open
firmware/xiao_esp32s3_camera.inoand set:const char* ssid = "YOUR_WIFI_SSID"; // 2.4 GHz only const char* password = "YOUR_WIFI_PASSWORD"; const char* raspi_ip = "YOUR_PI_IP"; // or "raspberrypi.local"
-
Flash: Arduino IDE → Tools → PSRAM → OPI PSRAM → Upload
⚠️ PSRAM setting is mandatory — without itesp_camera_init()fails with error0x105. -
Pi setup:
cd server python3 -m venv venv && source venv/bin/activate pip install -r requirements.txt cp .env.example .env && nano .env # set ADMIN_PASSWORD, SECRET_KEY, JWT_SECRET python raspberry_pi_server.py
-
Open browser:
https://raspberrypi.local/home
- Hardware Setup & Wiring
- Arduino IDE Configuration
- Raspberry Pi Setup & Caddy
- System Architecture
- Troubleshooting Guide
- No 5 GHz WiFi — ESP32-S3 hardware supports 2.4 GHz only
- PSRAM mandatory — must enable Tools → PSRAM → OPI PSRAM; without it camera init fails
- Local-only by default — remote access requires Caddy + domain or port forwarding
- Single camera — architecture supports one ESP32 node; multi-camera would need refactoring
| Version | Description |
|---|---|
| v1 | Initial MQTT-based system with I2S audio and Flask dashboard |
| v2 | Fix I2S/watchdog/framesize compilation errors; add WebSocket; 56% power reduction |
| v3 | Emphasise PSRAM initialisation order; OPI PSRAM setting is critical |
| v4 | Add diagnose.py health-check tool; comprehensive troubleshooting docs |
| v5 | Major refactor: dark-mode SPA, HTTPS/Caddy, SHA-256 auth, MJPEG streaming |
| v6 | Thread-safe WebSocket (FreeRTOS mutex), per-core WDT, 5-sample PIR debounce, rate-limited login, settings validation |
MIT — see LICENSE