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:
Kent 2026-10-09 02:22:05 +02:00
parent eb27a162d5
commit c4a15799dd
17 changed files with 1486 additions and 23 deletions

View file

@ -25,6 +25,26 @@ quick_phrases = [
"Husk å rydde rundt plassen din. Takk!",
]
# --------------------------------------------------------------------------
# Mesh: én sentral som fordeler til flere soner
# --------------------------------------------------------------------------
# Settes BARE på sentralen. Navn -> "local" (eget anlegg) eller URL til en
# sone. Sonene er vanlige announcere med ANNOUNCER_MESH_SECRET satt i .env –
# samme hemmelighet som sentralen. En sone kan brukes alene også.
# Sentralen syntetiserer én gang og sender ferdig WAV; sonen trenger
# verken nett eller stemmer for det den får derfra.
#
# [zones]
# her = "local"
# hovedscene = "http://10.20.70.11:8090"
# resepsjon = "http://10.20.70.12:8090"
# crew = "http://10.20.70.13:8090"
[mesh]
# secret settes som ANNOUNCER_MESH_SECRET i .env, ikke her
timeout_seconds = 5
poll_seconds = 2
[server]
host = "0.0.0.0"
port = 8090
@ -123,6 +143,7 @@ label = "Logg inn med crew-kontoen"
# preview forhåndslytte (synteser lyd)
# announce sende ut på anlegget
# schedule planlegge en sending fram i tid, og avbryte sine egne
# play ta imot ferdig lyd fra sentralen (mesh). Bare rollen «mesh».
# stop stoppe det som spilles
# clear tømme køen
# "*" betyr alt.
@ -132,3 +153,5 @@ label = "Logg inn med crew-kontoen"
anonymous = []
crew = ["view", "preview", "announce", "schedule"]
admin = ["*"]
# Sentralen, når den snakker med denne maskina som sone. Ikke endre.
mesh = ["play", "view"]