chore: remove NTFY notification roadmap
Not being pursued — the planning doc just adds noise. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
-154
@@ -1,154 +0,0 @@
|
|||||||
# ISR Roadmap
|
|
||||||
|
|
||||||
## notify.py — NTFY Loudness Notifications
|
|
||||||
|
|
||||||
### Context
|
|
||||||
|
|
||||||
Street ambience recorder. Goal: detect notable audio events (speech, thunder,
|
|
||||||
sustained unusual sounds) in hourly recording files and push a notification via
|
|
||||||
a self-hosted NTFY server. Generic short events (car horn, passing vehicle)
|
|
||||||
should be filtered out by a minimum section duration.
|
|
||||||
|
|
||||||
### Design decisions
|
|
||||||
|
|
||||||
| Topic | Decision |
|
|
||||||
|---|---|
|
|
||||||
| Detection | RMS + minimum section duration filter (KISS — no FFT for now) |
|
|
||||||
| Timing | Configurable: `immediate` / `daily` / `both` |
|
|
||||||
| Config | `[notify]` section in existing `config.ini` |
|
|
||||||
| Code structure | `notify.py` imports `analyze_wav` / `analyze_flac` from `web.py` (DRY) |
|
|
||||||
| Source name | Included in notification body; configurable display name per source |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Config additions (`config.example.ini`)
|
|
||||||
|
|
||||||
Add a `[notify]` section to `config.ini`:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[notify]
|
|
||||||
enabled = true
|
|
||||||
ntfy_url = https://ntfy.example.com/mytopic ; full URL incl. topic
|
|
||||||
mode = immediate ; immediate | daily | both
|
|
||||||
daily_time = 08:00 ; HH:MM — used in daily and both modes
|
|
||||||
debounce_minutes = 60 ; immediate mode: suppress repeat notifications within this window
|
|
||||||
min_section_duration = 2.0 ; seconds — sections shorter than this are ignored (filters car horns etc.)
|
|
||||||
min_sections = 1 ; number of qualifying sections required to trigger a notification
|
|
||||||
loudness_threshold = 0.05 ; RMS 0–1, same scale as web.py analysis threshold
|
|
||||||
```
|
|
||||||
|
|
||||||
Per recording source, add an optional `display_name`:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[radio1]
|
|
||||||
type = stream
|
|
||||||
url = http://icecast.example.com:8000/live
|
|
||||||
display_name = Street mic north ; shown in notification; defaults to section name [radio1]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Notification format
|
|
||||||
|
|
||||||
```
|
|
||||||
Title: ISR — Notable audio · Street mic north
|
|
||||||
Body: radio1_20260427_0300.wav
|
|
||||||
3 notable sections (≥ 2.0 s each)
|
|
||||||
→ 00:12 – 00:18
|
|
||||||
→ 01:45 – 01:52
|
|
||||||
→ 47:03 – 47:11
|
|
||||||
Peak RMS: 0.312
|
|
||||||
```
|
|
||||||
|
|
||||||
Daily digest example:
|
|
||||||
|
|
||||||
```
|
|
||||||
Title: ISR Daily Digest · 2026-04-27
|
|
||||||
Body: Street mic north — 4 files with notable events
|
|
||||||
03:00 file · 3 sections (peak 0.312)
|
|
||||||
07:00 file · 1 section (peak 0.091)
|
|
||||||
14:00 file · 2 sections (peak 0.204)
|
|
||||||
21:00 file · 1 section (peak 0.178)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Implementation plan
|
|
||||||
|
|
||||||
#### Phase 1 — Core
|
|
||||||
|
|
||||||
1. **`config.example.ini`** — add `[notify]` section and `display_name` key to
|
|
||||||
source section examples (as shown above).
|
|
||||||
|
|
||||||
2. **`notify.py` — file watcher**
|
|
||||||
- Polls `recordings/status.json` every 30 s.
|
|
||||||
- Tracks which files were in `active` on the previous poll.
|
|
||||||
- When a file disappears from `active` it was just closed → queues it for
|
|
||||||
analysis.
|
|
||||||
- Skips files with extensions that cannot be analysed (anything other than
|
|
||||||
`.wav` / `.flac`).
|
|
||||||
|
|
||||||
3. **`notify.py` — analysis + filter**
|
|
||||||
- Imports `analyze_wav` / `analyze_flac` from `web.py`.
|
|
||||||
- Applies `loudness_threshold` from `[notify]` config.
|
|
||||||
- Filters resulting sections to those with duration ≥ `min_section_duration`.
|
|
||||||
- Counts filtered sections against `min_sections` threshold.
|
|
||||||
|
|
||||||
4. **`notify.py` — NTFY HTTP POST**
|
|
||||||
- Plain `urllib` POST to `ntfy_url` (no extra dependencies).
|
|
||||||
- Sets `Title` and message body as described above.
|
|
||||||
- Logs success / failure to stdout.
|
|
||||||
|
|
||||||
#### Phase 2 — Cadence modes
|
|
||||||
|
|
||||||
5. **Immediate mode with debounce**
|
|
||||||
- Fires right after the file closes and analysis passes.
|
|
||||||
- Persists last-notification timestamp per source to a small
|
|
||||||
`notify_state.json` in the recordings directory.
|
|
||||||
- Suppresses sending if last notification for that source was within
|
|
||||||
`debounce_minutes`.
|
|
||||||
|
|
||||||
6. **Daily digest mode**
|
|
||||||
- Appends qualifying events to `notify_log.jsonl` in the recordings
|
|
||||||
directory (one JSON line per event: timestamp, source, filename, sections,
|
|
||||||
peak RMS).
|
|
||||||
- On each poll checks whether `daily_time` has passed today and no digest
|
|
||||||
has been sent yet (tracked in `notify_state.json`).
|
|
||||||
- Reads all undigested entries from `notify_log.jsonl`, groups by
|
|
||||||
`display_name`, sends one notification per source with notable activity.
|
|
||||||
- Marks entries as digested.
|
|
||||||
|
|
||||||
7. **Both mode**
|
|
||||||
- Immediate path: only fires when peak RMS exceeds a second, higher
|
|
||||||
threshold (`alarm_threshold`, default `0.3`; add to `[notify]` config).
|
|
||||||
- Daily digest path: fires for everything that passes `min_sections`.
|
|
||||||
|
|
||||||
#### Phase 3 — Integration
|
|
||||||
|
|
||||||
8. **Docker** — optional `notify` service in `docker-compose.yml`:
|
|
||||||
```yaml
|
|
||||||
notify:
|
|
||||||
build: .
|
|
||||||
command: python notify.py
|
|
||||||
volumes:
|
|
||||||
- ./recordings:/app/recordings
|
|
||||||
- ./config.ini:/app/config.ini:ro
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
9. **README** — new section documenting `notify.py` usage, config keys, and
|
|
||||||
Docker setup.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Open questions (decide before implementing)
|
|
||||||
|
|
||||||
- **Log rotation**: `notify_log.jsonl` grows indefinitely. Options: cap at N
|
|
||||||
days (configurable), cap at N MB, or leave cleanup to the user. No decision
|
|
||||||
made yet.
|
|
||||||
- **Multiple NTFY topics per source**: current design uses one global topic.
|
|
||||||
If per-source topics are ever needed, `ntfy_url` could be moved to the source
|
|
||||||
section and override the global one.
|
|
||||||
- **FFT / frequency analysis** (future): distinguishing thunder (low rumble,
|
|
||||||
50–200 Hz) from speech (300–3000 Hz) from vehicles would reduce false
|
|
||||||
positives further. Deferred — requires `numpy` and adds meaningful complexity.
|
|
||||||
Reference in New Issue
Block a user