# 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= ``` `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= ./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/` 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: `, 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.