Files
ISR/ROADMAP.md
T

155 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 01, 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,
50200 Hz) from speech (3003000 Hz) from vehicles would reduce false
positives further. Deferred — requires `numpy` and adds meaningful complexity.