# Classroom messaging The rooms talk to the church over MQTT. `classroom-mqtt` is the link: one small service per room that subscribes, validates what arrives, and hands the work to the classroom commands that already exist. **Nothing in the room depends on it.** A broker that is down, a network that is missing, a machine with no broker configured at all — each of those is a service sitting quietly, not a classroom that fails. The dock, the browser and the DVD player never ask whether it is connected, and nothing waits for it at boot. ## The room is the machine The room name is the hostname: `nursery`, `k2`, `older`. Lowercased, domain stripped, and anything that is not a letter, digit, `-` or `_` replaced, because it becomes a topic segment. `ROOM=` in device.conf overrides it for a machine whose hostname cannot be changed. ## Topics No product name appears in any topic. The broker is the church's and these are just rooms. | Topic | Retained | Direction | Meaning | | --- | --- | --- | --- | | `classroom/notify/all` | no | to the rooms | Show this in every room | | `classroom/notify/` | no | to the rooms | Show this in one room | | `classroom/status/` | **yes** | from the room | Whether that room is up | Notifications are events, so they are never retained. Status is state, so it is — published on connect, and set to `online: false` by the broker's last will if the room drops off. ### Notification payload ```json { "id": "unique-id", "title": "Sermon Starting", "message": "The sermon has started in the sanctuary.", "urgency": "normal", "expires_at": 1780000000 } ``` Only `title` is required. `urgency` is `low`, `normal` or `critical` and anything else becomes `normal`. `expires_at` is epoch seconds. `id` is what deduplication works on; without one, a message is shown every time it arrives. ### Status payload ```json {"room": "nursery", "online": true, "version": "0.1.36", "since": 1780000000} ``` ## What gets dropped Every one of these is ordinary traffic, logged and counted, never a reason to fall over: - retained notifications (see below) - payloads that are not JSON, or not a JSON object - a missing, blank or non-text title - a message or id that is not text - an `expires_at` that is not a time, or is in the past - an id already seen — the last 512 are remembered ## Stale messages cannot come back This is the property that matters most, and it has two halves. **The subscription is clean.** The client connects with a clean session, so a broker never hands over a backlog from while the room was off. Yesterday's message is not news. **Retained notifications are refused.** If someone publishes a notification with the retain flag, the broker will replay it to every room that subscribes afterwards — at every reboot, forever. Those are dropped on arrival. One honest limitation: MQTT only sets the retain flag on a message *replayed to a new subscription*. A retained publish delivered live to an already-connected room is indistinguishable from an ordinary one, and is shown once. That is the correct outcome — it is a live message at that moment — and the replay, which is the actual hazard, is blocked. Publishers should still not retain notifications. ## When the broker goes away The service stays up and paho retries with a backoff from 2 to 60 seconds. The disconnection is logged once, not once per attempt, so a broker down for a morning does not fill the journal. On reconnect the subscriptions are re-established and the retained status is republished. Verified against a real broker: the daemon survives the broker being killed, reconnects when it returns, and delivers the next message normally. ## Configuration In `/etc/ekklesia-classroom/device.conf`: ``` MQTT_ENABLED=false MQTT_HOST= MQTT_PORT=1883 MQTT_USERNAME= MQTT_PASSWORD= ``` With `MQTT_ENABLED=false`, or no host, the service logs one line and exits. That is the shipped default: every existing classroom upgrades into this release with messaging off until a broker exists. Because the broker password lives here, device.conf is now `0640 root:classroom` rather than world readable. Everything that reads it — the dock, the notification daemon, the DVD commands, the updater — runs as root or as the classroom user. ## Trying it ``` mosquitto_pub -h -t classroom/notify/nursery \ -m '{"id":"t1","title":"Sermon Starting","message":"The sermon has started."}' mosquitto_sub -h -t 'classroom/status/#' -v journalctl -u ekklesia-classroom-mqtt -f ``` ## What this is for Parent Call and Sanctuary Live View are next, and both need a room to be reachable. The transport is deliberately separate from what it carries: `classroom_mqtt` holds room identity, the topic contract and the rules for an acceptable message, with no broker library in it at all, so those decisions can be tested without one running.