# CLAUDE.md — SLMM (Sound Level Meter Manager) Device module for **Rion NL-43 / NL-53** sound level meters. Translates Terra-View's REST calls into the meter's ASCII command protocol over TCP, and pulls stored data over FTP. Current version: **v0.4.0**. Stack-level context (which repo owns what, version pairing) lives in `../terra-view/docs/tmi-stack.md`, which is also loaded as `~/CLAUDE.md`. **Terra-View never talks to a meter directly** — it always goes through here. SLMM owns the protocol; Terra-View owns the UI and the measurement records. --- ## Where things stand (updated 2026-08-29) v0.4.0 (2026-06-22) is the current release. The three things that define the current design: - **Fan-out live monitor.** The NL-43 has **one** TCP control connection, and every dashboard used to fight for it. Now a single poller reads the device and all subscribers share one cached feed: `WS /api/nl43/{unit_id}/monitor` delivers an instant first frame from cache, then live updates. Poll rate adapts to demand, unreachable devices back off, and the background poller skips units already covered by an active monitor so nothing double-polls. - **Alert engine.** Per-device threshold rules (metric + threshold + cooldown) with full CRUD, an onset/clear state machine, and acknowledgement. Enabled rules **pin the monitor on**, so they evaluate 24/7 with no UI client connected. Editing or deleting a rule resets its state and closes any open event. - **History for chart backfill.** A downsampled trail persists to `nl43_readings` and is served from `GET /api/nl43/{unit_id}/history`, so live charts can fill in recent history on load. L1/L10 percentiles are surfaced in status and the live feed. `CHANGELOG.md` is the authority for anything older; prefer it over prose here. --- ## Layout ``` app/ main.py FastAPI app + health checks routers.py all REST endpoints services.py NL43Client — TCP + FTP protocol monitor.py the fan-out live feed background_poller.py scheduled polling alerts.py rule evaluation + event state machine models.py NL43Config, NL43Status, nl43_readings database.py SQLAlchemy / SQLite (data/slmm.db) device_logger.py docs/ manuals/ templates/ nl52/ SLM-stress-test/ ``` Key docs: `docs/API.md` (endpoint reference with curl examples), `docs/COMMUNICATION_GUIDE.md` and `docs/nl43_Command_ref.md` (protocol). --- ## API surface ``` GET/PUT /api/nl43/{unit}/config GET /api/nl43/{unit}/status cached snapshot GET /api/nl43/{unit}/live fresh read POST /api/nl43/{unit}/{start|stop|pause|resume|reset|store} GET /api/nl43/{unit}/{battery|clock|results|settings} WS /api/nl43/{unit}/monitor fan-out live feed POST /api/nl43/{unit}/monitor/{start|stop} GET /api/nl43/_monitor/status GET /api/nl43/{unit}/history backfill trail .../alerts/rules .../alerts/events .../events/{id}/ack POST/GET /api/nl43/{unit}/ftp/{enable|disable|status|files|download} GET /api/nl43/{unit}/overwrite-check BEFORE changing a store name ``` --- ## Gotchas - **One TCP connection per meter.** This is the constraint the whole monitor design exists to work around. Never add a code path that opens its own connection alongside the monitor. - **1-second minimum between commands.** The NL-43 protocol requires it; SLMM enforces it automatically. Do not "optimise" it away. - **Check `overwrite-check` before changing a store name** — otherwise you can destroy data on the meter's SD card. - **FTP is active mode** — the device connects *back* to the server on port 21. - **`network_mode: host`** in compose, for direct network access to meters. - Environment: `PORT` (8100), `CORS_ORIGINS`, `TIMEZONE_OFFSET`, `TIMEZONE_NAME`.