Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48f2ab7954 | ||
|
|
0278c5fd06 | ||
|
|
5d0f31e584 | ||
|
|
518e6f287a |
@@ -0,0 +1,136 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog & release convention
|
||||||
|
|
||||||
|
Adopted 2026-09-18, to match seismo-relay and Terra-View. SLMM had no written
|
||||||
|
practice before this — earlier releases used ad-hoc commits like
|
||||||
|
`chore: version bump`. This is the standard going forward.
|
||||||
|
|
||||||
|
**Feature branches do NOT touch `CHANGELOG.md`. Write the entry on `dev`, as
|
||||||
|
part of finishing the merge, under `## [Unreleased]`. Cut the version on `dev` in a
|
||||||
|
dedicated release commit when you are ready to ship to `main`.**
|
||||||
|
|
||||||
|
- **The changelog is written on `dev`, never on a feature branch.** With
|
||||||
|
several branches in flight they all edit the same few lines at the top of
|
||||||
|
the file and conflict every time. Writing it once, after the merge, also
|
||||||
|
lets it describe what actually *landed* — including anything that changed
|
||||||
|
during conflict resolution.
|
||||||
|
- ⚠ **The merge is not finished until `## [Unreleased]` is updated.** Same sitting,
|
||||||
|
not "later" — that is the one failure mode of writing it after the fact.
|
||||||
|
Reconstruct from the branch's own commit messages:
|
||||||
|
`git log --oneline dev..<branch>` before you merge, or
|
||||||
|
`git log --oneline <merge-base>..<branch>` after.
|
||||||
|
- **No preamble under `## [Unreleased]`** — just the `### Added` / `### Changed`
|
||||||
|
/ `### Fixed` lists. The themed opening paragraph gets written at release
|
||||||
|
time, when the whole release is visible and can be named honestly. A theme
|
||||||
|
written when the first item landed is stale by the third.
|
||||||
|
- ⚠ **State the operational consequence**, **including when it is "none."**
|
||||||
|
Silence is ambiguous; "none" is information. For this repo that means:
|
||||||
|
- **DB migration** — whether a `migrate_*.py` script is needed, which one,
|
||||||
|
and whether it is safe to re-run. These live at the repo root and are
|
||||||
|
easy to forget at deploy time.
|
||||||
|
- **Meter-connection impact** — anything touching the NL-43/NL-53 TCP path,
|
||||||
|
polling cadence, or the monitor fan-out. The meter has a **single TCP
|
||||||
|
slot**, so a change that bounces the connection is an operational event,
|
||||||
|
not just a code change.
|
||||||
|
- **Releases are cut on judgement, not on a schedule or a merge.** `Unreleased`
|
||||||
|
is the staging area for whatever is going into the next release; when enough
|
||||||
|
has accumulated to be worth shipping, it gets a number and a date. Nothing
|
||||||
|
about a merge to `dev` triggers a release.
|
||||||
|
- **Cutting a release** is its own `chore(release): vX.Y.Z` commit on `dev`,
|
||||||
|
renaming `## [Unreleased]` → `## [X.Y.Z] - YYYY-MM-DD` and bumping the
|
||||||
|
version in `app/main.py` (the FastAPI `version=` argument).
|
||||||
|
- **`main` carries only released versions.** No `## [Unreleased]` section
|
||||||
|
there; it lands via the `dev` → `main` PR.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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`.
|
||||||
Reference in New Issue
Block a user