200+
Active Cameras
Across 8 states
102K
Images / Day
Web + fullsize ZIPs
822
B2 Buckets
10 B2 accounts
100K
Shutter Life
~1.9 yrs at 144/day
Upload Pipeline
Capture
gphoto2 every 5 min via Python daemon
ZIP
Web (980×655) + fullsize, stored in /mnt/uploading
Worker
Cloudflare Worker validates, deduplicates, routes
B2 / S3
Backblaze B2 (primary), AWS S3 (6 cameras fallback)
Display
livetimelapse.com.au client pages updated via callback
Hardware
Controller
- SBC: Raspberry Pi 3B or 4 (2GB)
- OS: Raspberry Pi OS Lite, read-only root
- VPN: ZeroTier One (ztmxshfycx network)
- USB: Huawei E3372 4G modem + DSLR USB
- Storage: 128GB microSD (OverlayFS root)
Camera
- Brand: Canon DSLR (various models)
- Interface: USB PTP via python-gphoto2
- Power: DR-E10 dummy battery + 7.4V buck
- Auto-off: Must be disabled in camera menu
- Capture rate: Every 5 min (144 shots/day)
Connectivity
- Primary: Optus 4G (Huawei E3372 USB modem)
- VPN: ZeroTier overlay for remote access
- Upload: curl to Cloudflare Worker endpoint
- Heartbeat: Ping to monitoring every 15 min
Power
- Solar: 20–50W 12V panel (site dependent)
- Battery: LiFePO4 12V (mandatory — boxes reach 60–80°C)
- Pi power: 12V → 5V buck converter (3A)
- DSLR power: 12V → 7.4V buck + DR-E10
- Daily use: ~12Wh (Pi + DSLR + 4G modem)
Software Stack
Pi-Side (Python 3)
- Capture daemon: Python 3 with python-gphoto2 (libgphoto2)
- Upload daemon: Bash script — monitors /mnt/uploading
- Remote control: Python async daemon, WebSocket client
- Cron: Capture every 5 min, heartbeat every 15 min
- Service: systemd — tlc-camera-daemon.service
Server-Side (Node.js + PHP + TypeScript)
- WebSocket relay: Node.js relay.js on port 8765
- Upload Worker: Cloudflare Worker (TypeScript)
- Database: Cloudflare D1 (SQLite) — camera_settings, audit
- Camera registry: cameras.json (PHP-managed, 200+ records)
- Monitoring: PHP cron scripts, 15-min intervals