Abstract This article documents the architecture, implementation, and operational characteristics of BabyMind AI — a multi-service AI infant monitoring system deployed on Raspberry Pi 5 hardware. The system integrates computer vision (Claude Haiku Vision API), large language model inference (Groq LLaMA-70b), real-time audio processing (WebRTC VAD), persistent storage (SQLite WAL), and a conversational interface (Twilio WhatsApp) into a unified 9-service production stack. Total hardware cost: $60. Monthly operating cost: under $5. The system has operated continuously in a home environment with an infant, providing 24/7 monitoring, structured health record logging, and natural language Q&A over WhatsApp. 1. Problem Definition Commercial infant monitors provide video streams and threshold-based alerts. They perform no contextual analysis, maintain no health records, and cannot answer natural language queries. The engineering challenge is: Capture continuous video and audio from a deployed camera Apply computer vision at regular intervals to generate structured observations Detect and classify audio events (cry detection) in real time Persist all observations and health events to a queryable database Expose a natural language Q&A interface grounded in real-time observation data Run all of the above on a $60 embedded Linux device with 4GB RAM, 24/7 2. System Architecture 2.1 Hardware Component Specification Cost Compute Raspberry Pi 5 (4GB RAM, 2.4GHz quad-core) ~$60 Camera Ring Indoor Camera (WebRTC, 1080p) Already owned Audio USB microphone (plughw:2,0, 44100Hz, S16_LE) ~$12 Display FREENOVE 4.3" DSI touchscreen (optional) ~$25 Storage 128GB microSD (Class 10) ~$15 OS Raspberry Pi OS (Linux 6.12, 64-bit) Free Total hardware: under $100. 2.2 Service Architecture The system runs 9 independent systemd services with defined startup dependencies and memory limits: ┌─────────────────────────────────────────────────────────────────┐ │ Raspberry Pi 5 (4GB RAM) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ babymind-ring│ │babymind-vision│ │ babymind-audio │ │ │ │ WebRTC live │──▶│ Claude Haiku │ │ WebRTC VAD │ │ │ │ stream │ │ Vision, 30s │ │ cry detection │ │ │ │ MemMax:256MB │ │ MemMax:512MB │ │ MemMax:256MB │ │ │ └──────────────┘ └──────┬───────┘ └────────┬─────────┘ │ │ │ │ │ │ ┌────────▼────────────────────▼───────────┐ │ │ │ SQLite (WAL mode) │ │ │ │ 7 tables · WAL · indexes · 30s timeout │ │ │ └────────────────┬────────────────────────┘ │ │ │ │ │ ┌──────────────┐ ┌──────────────▼──────────┐ │ │ │babymind-ctrl │ │ babymind-whatsapp │ │ │ │ HTTP control │ │ Flask + Twilio + │ │ │ │ MemMax:128MB │ │ Groq LLaMA-70b Q&A │ │ │ └──────────────┘ │ MemMax:600MB │ │ │ └─────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌───────────────┐ ┌──────────────────┐ │ │ │babymind-dash │ │babymind-reports│ │babymind-cloudflare│ │ │ │ Streamlit │ │ 7AM/9PM │ │ Tunnel → Twilio │ │ │ │ MemMax:512MB │ │ email+WhatsApp │ │ webhook │ │ │ └──────────────┘ └───────────────┘ └──────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ 2.3 Service Startup Order and Dependencies Services use staggered ExecStartPre delays to prevent boot-time resource contention: Service Boot Delay Memory Limit Restart Policy babymind-cloudflare 5s 128MB always, 10s babymind-ring 10s 256MB always, 10s babymind-audio 10s 256MB always, 15s babymind-vision 15s 512MB always, 15s babymind-whatsapp 20s 600MB always, 15s babymind-dash 20s 512MB always, 15s babymind-ctrl 15s 128MB always, 10s babymind-reports 25s 256MB always, 15s babymind-health 30s 128MB always, 30s All services use StartLimitIntervalSec=120 and StartLimitBurst=5. 3. Layer 1: Video Acquisition — Ring WebRTC Integration The Ring Indoor Camera streams via WebRTC using the ring_doorbell Python library. The vision service: Maintains a persistent WebRTC session with automatic reconnection Reads JPEG snapshots from the live stream on a 30-second interval Validates frame size — frames under 40KB are skipped (dark/empty frames) Sends valid frames to the Vision API pipeline Known issue: A RuntimeError: Task cannot await on itself appears in logs from the ring_doorbell library's asyncio implementation. This does not affect functionality — it is a library-level bug that does not crash the service. Frame validation logic: def is_valid_frame(frame_data: bytes) -> bool: # Skip dark/empty frames — saves ~40% of API calls at night if len(frame_data) str: if rms 60s Pushover HIGH priority Cry continues > 120s Pushover EMERGENCY (repeats until acknowledged) Cry resolves On silence Update DB record with duration 5.4 Performance Characteristics False positive rate: approximately 8% (environmental noise, primarily HVAC) False negative rate: approximately 3% (very quiet fussing below VAD threshold) Detection latency: 240ms (8 × 30ms frames) Resolution latency: 750ms (25 × 30ms frames) 6. Layer 4: Database — SQLite WAL Architecture 6.1 Schema CREATE TABLE sleep_sessions ( id INTEGER PRIMARY KEY, start_time DATETIME NOT NULL, end_time DATETIME, duration_minutes REAL, position TEXT, INDEX idx_sleep_start (start_time) ); CREATE TABLE feed_sessions ( id INTEGER PRIMARY KEY, start_time DATETIME NOT NULL, duration_minutes INTEGER, feed_type TEXT, -- bottle|breast|solids INDEX idx_feed_start (start_time) ); CREATE TABLE cry_events ( id INTEGER PRIMARY KEY, start_time DATETIME NOT NULL, end_time DATETIME, duration_seconds REAL, intensity TEXT, -- mild|moderate|severe resolved BOOLEAN DEFAULT FALSE, INDEX idx_cry_start (start_time) ); CREATE TABLE vision_observations ( id INTEGER PRIMARY KEY, camera_id TEXT NOT NULL, timestamp DATETIME NOT NULL, baby_state TEXT, position TEXT, face_visible BOOLEAN, safety_concern BOOLEAN, people_present TEXT, -- JSON array observation TEXT, INDEX idx_vision_ts (camera_id, timestamp) ); -- Additional tables: diaper_events, events, daily_summaries 6.2 Concurrency Configuration Nine services write to the same database concurrently. Configuration: PRAGMA journal_mode=WAL; # Write-Ahead Logging PRAGMA synchronous=NORMAL; # Safe with WAL, 5x faster than FULL PRAGMA cache_size=-64000; # 64MB cache PRAGMA temp_store=MEMORY; connect_timeout=30 # Wait 30s on lock, don't fail Result: Zero lock contention errors in production across 9 concurrent writers. 6.3 Storage Projections Data type Daily volume Annual projection Vision observations ~200 records × 200 bytes ~14MB Health events (feeds/cries/diapers) ~20 records × 100 bytes ~0.7MB Daily summaries 1 record × 500 bytes ~0.2MB Total ~15MB/year Automated cleanup runs at 2 AM daily: vision observations older than 7 days are purged, events older than 90 days are archived, VACUUM is run weekly. 7. Layer 5: LLM Q&A — Grounded Natural Language Interface 7.1 Context Assembly Each WhatsApp query assembles a context window from three sources: def build_context(query: str, camera_id: str) -> str: # 1. Current vision state (what Claude sees right now) current_obs = vision_memory.get_latest(camera_id) # 2. Recent observation history (last 8 hours with timestamps) recent_obs = vision_memory.get_recent(camera_id, hours=8) # 3. Today's health records from SQLite today_data = db.get_today_summary() # sleep, feeds, cries, diapers # 4. Recent chat history (last 3 exchanges for context continuity) chat_history = conversation_store.get_recent(n=3) return f""" CURRENT STATE: {current_obs} RECENT OBSERVATIONS (last 8h): {recent_obs} TODAY'S HEALTH DATA: {today_data} RECENT CHAT: {chat_history} QUERY: {query} """ Total context: approximately 1,800–2,200 tokens per query. 7.2 Model and Latency Model: llama-3.3-70b-versatile via Groq API Average response latency: 1.8s (P50), 2.9s (P95) Monthly cost: $0 (Groq free tier covers current query volume) Rate limit: 6,000 tokens/minute — never reached in production 7.3 Smart Narration — Activity Change Detection The system generates automatic WhatsApp messages when activity changes are detected, without requiring user queries: def should_send_narration( current_state: dict, previous_state: dict, last_message_time: datetime ) -> bool: # Always send on safety concerns if current_state.get("safety_concern"): return True # Minimum 30-minute gap between activity messages if (datetime.now() - last_message_time).seconds 60s Pushover HIGH No No feed > 3.5h Pushover NORMAL No Sleep > 4.5h Pushover NORMAL No Activity narration WhatsApp N/A No Tummy time detected Pushover LOW No 9.2 Deduplication COOLDOWN_PERIODS = { "activity_update": 1800, # 30 minutes "safety_alert": 600, # 10 minutes "cry_alert": 300, # 5 minutes "feed_reminder": 12600, # 3.5 hours "sleep_reminder": 16200, # 4.5 hours } 9.3 Night Mode Single WhatsApp command (/night) sets NIGHT_MODE=True. All non-safety alerts are suppressed. Safety alerts (face covered, safety concern) bypass night mode unconditionally. 10. Security Architecture 10.1 Twilio Webhook Validation Every incoming WhatsApp message validates the Twilio HMAC signature: from twilio.request_validator import RequestValidator def validate_twilio_request(request) -> bool: validator = RequestValidator(TWILIO_AUTH_TOKEN) return validator.validate( request.url, request.form, request.headers.get('X-Twilio-Signature', '') ) Requests failing validation return HTTP 403 and are logged. 10.2 Access Control AUTHORIZED_NUMBERS = {"+1XXXXXXXXXX", "+1XXXXXXXXXX"} # env var def is_authorized(phone_number: str) -> bool: return phone_number in AUTHORIZED_NUMBERS 10.3 Rate Limiting RATE_LIMITS = { "messages_per_minute": 10, # Per authorized number "camera_queries_per_30s": 1, # Vision API protection "photo_analysis_per_minute": 3, # Vision API protection } 10.4 Secrets Management All API keys stored as environment variables, loaded via python-dotenv. Never logged, never hardcoded. Cloudflare Tunnel eliminates port forwarding requirements — no inbound firewall rules needed. 11. Networking — Cloudflare Tunnel Twilio requires a public HTTPS endpoint for WhatsApp webhook delivery. Cloudflare Tunnel provides this without static IP or port forwarding: Twilio → https://[uuid].trycloudflare.com → Cloudflare Edge → cloudflared daemon (Pi) → Flask webhook (localhost:5001) The tunnel is managed as a systemd service with Restart=always. Tunnel reconnection is automatic. The public URL is stable across reconnections when using a named tunnel with a configured domain. 12. Automated Operations 12.1 Health Monitoring (Every 15 Minutes) HEALTH_CHECKS = [ check_all_services_running, # systemctl is-active for each check_camera_freshness, # last frame 80°C check_api_credentials, # test Anthropic + Groq connectivity check_tunnel_connectivity, # HTTP GET to webhook URL ] 12.2 Automated Backup (3 AM Daily) def backup_database(): source = sqlite3.connect(DB_PATH) backup = sqlite3.connect(BACKUP_PATH) source.backup(backup) # Hot backup — consistent under concurrent writes backup.close() source.close() 7-day rolling retention. Backup completes in under 1 second for the current database size (~1.1MB). 12.3 Automated Cleanup (2 AM Daily) Vision observations older than 7 days: purged Events older than 90 days: archived to separate table Weekly VACUUM to reclaim space Projected annual storage without cleanup: ~15MB Projected annual storage with cleanup: ~3MB 13. API Cost Summary Service Usage Monthly Cost Claude Haiku Vision ~51,840 images/mo (with dark frame skip) ~$1.50–$2.50 Groq LLaMA-70b ~500 queries/mo Free tier Twilio WhatsApp < 1,000 messages/mo Free sandbox Pushover Unlimited alerts $5 one-time Cloudflare Tunnel Always-on Free Gmail SMTP 2 emails/day Free Total monthly Under $5 14. Performance Characteristics Metric Value Vision analysis interval 30 seconds Vision API latency (P50) 1.2s Vision API latency (P95) 2.8s WhatsApp Q&A latency (P50) 1.8s WhatsApp Q&A latency (P95) 2.9s Cry detection latency 240ms Pi 5 CPU utilization (idle) 12–18% Pi 5 CPU utilization (vision processing) 35–45% Pi 5 temperature (sustained load) 62–68°C RAM utilization (all 9 services) 1.8–2.4GB Database size (3 months) ~1.1MB System uptime (measured period) 99.2% 15. Known Issues and Limitations Ring WebRTC library bug: RuntimeError: Task cannot await on itself appears in logs but does not crash the service or affect functionality. This is a known issue in the ring_doorbell library's asyncio implementation. Vision API dark frame behavior: Frames under 40KB are skipped entirely. In unusual lighting conditions (IR nightlight variations), the threshold may need adjustment. Cry detection false positives: The WebRTC VAD mode 2 setting produces approximately 8% false positives from environmental noise. Mode 3 (most aggressive filtering) reduces false positives to ~3% but increases false negatives to ~12%. Mode 2 is the production choice. Groq free tier limits: LLaMA-70b on Groq has a 6,000 tokens/minute limit. Current usage (~500 queries/month) is well within limits. At significantly higher query volumes, rate limiting may become a factor. SQLite vs PostgreSQL: SQLite WAL handles 9 concurrent writers without contention at current write volume (~100 writes/hour). At significantly higher write rates, migration to PostgreSQL would be warranted. 16. Tech Stack Reference Layer Technology Version OS Raspberry Pi OS (Debian 12) Linux 6.12 Language Python 3.11 Vision AI Claude Haiku (Anthropic) claude-haiku-4-5 Vision fallback Llama 4 Scout (Groq) llama-4-scout Language AI LLaMA-3.3-70b (Groq) llama-3.3-70b-versatile Audio VAD webrtcvad 2.0.10 Audio I/O PyAudio 0.2.14 Camera ring_doorbell 0.9.13 Database SQLite 3.45 (WAL mode) Web framework Flask 3.0.3 Dashboard Streamlit 1.35.0 SMS/WhatsApp Twilio 9.1.0 Notifications Pushover HTTP API Tunnel cloudflared 2024.x Service manager systemd 254 Computer vision (local) OpenCV 4.10 17. Conclusion BabyMind AI demonstrates that production-grade AI inference pipelines can be deployed on commodity embedded hardware at minimal cost. The key architectural decisions — SQLite WAL for concurrent writes, WebRTC VAD for low-latency audio processing, Claude Haiku for cost-efficient vision analysis, and WhatsApp as the user interface — each individually reduce complexity compared to alternatives. The system processes approximately 51,840 vision frames per month, handles 9 concurrent service writers to a single database without contention, and delivers natural language Q&A responses grounded in real-time observation data in under 2 seconds — all on a $60 device drawing under 10 watts. The codebase is approximately 3,500 lines of Python across 20 files. It will be open-sourced. Hardware cost: ~$60. Monthly operating cost: under $5. Codebase: ~3,500 lines Python. Services: 9 systemd units. Vision API calls: ~51,840/month. Database: SQLite WAL, 7 tables. Uptime: 99.2%. Tags: raspberry-pi · computer-vision · llm · iot · python · sqlite · systemd · claude · groq · whatsapp-api
How I Built a $5-a-Month AI Baby Monitor on Raspberry Pi
Full Article
Original Source
Read the full article at Hackernoon →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.