Feature: Macropad Integration (Stream Dock M18)
Version: 0.1.0 Last Reviewed: 2026-02-15 Status: Draft
User Story
As a parent, I can press physical buttons on macropads placed around the house (nursery, changing table, etc.) to quickly log baby events and control smart home devices, so that tracking is effortless even at 3 AM with one hand.
Research Summary
Device: VSD Inside Stream Dock M18
Hardware Specifications (from official Python SDK docs + GitHub source):
- 15 physical buttons (keys 1-15), arranged in a 5x3 grid
- 64x64 pixel key icons — JPEG format, each button displays a custom image
- 480x272 pixel touchscreen — full background image for dashboard display
- RGB LED ring — 24 LEDs, full RGB color (0-255), brightness 0-100%, effects
- USB HID communication — HIDAPI library, Vendor IDs
0x5500/0x5548 - Multiple hardware revisions: M18, M18V2, M18V25, M18V3 (all SDK-supported)
Key Capabilities for Baby Tracking:
- Button press/release events with callback support
- Custom 64x64 icons per button (e.g., bottle, diaper, moon icons)
- Full 480x272 dashboard on touchscreen (last feeding time, timer, etc.)
- LED color feedback (green=success, red=error, pulse=sleep active)
- Hot-plug detection via
DeviceManager.listen() - Async callback support for event loop integration
Python SDK
Package: streamdock-sdk — installed from GitHub source (NOT available on PyPI)
Source: https://github.com/MiraboxSpace/StreamDock-Device-SDK/tree/main/Python-SDK
Core API:
| Method | Description |
|---|---|
DeviceManager() | Create manager, enumerate devices |
device.open() / device.close() | USB connection lifecycle |
device.init() | Wake screen, set brightness, clear icons |
device.set_key_image(key, path) | Set 64x64 JPEG icon on button (1-15) |
device.set_touchscreen_image(path) | Set 480x272 background |
device.set_key_callback(fn) | Sync callback: fn(device, key, state) |
device.set_key_callback_async(fn) | Async callback for asyncio |
device.set_led_color(r, g, b) | Set LED ring color |
device.set_led_brightness(0-100) | LED brightness |
PILHelper.create_image(device, bg) | Create blank key-sized image |
Dependencies: Pillow, pyusb, pyudev (Linux), libhidapi
Raspberry Pi Zero 2 W
Recommended over original Pi Zero (quad-core ARM Cortex-A53 vs single-core ARM11).
- OS: Raspberry Pi OS Lite (Bookworm, 64-bit) — headless, Python 3.11+
- USB: Single micro-USB OTG port; needs adapter for Stream Dock
- Power: 5V/3A recommended (Pi + USB device draw)
- Auto-start: systemd service + Python venv
udev rules required for non-root USB access:
SUBSYSTEM=="usb", ATTR{idVendor}=="5500", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="5548", MODE="0666"
Home Assistant Integration
Approach: REST API (simplest, recommended for MVP)
requests.post(f"{HA_URL}/api/services/light/toggle",
headers={"Authorization": f"Bearer {HA_TOKEN}"},
json={"entity_id": "light.nursery"})
Future: WebSocket API for real-time state updates (button icons reflect light on/off state).
Architecture Decision: Standalone Repository
NOT part of Baby Basics monorepo. Rationale:
- Different runtime (Python on Pi vs Node.js + Swift)
- Different deployment model (flashed to Pi appliances vs server/App Store)
- Different release cadence (hardware integration evolves independently)
- Home Assistant integration is orthogonal to Baby Basics core
- Each macropad runs independently; config is per-device
- Simpler for potential open-sourcing
Proposed Repo Structure
baby-macropad/
pyproject.toml
src/baby_macropad/
main.py # Entry point
config.py # YAML config loader
device.py # StreamDock M18 wrapper
actions/
baby_basics.py # Baby Basics API client
home_assistant.py # HA REST API client
composite.py # Multi-action (log + dim light)
ui/
icons.py # Icon generation (Pillow)
dashboard.py # Touchscreen layout
feedback.py # LED + icon feedback
offline/
queue.py # SQLite offline buffer
sync.py # Background sync worker
web/
app.py # FastAPI config UI
config/
default.yaml # Default button layout
icons/ # Icon assets
tests/
scripts/
install.sh # Pi setup script
Configuration via YAML
device:
brightness: 80
led_idle_color: [30, 0, 60] # dim purple
baby_basics:
api_url: "https://baby.bretzfam.com/api"
token: "${BB_API_TOKEN}"
child_id: "uuid-here"
home_assistant:
url: "http://homeassistant.local:8123"
token: "${HA_TOKEN}"
buttons:
1:
label: "Breast L"
icon: "breast_left.png"
action: baby_basics.log_feeding
params:
type: breast
started_side: left
feedback:
success_led: [0, 255, 0]
2:
label: "Breast R"
action: baby_basics.log_feeding
params:
type: breast
started_side: right
3:
label: "Bottle"
action: baby_basics.log_feeding
params:
type: bottle
amount_ml: 120
4:
label: "Wet"
action: baby_basics.log_diaper
params:
type: wet
5:
label: "Dirty"
action: baby_basics.log_diaper
params:
type: dirty
6:
label: "Sleep"
action: baby_basics.toggle_sleep
7:
label: "Note"
action: baby_basics.log_note
params:
content: "Quick note logged"
# Home Assistant
11:
label: "Nursery Light"
action: home_assistant.toggle
params:
entity_id: "light.nursery"
12:
label: "Night Light"
action: home_assistant.toggle
params:
entity_id: "light.nightlight"
13:
label: "Fan"
action: home_assistant.toggle
params:
entity_id: "fan.nursery"
14:
label: "Sound Machine"
action: home_assistant.toggle
params:
entity_id: "media_player.sound_machine"
15:
label: "All Off"
action: home_assistant.scene
params:
entity_id: "scene.nursery_off"
Tech Stack
| Component | Choice | Rationale |
|---|---|---|
| Python | 3.11+ | Pre-installed on Pi OS Bookworm, good async |
| Stream Dock SDK | From GitHub source | No PyPI package; pin to git commit |
| Web framework | FastAPI | Async-native, auto docs, Jinja2 templates |
| ASGI server | Uvicorn | Lightweight, works on Pi Zero 2 W |
| Config | YAML + Pydantic | Human-editable, validated at startup |
| Offline queue | SQLite (stdlib) | Zero dependency, reliable event buffer |
| HTTP client | httpx | Async support, timeout handling |
| Image generation | Pillow | Required by StreamDock SDK already |
| Process manager | systemd | Native on Pi OS, restart on crash |
Baby Basics API Endpoints Used
All endpoints under /children/:childId/:
| Action | Method | Endpoint |
|---|---|---|
| Log feeding | POST | /children/:id/feedings |
| Log diaper | POST | /children/:id/diapers |
| Start sleep | POST | /children/:id/sleeps |
| End sleep | PATCH | /children/:id/sleeps/:sleepId |
| Log note | POST | /children/:id/notes |
| Get dashboard | GET | /children/:id/dashboard |
Offline Buffering Strategy
- SQLite queue on Pi: When API unreachable, store events with timestamp + payload
- Retry on reconnect: Background thread polls, flushes queue on recovery
- LED feedback: Green flash=success, yellow pulse=queued offline, red=persistent failure
- Idempotency: Client-generated UUID per event prevents duplicates on retry
- Queue size: 1000+ events; aggressive retry (exponential backoff 1s-60s)
MVP Scope
Phase 1: Core Baby Tracking
- Stream Dock M18 device detection and initialization
- YAML-based button configuration
- Baby Basics API client (auth, POST feedings/diapers/sleeps)
- Key icon rendering (Pillow, text + simple icons)
- LED feedback on button press (green flash = success)
- Touchscreen dashboard showing last event times
- Offline SQLite queue with background sync
- systemd service for auto-start on boot
- Pi setup script (
install.sh)
Phase 2: Home Assistant
- HA REST API client (toggle lights, fans, scenes)
- Button-to-entity mapping in YAML
- State-aware icons (light ON = yellow, OFF = gray)
Phase 3: Web Config UI
- FastAPI web app on Pi (port 8080)
- View/edit button layout
- Monitor event log and sync status
NOT in MVP
- WebSocket HA state subscription (Post-MVP)
- Multi-child support / page buttons (Post-MVP)
- OTA update mechanism (Post-MVP)
- Sleep timer display on touchscreen with elapsed time (Post-MVP)
- Suggested feeding side from dashboard API (Post-MVP)
- 3D-printed Pi Zero + M18 mounting bracket (Post-MVP)
Relevant Open Source Projects
| Project | Stars | Relevance |
|---|---|---|
| basnijholt/home-assistant-streamdeck-yaml | 351 | Closest architecture model. YAML button-to-HA action mapping. |
| MiraboxSpace/StreamDock-Device-SDK | — | Official SDK source (Python + C++) |
| tobimori/streamdeck-homeassistant | 82 | Elgato Stream Deck HA plugin. Service call patterns. |
| python-elgato-streamdeck | ~500+ | Gold-standard Python Stream Deck library. Architecture reference. |
| 4dcu-be/MacroPad | 3 | Raspberry Pi powered macropad. Pi + USB integration. |
Known Gotchas
- SDK maturity: Relatively new, maintained by one developer. Mix of Chinese/English docs. Small community — expect to read source code.
- No PyPI package: Must install from GitHub source. Pin to specific commit.
- Temp file pattern:
set_key_image()saves temp files to disk. Use tmpfs mount on Pi to avoid SD card wear. - Thread safety:
DeviceManageris NOT thread-safe. Callbacks run in USB read thread — keep handlers fast, offload to queue. - Pi Zero USB: Single OTG port needs adapter. Stream Dock may need powered USB hub if power draw is too high.
- 64x64 icon size: Very small. Use high-contrast simple icons, not text labels.
- Security: API tokens stored on Pi filesystem. Use
chmod 600on config. Keep Pi on trusted home network. - Multiple macropads: Each Pi is independent. Consider shared provisioning script for fleet setup.
- ctypes native loading: SDK loads
libhidapivia ctypes. ARM build ships with Pi OS apt but verify compatibility.
Boundaries
- This spec covers the macropad appliance only (standalone Python app on Pi)
- Baby Basics API and iOS app are separate — macropad is a client
- Home Assistant setup/configuration is out of scope (assumes HA is already running)
- Hardware purchasing and physical setup are out of scope