Mesh: én sentral fordeler til flere soner
Flere anlegg – hovedscene, resepsjon, crew – og ett sted å sende fra, med avkryssing for hvilke soner meldingen skal til. Sentralen er en vanlig announcer med [zones] i config. En sone er en vanlig announcer med ANNOUNCER_MESH_SECRET. Samme image, ingen mode, og en sone kan brukes alene hvis sentralen faller ut. Hver avhengighet med C-kode har aarch64-wheel i uv.lock, så det samme bildet går på en Raspberry Pi uten å kompilere noe. Sentralen syntetiserer én gang og sender ferdig WAV (POST /api/play, multipart). Sonen trenger da verken nett, edge-tts eller Pipers 670 MB for det den får – Item.wav er hele endringen i avspillingen. Køene forblir per sone: dispatcheren har egen tråd og egen kø, leverer til hver sone i hver sin tråd med timeout, og går videre. En opptatt hovedscene holder ikke igjen resepsjonen, og en sone som er nede feiler alene – synlig per sone i UI og revisjonslogg. Auth gjenbrukes: ny leverandør mesh på header X-Mesh-Key (ikke Bearer – den leses av JWT-leverandøren, som avviser alt som ikke er et JWT før neste får prøve), med én handling, play. To regler som fulgte med: - play følger aldri med «alt åpent». Uten det kunne hvem som helst POSTe en WAV til en åpen sone og få den spilt. Testen for åpen modus fanget det. - En legitimasjon som vises fram og er feil gir 401 også der gjester ellers har lov. Før slapp en feil PIN i åpen modus stille gjennom som gjest. mesh teller ikke som innlogging for folk: en sone med bare nøkkelen har fortsatt åpen side lokalt. Planlagte sendinger husker sonene sine og fordeles gjennom dispatcheren; planen ender som sent hvis minst én sone tok imot, med «hovedscene: kø #3, crew: nede» som note. Hodeløs Pi: install.sh kjører loginctl enable-linger, ellers finnes ikke lydsocketen når ingen logger inn. docs/mesh.md tar også raspi-config for jack framfor HDMI. 38 nye tester, 148 totalt. Fordelingen testes uten nett med stubbet transport; selve HTTP-en testes mot en levende werkzeug-server, så den håndrullede multiparten møter Flask sin parser. Verifisert i compose med sentral og to soner: til alle, bare til én, og én sone stoppet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
eb27a162d5
commit
c4a15799dd
17 changed files with 1486 additions and 23 deletions
119
docs/mesh.md
Normal file
119
docs/mesh.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Mesh: én sentral, flere soner
|
||||
|
||||
Flere anlegg – hovedscene, resepsjon, crewområde – og ett sted å sende fra,
|
||||
med avkryssing for hvilke soner meldingen skal til.
|
||||
|
||||
## Hvordan det virker
|
||||
|
||||
**Sentralen** er en vanlig announcer med `[zones]` i `data/config.toml`.
|
||||
**En sone** er en vanlig announcer med `ANNOUNCER_MESH_SECRET` i `.env`.
|
||||
Samme image, ingen «mode». En sone kan derfor brukes alene også: faller
|
||||
sentralen ut, går crew bort til sonens egen side og roper derfra.
|
||||
|
||||
Sentralen syntetiserer meldingen **én gang** og sender ferdig WAV til hver
|
||||
valgt sone (`POST /api/play`). Sonen trenger verken nett, edge-tts eller
|
||||
Pipers stemmer for det den får derfra – en Raspberry Pi med en høyttaler
|
||||
holder.
|
||||
|
||||
Køene er **per sone**. Er hovedscenen opptatt med en lang melding, går en
|
||||
resepsjonsmelding ut med en gang. Sentralen fordeler og går videre; hver
|
||||
sone køer selv. Faller én sone ut, feiler bare den – de andre får meldingen,
|
||||
og UI-et viser hvilken som ikke svarte.
|
||||
|
||||
Soner som får samme melding starter innen ~1 s av hverandre. Fint for
|
||||
adskilte rom. Lekker rommene inn i hverandre hører man det; synkron start
|
||||
mot NTP er en påbygging som ikke finnes ennå.
|
||||
|
||||
## Sette opp
|
||||
|
||||
### Sentralen
|
||||
|
||||
```toml
|
||||
# data/config.toml
|
||||
[zones]
|
||||
her = "local" # eget anlegg. Utelat hvis sentralen ikke har høyttalere
|
||||
hovedscene = "http://10.20.70.11:8090"
|
||||
resepsjon = "http://10.20.70.12:8090"
|
||||
crew = "http://10.20.70.13:8090"
|
||||
```
|
||||
|
||||
```bash
|
||||
# .env
|
||||
ANNOUNCER_MESH_SECRET=<openssl rand -hex 24>
|
||||
```
|
||||
|
||||
`docker compose restart`. Loggen sier `mesh: sentral for her, hovedscene, …`.
|
||||
|
||||
### Hver sone
|
||||
|
||||
```bash
|
||||
git clone https://git.1x1.no/kent/announcer.git && cd announcer
|
||||
ANNOUNCER_MESH_SECRET=<samme nøkkel> ./install.sh
|
||||
```
|
||||
|
||||
Loggen sier `mesh: sone, tar imot fra sentralen`. Ingen `[zones]` på sonen.
|
||||
|
||||
Sonen må nås fra sentralen på adressen i `[zones]`. Med NetBird setter
|
||||
`install.sh` `BIND_IP` til sonens NetBird-adresse – bruk den i `[zones]`.
|
||||
Uten NetBird: sett `BIND_IP=0.0.0.0` (eller LAN-adressen) i sonens `.env`.
|
||||
|
||||
### Raspberry Pi uten skjerm
|
||||
|
||||
To ting som ellers gir en sone som aldri sier noe:
|
||||
|
||||
1. **Lyd uten innlogging.** Lydsocketen ligger i `/run/user/<uid>` og finnes
|
||||
bare når brukerens systemd-instans kjører – normalt fra grafisk
|
||||
innlogging. En Pi i et skap har ingen som logger inn. `install.sh` kjører
|
||||
`loginctl enable-linger`, som starter brukerens tjenester (PipeWire
|
||||
inkludert) ved boot uansett. Sjekk: `ls /run/user/$(id -u)/pipewire-0`
|
||||
etter en omstart uten å logge inn.
|
||||
2. **Hvilken utgang.** En Pi med både HDMI og jack sender lyden til HDMI som
|
||||
standard – altså til skjermen som ikke er der. `sudo raspi-config` →
|
||||
System Options → Audio → jack (eller USB-lydkortet). Eller sett
|
||||
`ANNOUNCER_SINK` i `.env` til navnet fra `pactl list short sinks`.
|
||||
|
||||
## I bruk
|
||||
|
||||
Over «Send ut» står sonene som avkryssing, alle valgt som standard; valget
|
||||
huskes i nettleseren. Forhåndslytt gjelder lyden, ikke sonene – du kan bytte
|
||||
sone uten å lytte på nytt.
|
||||
|
||||
«Anlegget»-kortet viser én linje per sone: grønn prikk og hva som spilles,
|
||||
eller rød prikk og «nede». «Siste meldinger» viser hver fordeling med et
|
||||
merke per sone: `hovedscene ✓`, `crew ✗ nede`.
|
||||
|
||||
Planlagte sendinger husker sonene sine og overlever omstart av sentralen.
|
||||
|
||||
## Hva som kan gå galt
|
||||
|
||||
| | Hva du ser | Hva det betyr |
|
||||
|---|---|---|
|
||||
| Sone nede ved sending | `✗ nede` på den sonen, `✓` på resten | nådde ikke fram innen 5 s. Resten gikk |
|
||||
| `✗ avvist` | `401 Feil mesh-nøkkel` i merket | `ANNOUNCER_MESH_SECRET` er ulik på sentral og sone |
|
||||
| Rød prikk på en sone | «nede: …» i sonelista | statuspollingen når den ikke. Sjekk adresse i `[zones]` og `BIND_IP` på sonen |
|
||||
| Alle soner `✗ feilet` | feilmelding på fordelingen | syntesen feilet på sentralen (nettet nede og nevral stemme valgt). Velg en lokal stemme |
|
||||
| Sonen spiller ikke, men viser `feilet` | `fant verken pw-play eller paplay`-aktig feil i sonens kø | ingen lydsocket: se «Raspberry Pi uten skjerm» |
|
||||
| Sone restartet med melding i kø | meldingen borte | køen ligger i minnet, som for lokale meldinger. Akseptert |
|
||||
|
||||
Alt havner i sentralens `data/logs/announcements.jsonl` som `announce`
|
||||
(hvem, til hvilke soner) og `dispatched` (hva hver sone svarte), og i hver
|
||||
sones logg som `play`.
|
||||
|
||||
## Protokoll, for den som vil skrive sin egen sone
|
||||
|
||||
`POST /api/play`, header `X-Mesh-Key: <hemmelighet>`, multipart:
|
||||
|
||||
| felt | |
|
||||
|---|---|
|
||||
| `wav` | fila. 16-bit mono WAV, jingle og tale ferdig satt sammen |
|
||||
| `meta` | JSON: `origin_id`, `text`, `voice`, `user`, `subject`, `method` |
|
||||
|
||||
Svar `200 {"id": <kø-id>}`. Statusen ses i `GET /api/status` med samme
|
||||
header. Alt annet er 400 med `{"error": "…"}`; feil nøkkel er 401.
|
||||
|
||||
Egen header, ikke `Authorization: Bearer`: den leses av JWT-leverandøren,
|
||||
som avviser alt som ikke er et JWT før neste leverandør får prøve.
|
||||
|
||||
`play` følger aldri med «alt åpent». En sone uten `config.toml` lar hvem
|
||||
som helst sende lokalt, men ikke spille en fil de har med seg – det krever
|
||||
nøkkelen.
|
||||
Loading…
Add table
Add a link
Reference in a new issue