Architecture Overview
Control Flow
Section titled “Control Flow”main() -> NewDaemon() -> Run() |-- Unix socket listener Command routing (status, track, privacy, PTZ, ...) |-- HTTP server Web UI + API + Prometheus /metrics |-- Polling ticker (2s) /proc scanning -> call detection + debounce |-- Netlink uevent listener USB hotplug detection `-- systemd sd_notify READY=1 + WATCHDOG=1Source Layout
Section titled “Source Layout”| File | Purpose |
|---|---|
main.go |
Daemon struct, lifecycle, signal handling, entry point |
commands.go |
Command routing for Unix socket and CLI |
handlers.go |
HTTP routing, web handlers, toast propagation |
ptz.go |
PTZ logic: parsing, axis dispatch, V4L2 control |
metrics.go |
OTel metrics registration and updates |
stream.go |
MJPEG streaming, snapshot, JPEG frame extraction |
sse.go |
SSE broadcaster (thread-safe fan-out) |
http.go |
HTTP helpers: writeJSON, middleware, security headers |
hid.go |
HID bidirectional communication over hidraw |
device.go |
HID device state management |
process.go |
/proc scanning for call detection, PipeWire, notifications |
uevent.go |
Netlink uevent listener for hotplug |
auto.go |
Auto-manage loop, call start/end handling, debounce |
state.go |
State persistence (JSON, atomic write) |
probe.go |
Device probing (sysfs walks for video4linux + hidraw) |
waybar.go |
Waybar integration: JSON struct, tooltip builder |
cache.go |
Named cache types: lastFrameCache, ptzCache |
templates.templ |
HTML templates (compiled via templ generate) |
internal/pixy/ |
Shared types: Config, State, CameraState, AudioMode |
Concurrency Model
Section titled “Concurrency Model”| Lock | Scope |
|---|---|
Daemon.mu (sync.RWMutex) |
Protects state, videoDev, hidrawDev, debounce counters |
Daemon.hidMu (sync.Mutex) |
Serializes HID device access (tracking, audio, gesture) |
Daemon.v4l2Mu (sync.Mutex) |
Serializes V4L2 subprocess access (PTZ, center, presets) |
Daemon.streamSema (chan, cap 1) |
Limits to one MJPEG stream |
Daemon.lastFrame |
Has its own sync.RWMutex |
Daemon.ptzCache |
Has its own sync.RWMutex, 2-second TTL |
Daemon.broadcaster |
Thread-safe SSE fan-out, non-blocking sends |
The HID and V4L2 locks are separate to allow concurrency: the 200ms HID protocol sleep does not block V4L2 commands.
Key Design Decisions
Section titled “Key Design Decisions”HID Protocol
Section titled “HID Protocol”Commands are 9-byte config reports followed by a commit report, with a 200ms sleep between them. Responses are 64-byte reads parsed by byte position.
State Persistence
Section titled “State Persistence”JSON file at {StateDir}/state.json, atomic write via .tmp + rename. Loaded state always wins over defaults. The state file carries a schema version for forward compatibility.
Call Detection
Section titled “Call Detection”Scans /proc/*/fd for processes holding the video device open, excluding self and descendants. Debounced (default 3 cycles x 2s = 6s).
Dependency Injection
Section titled “Dependency Injection”All external interactions (HID, v4l2, PipeWire, notifications) are function fields on Daemon, enabling full test injectability without interfaces.
Branded Types
Section titled “Branded Types”PID and SourceID use phantom-type branding (go-branded-id) to prevent mixing process IDs and PipeWire source IDs at compile time.
API Endpoints
Section titled “API Endpoints”| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Web UI (HTML) |
/panel |
GET | Status panel (DataStar SSE partial) |
/api/events |
GET | SSE stream for live updates |
/api/health |
GET | JSON health check |
/api/status |
GET | JSON status for shell widgets |
/api/snapshot |
GET | Capture still frame |
/api/stream |
GET | MJPEG stream |
/api/track |
POST | Enable tracking |
/api/idle |
POST | Set idle |
/api/privacy |
POST | Enable privacy |
/api/toggle-privacy |
POST | Toggle privacy |
/api/audio/{mode} |
POST | Set audio mode (nc, live, original) |
/api/auto |
POST | Toggle auto mode |
/api/gesture |
POST | Toggle gesture |
/api/center |
POST | Center camera |
/api/sync |
POST | Sync state from hardware |
/api/probe |
POST | Re-probe device |
/api/ptz/{axis} |
POST | Set PTZ value (pan, tilt, zoom) |
/api/speed/{axis} |
POST | Set motor speed (pan, tilt, zoom) |
/api/tracking/{variant} |
POST | Set tracking variant (face/halfbody/fullbody) |
/api/preset/save/{name} |
POST | Save preset |
/api/preset/load/{name} |
POST | Load preset |
/api/preset/delete/{name} |
POST | Delete preset |
/api/preset/push/{name} |
POST | Push preset to hardware slots (moves camera) |
/api/preset/pull |
POST | Sweep hardware slots into hw-* presets |
/metrics |
GET | Prometheus metrics |
/static/* |
GET | Embedded assets (JS, CSS, DataStar) |
/debug/pprof/* |
GET | pprof endpoints (only when debug=true) |
Where to go next
Section titled “Where to go next”- HID Protocol — camera control over hidraw in depth
- Call Detection — how calls are detected via
/proc - Configuration — env vars and state persistence
- Prometheus Metrics — observability endpoints
- Contributing — extend the daemon