# 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å. ## Sonene melder seg selv Du trenger ikke skrive inn sonene på sentralen. En sone sender et hjerteslag til sentralen hvert 30. sekund (`POST /api/mesh/register` med navn, egen adresse og mesh-nøkkelen). Riktig nøkkel er godkjenningen: sonen dukker opp i lista med en gang og kan krysses av. Slutter den å slå – strøm, nett, flyttet – tas den ut etter 90 sekunder. Starter den igjen, er den tilbake innen fem. Det eneste en sone må vite er hvor sentralen er, og det løses lagvis: | Nett | Hvordan sonen finner sentralen | |---|---| | Samme LAN/VLAN | **mDNS.** Sentralen annonserer `_announcer._tcp`, sonen finner den selv. Null config | | NetBird | `ANNOUNCER_SENTRAL=http://.skynet.corp:8090` – multicast går ikke gjennom tunnelen, men NetBird-DNS gjør navnet stabilt | | Begge satt | `ANNOUNCER_SENTRAL` vinner; mDNS brukes bare når den er tom | Faste adresser i `[zones]` virker fortsatt, og vinner over et navn som melder seg – en skrivefeil i config skal ikke kunne overstyres av en boks på nettet. ## Sette opp ### Sentralen ```bash # .env ANNOUNCER_MESH_SECRET= ANNOUNCER_ROLE=sentral ANNOUNCER_NAME=her # navnet på sentralens eget anlegg i lista ``` `docker compose restart`. Loggen sier `mesh: sentral «her», soner nå: her` og `mDNS: annonserer her som sentral`. Sonene kommer etter hvert som de melder seg. Har sentralen ikke eget anlegg: `ANNOUNCER_LOCAL=0`. ### Hver sone, på samme LAN ```bash git clone https://git.1x1.no/kent/announcer.git && cd announcer ANNOUNCER_MESH_SECRET= ANNOUNCER_NAME=hovedscene ./install.sh ``` Det er alt. Loggen sier `mDNS: fant sentral her på http://…` og `meldt hos sentralen … som hovedscene`. ### Hver sone, over NetBird Lag én setup-nøkkel i NetBird (Setup Keys → reusable, auto-gruppe `announcer`), og én policy `announcer → announcer, TCP 8090`, så sentral og soner når hverandre. Så, på hver boks: ```bash NETBIRD_SETUP_KEY= \ ANNOUNCER_MESH_SECRET= \ ANNOUNCER_NAME=resepsjon \ ANNOUNCER_SENTRAL=http://announcer-her.skynet.corp:8090 \ ./install.sh ``` `install.sh` installerer NetBird om den mangler, melder maskina inn som `announcer-resepsjon`, venter på `wt0`, og binder appen til NetBird-adressen. Sentralen heter `announcer-` i NetBird av samme regel, så `ANNOUNCER_SENTRAL` kan stå fast fra arrangement til arrangement. Selvhostet management-server leses fra `/etc/netbird/config.json` hvis den finnes fra før; ellers `NETBIRD_MANAGEMENT_URL=…`. ### Vertens nett Containeren bruker `network_mode: host`. Det er det som gjør at mDNS når LAN-et, at `wt0` er synlig, og at **ufw styrer porten igjen** – det gamle Docker-smutthullet er borte. `BIND_IP` i `.env` avgrenser i tillegg hvilke adresser appen svarer på; `127.0.0.1` er alltid med, så helsesjekken virker. ### Raspberry Pi uten skjerm To ting som ellers gir en sone som aldri sier noe: 1. **Lyd uten innlogging.** Lydsocketen ligger i `/run/user/` 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 | | Sonen står ikke i lista | sonens egen side sier «ingen sentral funnet» | mDNS når ikke fram (annet VLAN, eller `ANNOUNCER_MDNS=0`), og `ANNOUNCER_SENTRAL` er tom. Sett den | | Sonen sier «401 Feil mesh-nøkkel» | i sonens logg, hvert 5. s | nøkkelen er ulik. Sentralen avviser, og sonen kommer aldri i lista | | Sonen forsvant fra lista | `sone x har ikke meldt seg på 90 s – tatt ut` i sentralens logg | strøm, nett, eller sonen fikk ny IP og melder seg på nytt derfra om et øyeblikk | | Sentralen ser sonen, men `✗ nede` | adressen sonen meldte er ikke nåbar fra sentralen | sonen regnet ut feil egen adresse (to nett). Sett `ANNOUNCER_URL` på sonen | | `✗ 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 Melde seg: `POST /api/mesh/register`, header `X-Mesh-Key`, JSON `{"name": "resepsjon", "url": "http://100.75.12.3:8090", "version": "0.3"}` hvert 30. sekund. Svar `200 {"ok": true, "stale_seconds": 90}`. `409` hvis navnet er opptatt av config på sentralen. Sentralen kaller så `url` tilbake for lyd og status – den må altså være nåbar *fra sentralen*. mDNS: tjenestetypen er `_announcer._tcp.local.`, TXT `role=sentral`, `name=`, `version=`. Soner trenger ikke annonsere; de melder seg. `POST /api/play`, header `X-Mesh-Key: `, 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": }`. 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.