Institutional Audio Scheduler · Virtual Muezzin · PA System
One small box that plays the right sound, in the right room, at exactly the right second.
Multilarm turns any Windows PC, Linux server or a £35 Raspberry Pi into a precision, year-round audio scheduler and live public-address system — fully offline, endlessly configurable, and quiet until the moment it matters.
Adhan & prayer times
School bells
Factory shift sirens
Care-home announcements
Hotel & gym zones
Hospital PA
What is Multilarm?
Multilarm is a lightweight console application that plays specified audio files at specified times — recurring every day, week, month or across the whole year — and gives you live remote control of the whole thing from your phone, your office, or anywhere on the internet.
It was born to solve one stubborn problem: a mosque wanting a single, reliable device that calls the Adhan at the correct prayer time every day of the year, with no subscription, no cloud dependency, and no monitor or keyboard plugged in. From that root it grew into a general-purpose institutional audio engine — the same scheduling brain happily runs school bells, factory shift sirens, care-home activity announcements, hotel and gym background music with timed messages, and hospital paging.
Install it on a Raspberry Pi tucked behind an amplifier, point it at your audio folders, set your times, and walk away. It will keep working through reboots, power cuts and years of seasons — and when you do want to change something, you never need to touch the box again.
Who is this guide for?
①
First-time users
Start with the Quick Start and Installation pages — you'll have sound playing on schedule within minutes.
How to use this helpUse the navigation tree on the left, or type in the Filter topics box to jump straight to a page. Every page is self-contained and cross-linked — follow the green links freely. Use the Previous / Next buttons at the bottom of each page to read it like a manual.
Why Multilarm
Built for places that cannot afford to miss a cue.
A missed prayer call, a bell that rings a minute late, an evacuation tone that doesn't sound — in real institutions these are not bugs, they are incidents. Multilarm is engineered to be the dependable, set-and-forget audio brain those places need.
What makes it different
⛶
Truly offline
No internet required for any core function. The schedule, the audio, the neural voice — all live on the device. The cloud is an option, never a dependency.
⚡
Tiny footprint
Runs headless on a Raspberry Pi. Minimal power, minimal hardware, minimal noise — install it once and forget the box exists.
⧉
Multi-zone native
Mirror one schedule to many speakers, or run independent zones side-by-side, each with its own calendar, volume and audio chain.
⟳
Year-aware scheduling
Daily, weekly, monthly or full-year calendars, automatic UK daylight-savings correction, and per-day rule overrides for Fridays, Ramadan, holidays and seasons.
☷
Priority audio engine
Alarms always win. Background radio and ambience duck out of the way instantly and resume seamlessly — no manual timing, ever.
⌖
Control from anywhere
Phone on the same Wi-Fi, browser across the internet, MQTT from a building management system, or the keyboard in front of it — every path reaches the same engine.
Who runs Multilarm
🕌 Mosques & prayer rooms
The original use case. Load a full-year prayer-time calendar, drop in your favourite Adhan recordings, and let Multilarm call each prayer at the exact local time — with automatic DST, Friday Jumu'ah overrides, Ramadan Sehri/Iftar messages, gentle fade-in for Fajr, and a live mic so the imam can speak through the same speakers. Sample Bangladesh and UK (Sheffield) calendars are included.
🏫 Schools & colleges
Period bells, assembly calls and break tones on a weekly timetable, with different schedules for exam weeks or term breaks via date-scoped config switching. A bundled weekly school PA sample shows the pattern.
🏭 Factories & warehouses
Shift-start, break and shift-end sirens around the clock, mirrored to amplifiers across the floor, with quieter night-shift volume profiles. Trigger ad-hoc evacuation or all-clear tones from a wall panel over the LAN trigger API.
🏥 Care homes & hospitals
Timed medication reminders, mealtime and activity announcements spoken in a natural neural voice, ambient music between events to keep speakers warm, and a remote dashboard so staff manage everything without touching the device. A bundled daily care-home PA sample is included.
🏨 Hotels, gyms & retail
A fully automated virtual radio station that shuffles your music library during opening hours, with timed spoken messages, closing-time announcements and per-zone volume. A bundled monthly gymnasium PA sample demonstrates monthly recurrence.
Ready-made starting pointsMultilarm ships with three worked sample deployments — daily (care home), weekly (school) and monthly (gym) — plus default UK and Bangladesh prayer calendars. See
Sample Deployments.
Feature Overview
Everything Multilarm can do, at a glance.
A complete inventory of the engine. Each item links to its detailed documentation.
Scheduling & playback
Scheduled playback
Play any audio at precise times, year-round, with daily/weekly/monthly/yearly recurrence. Details →
Alarm folders
Each time slot picks a random file from a chosen folder, so the same call never feels repetitive. Details →
Per-day rules
Friday, Ramadan, weekend and seasonal overrides without duplicating the whole year. Details →
Fade-in & repeat
Gentle fade-in for wake-up alarms; repeat a call N times with a configurable gap. Details →
Virtual radio
Automated shuffle station with no-repeat cycles, crossfade and time-range scheduling. Details →
Quote & ambient
Random spoken segments and ambient fill between events, on their own schedules. Details →
Physical bell (Multilarm Box)
Ring a real school/mosque bell on schedule through a USB relay board — pulses, not audio. Details →
Voice & speech
Neural TTS (Kokoro)
High-quality offline neural text-to-speech — type a sentence, hear it spoken. Details →
Word-library TTS
Stitch announcements from your own recorded words — perfect for a specific voice. Details →
Voice Broadcast
Record a clip, preview it, and broadcast it through the PA after confirmation. Details →
Push-to-Talk
Live microphone straight through the speakers — speak over the PA in real time. Details →
Talkback
The reverse of PTT — somebody at the device rings the office and the operator can answer and listen. Details →
Audio & output
Priority engine
Three priority tiers so alarms always interrupt and background audio resumes cleanly. Details →
Multi-speaker mirroring
Fan one stream to many output devices, frame-aligned. Details →
Volume profiles
Time-of-day volume overrides — quiet nights, louder days. Details →
Wide format support
MP3, WAV, OGG, FLAC, AIFF, M4A and online streams. Details →
Crossfade
Smooth, configurable transitions between radio tracks and after interruptions. Details →
Audio health check
Startup validation of every path with per-folder file counts. Details →
Connecting speakers (wiring)
Multilarm plays through whatever audio output the device already has — you don't need special hardware. Pick whichever of these fits what you've got:
① Powered speaker or existing amp
Run a 3.5 mm or USB audio lead from the device into any powered speaker, or into the AUX/line-in of an amplifier or 100 V line system you already have in the building. This is the most common setup — Multilarm just becomes another source on your existing PA.
② Bluetooth speaker
Pair a Bluetooth speaker (the Raspberry Pi first-boot wizard can pair one for you). Good for a single room or a temporary setup; keep it powered and in range.
③ DAC HAT → amplifier
For the cleanest sound on a Pi, add an IQaudIO or HiFiBerry DAC HAT and feed its line-out into your amplifier. The Pi image's wizard sets the DAC overlay for you.
Multiple zones? Send the same audio to several outputs at once with multi-speaker mirroring. To pick a specific output, press Ctrl+O at the console to list device indices, then set PlaybackDevice. If you can hear a scheduled alarm or the setup wizard's test sound, your wiring is good.
Remote control & integration
Web Remote
Built-in dashboard + REST API on your LAN, with optional auth. Details →
Cloud Relay
Control from anywhere over the internet — no port forwarding. Details →
Multilarm Hub
One dashboard fans out to every instance on a host. Details →
Trigger API
Inbound HTTP / MQTT triggers from BMS, nurse-call, timetables. Details →
Webhooks
Push events (alarm fired, TTS spoken…) to external receivers. Details →
File Manager
Upload, browse and delete audio remotely — no SSH/RDP. Details →
System tray app
Windows tray companion auto-discovers running instances. Details →
Config switching
Load a different config on Ramadan, weekends, winter — automatically at midnight. Details →
Hot-reload
Edit the config; changes apply in ~1.5s with no restart. Details →
New console
A task-based control panel at /app — Now, Scenes, Automations, Schedule and Settings. The classic dashboard at / is unchanged and still fully supported.
Command line
Drive the running device from a script, cron or SSH: --say, --scene, --list-scenes, --apply-pack, --normalise, --analyse.
Home Assistant
A custom component exposing one button per Scene, status sensors and an announce service.
Config templates (multi-site)
In the cloud dashboard, an account owner can save a partial set of settings as a template — a fade time, a volume, a bell schedule — and push it to up to 64 sites in one action. Only the settings in the template change; every site keeps its own schedule, audio folders and hardware settings, because the device merges the named fields and leaves the rest alone. Device names, room names, cast names and the cloud token can never go in a template, so a push cannot make a whole fleet answer to the same name. Passwords are stored encrypted and never shown back to the browser. Settings that usually differ per site (audio device, ports, GPIO pin, multi-room role) are allowed but flagged before you push. Each device reports how many settings it recognised, so a mistyped name shows up as “applied 5 of 6” rather than as silence.
What keeps working if we disappear
Worth knowing before you build a building around this. If the project stopped existing tomorrow, this installation would carry on doing exactly what it does today — schedules, announcements, on-device speech, music, zones, relays, the emergency button and the dashboard on your own network. The only thing that would stop is the optional Cloud Relay, because that is the only part hosted by us. That is a consequence of the design rather than a promise: the licence is perpetual with no expiry and no phone-home check, your configuration is plain XML you can read in Notepad, and your audio is ordinary files in ordinary folders.
The things we do not offer are stated in the same place: there is no public status page for the cloud relay, and no contractual numeric response-time SLA. Security problems go to hello@multilarm.com with “security” in the subject. The full statement, including a dated release history, is at https://multilarm.com/longevity.html.
Installing on a Raspberry Pi in one command
On Raspberry Pi OS or Debian, curl -fsSL https://multilarm.com/install.sh | sh installs the signing key, adds the Multilarm apt repository and installs the package. After that Multilarm updates with the same sudo apt update && sudo apt upgrade as everything else on the machine, and your settings in ~/.local/share/multilarm are never touched by an upgrade or a removal.
The script is deliberately unexciting. It prints every command before running it, it never executes anything it downloads (the signing key is fetched to a file and dearmoured, not piped into a shell), and on a machine we do not build for — anything other than arm64 or armhf — it stops and says so rather than adding a repository that will never contain a package. If you would rather not pipe a script into a shell, read it at https://multilarm.com/install.sh or run the three apt commands yourself; they are printed in the online documentation and the script does nothing else.
On a headless Pi that nobody logs in to, follow the install with systemctl --user enable --now multilarm and sudo loginctl enable-linger $USER. Without the second one the per-user service does not come back after a reboot — it is a per-user service specifically so it can reach your sound device, and that is the cost of it.
Endpoint supervision, the nightly self-test and the PA Health Report
Every failure this covers has the same shape: the system carries on behaving normally while a part of it has quietly stopped working, and nobody finds out until the day it is needed. A relay board whose USB adapter has been unplugged still “rings” the bell — the schedule fires, the audit log records it, and no metal moves. A room that dropped off the sync group is a silent corridor nobody walks down until the fire drill. An amplifier switched off at the rack looks, from software, exactly like an amplifier playing perfectly.
Once a night (03:00 by default) Multilarm goes and looks. It checks the output device, free space, that every configured audio file and folder is still there, that the speech engine is loaded, that the relay board answers, and that every room following this device has been heard from recently. Anything that fails is raised as a fault, and faults already travel out over e-mail, Telegram and webhooks — there is no separate alert to set up.
The self-test is silent, which is what lets it run at three in the morning. The relay check re-asserts every channel to the state it is already in: a real end-to-end serial write that fails if the board is gone and moves no contacts. The bell is never pulsed. Everything else is pure observation.
The one exception is the optional amplifier loopback. It plays a short test tone and listens for it coming back on a capture input wired from the speaker line, passing only when that exact frequency returns well above the room’s own level — a ratio, so it does not depend on input gain, and measured at one frequency, so a lorry going past does not pass. Because it makes a sound it is off by default, and when it cannot run it records “not run”, never “passed”.
Once a week the recorded runs are rendered into a PA Health Report — an ordinary printable page written next to the config, and served live at /api/supervision/report. That artifact is the point: “the system says it is fine” is worth very little, while a dated record of the system proving itself every night for three months is something a fire officer, an insurer or a facilities manager can actually use.
Settings and history are at GET / POST /api/supervision; POST /api/supervision/run runs one on demand and records it as manual, which is what a commissioning engineer wants. Supervision can only ever observe — there is no stop, no volume, no schedule change and no relay actuation anywhere in it. A supervisor that can act would be a second, unaudited control path into a life-safety system.
Timers, break reminders and daily routines
All of this was already possible with the scheduler — in the vocabulary of prayer times, alarm folders and time-range rule strings. That is exactly why nobody found it. A teacher who wants a twenty-minute exam timer, or a household that wants a wind-down announcement at nine, should not have to learn a rule grammar first. This adds the plain words on top of the machinery that is already there; it adds no new capability to the engine.
Timers are one-shot countdowns somebody starts by hand: POST /api/timers/start with {"label":"Exam","minutes":20}. With nothing else supplied it announces “Exam finished.” when it runs out; give it say or play to change that. Cancel one with POST /api/timers/cancel. The longest timer is twelve hours — anything longer belongs in the schedule, where it survives a reboot properly. A running timer is written to disk with an absolute due time, so a fifty-minute exam timer survives a power blip; but a timer that elapsed while the machine was off is discarded rather than fired, because a burst of yesterday’s timers going off during Monday assembly is worse than losing a countdown nobody is waiting on.
Routines recur. Either at a time of day ("kind":"at","at":"07:00") or every so many minutes inside a window ("kind":"every","everyMinutes":90,"from":"09:00","to":"17:00"), with an optional days list like mo,tu,we,th,fr. An interval routine is anchored to the start of its window, not to whenever the service last booted, so “every 90 minutes from nine” lands on the same minutes every day and a restart does not shift the whole day’s reminders. Read and write them at GET / POST /api/routines.
The status also carries ready-made presets — break reminders every 90 minutes, hourly stand-up, a 25-minute focus block, wake, lunch, end of the working day, wind-down, lights out. Pick one and change the time; that is the whole feature.
Two things are deliberately not possible. A routine or a timer can only ever start audio — there is no stop, no volume and no schedule-disable action — and everything here plays at priority 3 (queued) and nothing else. A reminder is by definition not urgent, so it waits for whatever is already playing to finish. A break reminder that cut into an evacuation announcement would be a serious fault, and the way to guarantee it cannot happen is to give routines no way to express urgency at all.
Phrase packs — announcements in any language
The built-in neural voice speaks English (en-US and en-GB) and we will not pretend otherwise. A phrase pack is the honest answer for everything else: a file of pre-recorded audio, spoken by somebody who actually speaks the language, with a manifest saying what each recording means. Nothing about it touches speech synthesis.
A pack is a single .mlpack file (a zip). Hand it over on a USB stick, e-mail it, or pass it to another Multilarm site — it needs no account, no download service and no internet. Install it from the Web Remote (POST /api/phrasepacks/install), or simply copy the folder into <config folder>\PhrasePacks\<pack id>\; what is installed is discovered by looking, so a pack copied on by hand is found on the next start.
Build one with ops\make-phrase-pack.ps1 -Source .\evac-urdu. The folder holds manifest.json and one audio file per phrase, flat:
{
"id": "evac-urdu",
"name": "Evacuation announcements (Urdu)",
"language": "ur",
"languageName": "Urdu",
"speaker": "A parent volunteer",
"licence": "Given to the school",
"phrases": [
{ "id": "evacuate", "text": "…",
"english": "Leave the building now",
"file": "evacuate.wav" }
]
}The english gloss is not decoration — an operator who does not read the language still has to pick the right recording under pressure, and it is what the dashboard lists.
Play one or several at POST /api/phrasepacks/play with {"pack":"evac-urdu","phrases":["evacuate"],"priority":1}. A list is allowed because a real evacuation announcement is the same instruction in two or three languages, one after another; they play as one announcement rather than each re-interrupting the last. Priority is 1 (immediately) or 3 (queued) and nothing else, exactly as for speech — a phrase must never sit at priority 2 waiting for a gap an emergency has already taken. A phrase can only ever start audio: there is no stop, no volume and no silence action, because the person who recorded a pack is not the person who commissioned the building.
Installed recordings are ordinary audio files at a known path, so everything that already takes a file path — a scene’s play step, an alarm folder, the External Trigger API’s play-file — works with them unchanged.
Packs are checked whole on install: every entry name is validated (no folders, no .., audio extensions only), the pack is staged and only made visible once it is complete, and a pack missing even one of its recordings is refused outright rather than installed in part. A pack that looks installed and fails on the day it is used is the outcome the whole feature exists to avoid.
Sound masking / focus audio
Generated masking noise on a schedule, for an open-plan office, a clinic waiting room or a study space. Four profiles — pink (the usual choice), brown (deeper, covers traffic), white and hush (pink with the hiss taken off, for a waiting room). The noise is computed on the device, not sampled from a library, so there is nothing to license and nothing extra to install.
The level is capped at 0.6 on purpose. Masking sits under the conversation it is covering; a level of 1.0 is not masking, it is a fault.
The radio always wins. Masking only starts when the background channel is genuinely idle, and it never stops anything: if the Virtual Radio is playing, or another source holds the background channel, masking simply stays quiet and the status shows radioHasChannel so an operator can tell “not scheduled” from “the radio has it”. Alarms, announcements and emergencies interrupt it exactly as they interrupt music, through the ordinary priority rules.
Settings live in <config name>.masking.json next to the configuration and are read and written at GET/POST /api/masking. The two-minute loop is rendered once into the config folder and reused after that.
Chime and tone library
Twelve chimes and signal tones are generated into a Chimes folder beside your configuration the first time Multilarm runs: two- and three-note chimes, soft and bright bells, marimba and glass, a low gong, pips, a double alert, a school bell and an end-of-break tone. Point TTSChimePath (or an alarm folder) at any of them.
They are synthesised, not sampled. A bundled “royalty-free” sample pack carries redistribution terms a customer installing an MSI never sees and cannot re-audit; sound computed from sine waves has no third party in it at all, and costs a few megabytes rather than tens.
An existing file is never overwritten, so replacing one with your own recording is permanent — and deleting one brings the generated version back on the next start. GET /api/chimes lists the set.
Live calendar sync
The Config Generator has always imported an .ics file, turning a term calendar into schedule rules at design time, and that is unchanged. Live calendar sync is the other half: the device subscribes to one or more calendar addresses, re-reads them every fifteen minutes, and announces events as they come round — including the meeting somebody added this morning.
Any private .ics subscription address works: Google Calendar’s secret address in iCal format, Outlook’s publish a calendar link, or a CalDAV collection (Nextcloud, Radicale, Zimbra) with a username and password. There is no sign-in to Google or Microsoft and no mailbox access — a subscription link carries only the calendar, and the customer can revoke it in one click.
Each feed can announce a fixed number of minutes before the start, filter to events whose title contains certain words, skip all-day entries, and stay silent during quiet hours. The announcement text is a template: {summary}, {time}, {location} and {lead} are filled in. A feed may instead run a scene.
A calendar can only start things — say something, or run a scene. There is deliberately no stop, no volume and no schedule-disable action, because a calendar is edited by people who have never seen this device; an event titled “quiet please” must not be able to silence a building. Announcements are queued behind whatever is playing, never on top of it, so the diary can never interrupt an emergency broadcast. A device that was switched off does not come back and read out this morning’s events.
Feeds live in <config name>.calendars.json next to the configuration, and are read and written over the LAN API at GET/POST /api/calendars. POST /api/calendars/refresh fetches every feed immediately and reports what came back per feed — use it after adding an address, because a wrong one otherwise just looks like a quiet diary.
Node-RED
Three nodes — speak, scene and status — that drive the device from a flow over the Web Remote API on your own LAN. The scene dropdown reads the live scene list off the device. Copy Integrations\node-red onto the Node-RED machine and npm install it from disk. There is deliberately no stop/volume node: a flow runs with nobody watching, and no automation should be able to silence a live building.
Operations & reliability
Run as a service
Auto-start at boot on Windows (Service) and Linux (systemd). Details →
Logging
Error, playlist and audit logs, plus a 500-entry rolling console buffer. Details →
Email alerts
Get notified when a device goes offline or misses an alarm. Details →
Audit trails
Two-ledger auditing of every remote action — intent and execution. Details →
Emergency button & zones
A wired GPIO panic button that overrides everything, plus relay-muted zone targeting (Multilarm Box). Details →
Multilarm Network
A companion that watches the network the box sits on, installed and running as part of every Multilarm installation — lists every device with its manufacturer (named offline), runs speed tests, and warns you in plain English if the internet drops or a watched device goes offline. Read-only, zero setup, idle until the device is paired; alerts arrive on the same email/Telegram you already use.
Scenes
One button that does several things in order — play a sound, speak a line, wait, change the volume, ring a bell. Stored separately from your schedule, so adding scenes changes nothing you already have.
Automations
When something happens, do something: a smoke alarm is heard, the room gets loud, the clock reaches a time. Rules can only start things — nothing can silence the building.
Listening (optional)
The device can measure how loud the room is and recognise a smoke alarm. Off by default; clips are measured and deleted immediately and no audio is ever stored or sent anywhere.
Scenes, automations and listening
Scenes — one button, several actions
A scene is a named list of steps that run in order: play a sound,
speak a line, wait, set the volume, pulse a relay, stop. It is what sits behind a single
button in the console, and it can be run from the command line or by an automation.
Scenes live in their own file next to your config —
<config name>.scenes.json — for one simple reason: nothing in the
config file can express “play this, then say that, then ring the bell”.
Your existing schedule, alarms, speech and background audio are untouched. A site with
no scenes file behaves exactly as it always did.
Create and edit scenes on the Scenes tab of the console at
/app. Renaming a scene deliberately does not change its id, so anything
already referring to it keeps working.
Ready-made sets
If you are starting from nothing, pick a use-case pack at the top of
the Scenes tab: Home, Shop or cafe, Gym or studio, Office or workshop, School, or Place
of worship. Its scenes are added to your device.
A pack never replaces anything. If a scene with the same name already exists it is
skipped and named in the confirmation, and applying the same pack twice adds nothing the
second time. The Place of worship pack adds hall announcements only — it leaves
your prayer-time schedule exactly as it is.
Automations — when this, do that
On the Automations tab you build rules in plain English:
- When — a smoke alarm is heard, the room is louder or quieter
than a level you choose, the clock reaches a time, or Multilarm starts.
- If — optionally not between two times (quiet hours), and a
minimum wait between one firing and the next.
- Then — run a scene, or announce some text.
A rule can only start things. There is deliberately no rule that can
stop audio, mute the device or disable your schedule, so no automation — however it
was written or edited — can be the reason your building goes quiet.
Listening to the room
Turn on Listen to the room in Settings and the device measures the room every
twenty seconds. That gives you two things: a live measure of how loud the room is, and
recognition of the standard smoke-alarm sound — the three-beep pattern every smoke
alarm sounds. You can then use either as the When of an automation.
What happens to the audio. This matters, so it is worth being exact:
- It is off until you turn it on.
- A short clip is recorded to one temporary file, measured, and deleted
immediately — before it is even analysed. There is no rolling buffer and
no archive.
- Only a number (how loud) and a label (alarm or no
alarm) ever leave the device. No audio is stored, transmitted, or sent to the
cloud — not to us, not to anyone.
- Every detection is written to the audit log.
- Listening always gives way to you. If someone is using push-to-talk, recording a
voice broadcast or recording speech, listening simply skips that turn.
Detection requires both the right pitch and the right rhythm, so a kettle, a buzzer or
a reversing vehicle will not set it off. It reports what it heard — what your
building does about it is up to the automation you write.
Matching announcements to the room
With Match announcements to the room switched on, a scene raises its volume in
a noisy room and lowers it in a quiet one, then puts your volume back afterwards. It
applies to scenes only, and it is firmly limited — it can never make a scheduled
alarm or an emergency announcement quieter, and it never drops to silence.
Working from the command line
Every verb below drives the running device, so Multilarm must already be
going. They are useful in a script, in cron or Task Scheduler, or over SSH.
multilarm --say "The hall closes in ten minutes"
multilarm --scene closing
multilarm --stop-scene
multilarm --list-scenes
multilarm --list-packs
multilarm --apply-pack retail
Two more read a file instead and need no running device:
multilarm --analyse recording.wav measure loudness
multilarm --detect-alarm clip.wav check for a smoke alarm
multilarm --normalise ./sounds even out a folder
--normalise writes <name>.normalised.wav beside each
file and never modifies your originals. It refuses to push a file into
distortion, and skips anything that is silent.
For scripting, the exit code tells you what happened: 0 success,
1 you got the arguments wrong, 2 the device could not be
reached, 3 the device refused.
Get Going
5-Minute Quick Start
From download to scheduled sound in five steps. This walks the simplest path — a single Windows machine playing the default prayer calendar.
Step 1 — Install
Run the Windows MSI installer (it bundles .NET, the audio engine and the default Adhan/ambience/quote audio). On Linux/Pi, use the one-line installer. Full detail per platform on the Installation page.
# Linux / Raspberry Pi — one line:
sudo curl -s -L https://bit.ly/multilarm-linux | bash
Step 2 — Launch it
Double-click Multilarm.exe (Windows) or run $HOME/Multilarm (Linux). On first run it writes a default Multilarm.config.xml next to itself, pre-loaded with the UK (Sheffield) prayer calendar and sensible ambience settings. You'll see a colour-coded console and the next-alarm countdown in the title bar.
Nothing happens yet?That's normal — Multilarm stays silent until a scheduled time arrives. Press Ctrl+T to test-play a random alarm right now and confirm your speakers work.
Step 3 — Point it at your audio
Drop your Adhan/bell/announcement files into the alarm folders, or change AlarmPath to your own folders. You can edit the config three ways:
- Open
Multilarm.config.xml in any text editor (it's plain XML).
- Use the visual Configuration Generator — load, edit with per-field help, save.
- Enable the Web Remote and edit from your browser.
Step 4 — Set your times
The DateAndTimeData field holds your calendar. For a mosque, paste your annual prayer timetable; for a school, list your bell times. See Scheduling & Recurrence for the exact format, or just start from one of the bundled samples.
Step 5 — Control it remotely (optional)
Set WebRemoteEnabled=True, then open http://<device-ip>:6580/ from your phone on the same Wi-Fi. For control over the internet, connect a free Cloud account instead — no router setup needed.
That's itSet it to
start at boot and the device will run your schedule unattended, year after year. Everything beyond this is refinement — explore the rest of this guide at your own pace.
Where things live
| Item | Location |
| Program | Multilarm.exe / $HOME/Multilarm |
| Configuration | Multilarm.config.xml (next to the program) |
| Audio libraries | Adhan/, TTS/, Radio/ folders (configurable) |
| Error log | Multilarm.error.log |
| Playlist log | Multilarm.playlist.log |
| Audit log | Multilarm.audit.log |
Installation
Installing Multilarm
Multilarm is self-contained — the required .NET runtime, the native audio engine and the bundled audio all ship inside the package. Choose your platform below.
Windows
Use the MSI installer (64-bit). It includes the .NET runtime and all dependencies, plus the Adhan, bleep, ambient and text-to-speech audio — which is why the package is larger than the program itself. Administrator privileges are requested so the installer can place files correctly.
Alternatively, download the standalone executable and the resource ZIP from the Binaries folder. Extract the resources into the program's root directory. The native audio library multilarm_audio.dll already ships alongside Multilarm.exe, so no system-folder copy is needed.
Run as a Windows Service
Multilarm can run as a Windows Service (LocalSystem, auto-start at boot — ideal for a headless install). Run the bundled batch files as administrator:
install-service.bat :: install & start the service
uninstall-service.bat :: stop & remove the service
Linux / Raspberry Pi
Option A — Flash the ready-made SD-card image (easiest)
A complete Raspberry Pi OS Lite (64-bit) image with Multilarm pre-installed is available from the download centre (multilarm-pi.img.xz). No Linux commands are needed:
- Download the
.img.xz file and flash it onto an 8 GB (or larger) microSD card with Raspberry Pi Imager (choose “Use custom”) or balenaEtcher — do not unzip it first; both tools flash .img.xz directly.
- Insert the card into a Raspberry Pi 3 / 4 / 400 / 5 or Zero 2 W, connect a screen and keyboard (needed once, for the wizard), and power on.
- The first-boot wizard appears on the screen and walks you through: Wi-Fi (skip if using Ethernet), linking to the cloud (choose Get a setup code — it shows a short code you enter at multilarm.com/setup; pasting a token is offered as an alternative), and audio output — a DAC HAT (IQaudIO / HiFiBerry) or a Bluetooth speaker.
- Multilarm then starts as a systemd service and auto-starts on every boot. The screen and keyboard can be removed.
SecurityThe image ships with SSH enabled and a default login of multilarm / multilarm. Change the password the first time you log in: passwd. Verify your download against the published SHA-256 checksum (sha256sum -c multilarm-pi.img.xz.sha256).
Option B — APT package (recommended on Raspberry Pi OS / Debian)
Add the Multilarm repository once, then install and update with the same apt commands you use for everything else. Packages are signed; both ARM 64-bit (arm64) and ARM 32-bit (armhf) are provided, and one repository serves Debian 12 (bookworm) and 13 (trixie).
# 1. Add the signing key:
curl -fsSL https://multilarm.com/apt/multilarm.gpg | sudo gpg --dearmor -o /usr/share/keyrings/multilarm.gpg
# 2. Add the repository:
echo "deb [signed-by=/usr/share/keyrings/multilarm.gpg] https://multilarm.com/apt stable main" | sudo tee /etc/apt/sources.list.d/multilarm.list
# 3. Install:
sudo apt update && sudo apt install multilarm
# 4. Start it for your user (per-user service, so it can reach your sound device):
systemctl --user enable --now multilarm
Later updates are just sudo apt update && sudo apt upgrade. Config and logs live in ~/.local/share/multilarm, so removing the package never deletes your settings.
Not a Debian packageMultilarm is not part of Debian or Raspberry Pi OS and is not distributed by them. The repository above is operated by us.
Option C — Install script onto an existing Linux system
Tested on Raspberry Pi (Debian-based). The installer auto-detects architecture — installing 64-bit audio drivers on ARM64 systems and 32-bit drivers otherwise.
# Automatic install:
sudo curl -s -L https://bit.ly/multilarm-linux | bash
# Launch:
$HOME/Multilarm
Boot at startup (systemd user service)
# Create the service:
sudo curl -s -L https://bit.ly/multilarm-service | bash
# Check status:
systemctl --user status multilarm.service --no-pager --full
# Stop:
systemctl --user stop multilarm.service
systemctl --user daemon-reload
Removing the service / uninstalling
# Remove just the service:
systemctl --user disable multilarm.service
rm ~/.config/systemd/user/multilarm.service
systemctl --user daemon-reload
# Remove core files (keeps .NET, Adhan, config, audio lib, etc.):
sudo curl -sL https://bit.ly/multilarm-uninstall | bash
# Remove everything, non-interactive:
sudo curl -sL https://bit.ly/multilarm-uninstall | bash -s -- --yes
Audio hardware on the PiFor best results use a dedicated audio HAT (e.g. IQAudio DAC Pro) or a Bluetooth speaker. Helper scripts:
# Bind a Bluetooth speaker at boot (replace with its MAC):
sudo curl -sL https://bit.ly/RPiConfig-BTAudio | bash -s AA:BB:CC:DD:EE:FF
# Install IQAudio DAC Pro HAT drivers:
sudo curl -sL https://bit.ly/RPiConfig-DACProHAT | bash
System requirements
| Platform | Notes |
| Windows | 64-bit (win-x64). .NET runtime bundled. ~200 MB executable (includes the neural TTS model). |
| Linux | ARM 32-bit (armhf) and ARM 64-bit (arm64) builds. Tested on Raspberry Pi OS / Debian 12 (bookworm) and 13 (trixie), 64-bit. |
| Write access | The program needs write access to its own folder for the config file and logs. |
Core Concepts
How Multilarm thinks
A few ideas underpin everything. Understand these and the rest of the configuration falls into place.
The priority audio engine
All audio flows through one engine that arbitrates between competing sounds using three priority tiers plus a background layer. You never have to time things manually to avoid overlap — the engine does it.
| Tier | What plays here | Behaviour |
| P1 — Immediate | Adhan alarms, Voice Broadcast, Push-to-Talk | Interrupts everything. Background gracefully ducks/fades, then the alarm plays (with fade-in if set). |
| P2 — On Quiet | Quote segments, ambient audio | Waits for a natural quiet moment in the background before playing. |
| P3 — Queue | TTS announcement words, queued tracks | Plays after the current P1 interrupt (or background track) finishes. |
| Background | Virtual radio shuffle | The base layer; crossfades between tracks and resumes after any interruption. |
When an Adhan fires, the radio fades down, the alarm plays at full priority, and the radio fades back up — seamlessly. After an alarm, any queued TTS announcement speaks, then normal life resumes.
The scheduling model
Multilarm separates when from what:
DateAndTimeData — the calendar: which times fire on which dates.
DateAndTimeDataFormatInEffect — reshapes those raw times per day (add/subtract minutes, reorder, duplicate slots).
AlarmIndexData — which audio folder each fired slot draws from.
TextData — the on-screen / spoken text for each slot.
On top of all four sits a powerful, optional rules & per-day scope system so Fridays, Ramadan, weekends and seasons behave differently — without rewriting the whole year. See Scheduling & Recurrence for the full mechanics.
Path tags
Any file/folder setting accepts path tags that expand to the right system folder on each platform — so one config works across Windows and Linux.
| Tag | Target | Windows | Linux |
{app} | App folder | install dir | ~/.local/bin |
{temp} | Temp | …\Temp | /tmp |
{home} | User profile | C:\Users\Name | /home/name |
{docs} | Documents | …\Documents | /home/name |
{data} | App config (Roaming) | …\AppData\Roaming | ~/.config |
{local} | Local data | …\AppData\Local | ~/.local/share |
Example: {app}\Adhan|{app}\Bleep resolves to the Adhan and Bleep folders next to the executable, on any OS.
Config hot-reload
Multilarm watches its config file. When you save a change — by hand, via the Config Generator, or through any remote interface — it re-applies most settings within about 1.5 seconds, no restart needed. Four settings are the exception (they bind hardware/ports at startup):
Restart-required fieldsWebRemoteEnabled, WebRemotePort, PlaybackDevice and RecordDevice. Everything else hot-reloads.
The audio engine under the hood
Playback and recording are powered by MultilarmAudio, an in-house native engine built on miniaudio (MIT-0) and libFLAC (BSD-3). It decodes each sound once and can fan it out to many output devices, frame-aligned. There is zero GPL code anywhere in Multilarm — relevant if you need to audit licensing for a commercial deployment.
Configuration Reference
The configuration file
All settings live in Multilarm.config.xml in the program's root directory. This page explains the file; the sub-pages document every field grouped by purpose.
How the file works
- Tags are case-sensitive, in
<Tag>Value</Tag> format.
- Settings load at boot. A missing tag, or an invalid
True/False value, is rewritten with the default — so the program needs write access to its folder.
- If the file is missing entirely, Multilarm creates a fresh one with default settings (UK Sheffield prayer calendar, ambience every 3 minutes). To reset to defaults, just delete the file.
- Avoid using line feeds as delimiters within fields (except
TextData) — they parse differently across OSes.
Back up before editingBecause a missing or corrupt tag is overwritten with the default, keep a backup of any config you've invested time in.
Three ways to edit
✎
Text editor
It's plain XML — edit it in Notepad, nano, VS Code, anything.
▦
Config Generator
A visual editor with per-field help and rule builders. See it →
Field groups
The reference is split for readability — jump to any group:
Calendars, recurrence modes, date formats, the time-shaping engine.
Alarm folders, formats, fade-in, repeat, slot-to-folder mapping.
OffsetRules, TextDataRules, AlarmIndexRules and scope grammar.
Neural & word-library TTS, voices, chime, the text engine.
Playback/record device selection, multi-speaker mirroring.
Source, folder, formats, schedule rules, online streams.
Random segment playback and ambient fill.
Master volume, crossfade, volume profiles.
Web Remote, Cloud, Trigger/MQTT, auto-update, instance & config-switch settings.
Relay board, scheduled bell, emergency button, zones, remote-emergency policy.
Phone-system account, paging extension, allow-list, registration & port.
Measuring the room, matching announcements to it, and recording on a timetable.
Configuration · Scheduling
Scheduling & Recurrence
These fields define your calendar: which times fire on which days, and how the raw times are reshaped per day.
Recurrence mode
Pick one recurrence mode. If more than one is True, the priority is Month → Week → Day. If none are True, DateAndTimeData is treated as a full-year calendar with date+month markers.
RecurEveryDayTrue/Falsedefault False
The whole of DateAndTimeData is one day's worth of times, recycled every day. No date markers needed. Ideal for school bells or factory shifts that are the same every day.
RecurEveryWeekTrue/Falsedefault False
The date field holds a weekday (mon, tue… case-insensitive, short or full). The week's data recycles every week.
RecurEveryMonthTrue/Falsedefault False
The date field is a day-of-month number (ideally 31 entries to cover the longest month). The month's data recycles every month.
Date & time format markers
MonthFirstTrue/Falsedefault False
Treat the first value in the date field as month rather than day (American MM-DD instead of DD-MM).
DateIdentifierCharacter(s)default *
Marks the start of each date entry in DateAndTimeData. Not needed when RecurEveryDay is True. Example: *1-1|6:26 starts the entry for 1 January.
DateDelimiterCharacter(s)default -
Splits day from month in the date field (only for full-year data). In *1-1|… the - splits day 1 from month 1.
TimeDelimiterCharacter(s)default |
Separates the date field from the times, and the times from each other.
UKDaylightSavingsTrue/Falsedefault True
Applies UK DST correction in the last weeks of March and October. Only used for full-year data.
The calendar itself
DateAndTimeDataDelimited string
The master calendar. Each entry: DateIdentifier + date + TimeDelimiter-separated times. Times must be in ascending order; 12- or 24-hour both work — an hour lower than the previous one is read as PM (so 1:44 after 12:11 means 13:44). Use 00:<min> for after-midnight, not 12:<min>.
*1-1|6:26|8:20|12:11|1:44|4:00|5:48*2-1|6:26|8:20|12:11|1:45|4:01|5:49*…
The default is a full year of UK (Sheffield) prayer times.
Pasting a timetableFormat your calendar in a word processor first — strip stray spaces and line feeds (use Find/Replace with ^p for line breaks). Use a 29-day February for whole-year data and apply DST adjustments after March/October.
DateAndTimeDataFormatInEffectDelimited stringdefault 1-5|1|2|2+10|3-10|3|4|5-7|5|6
Dynamically reshapes each day's times. Each token is a 1-based source slot index, optionally with +N/-N minutes. This lets you derive many output slots from a few source times.
With times 6:26|8:20|12:11|1:44|4:00|5:48 and the default format, 2+10 means "slot 2 (8:20) plus 10 min = 8:30". The resulting list is sorted ascending before scheduling.
Supports per-day scope lines (e.g. a Tuesday-only variant with an extra slot).
Worked example
Day times 6:26|8:20|12:11|1:44|4:00|5:48 + default FormatInEffect produce the effective day:
6:21 | 6:26 | 8:20 | 8:30 | 12:01 | 12:11 | 1:44 | 3:53 | 4:00 | 5:48
— ten alarm slots derived from six prayer times, each mappable to its own audio folder and announcement text.
Configuration · Alarms
Alarms & Playback
What plays when a scheduled time fires, from which folder, and how.
AlarmPathPipe-delimiteddefault {app}\Adhan|{app}\Bleep
Folders containing alarm audio. List any number, separated by |. A random file is chosen from the relevant folder each time (so an Adhan never feels repetitive). Zero-length files and the ambience file are excluded.
Top level onlyOnly the listed directories are searched — subfolders are not included automatically. Add subfolders as separate entries.
AlarmFileFormatStringdefault mp3
Which formats count as alarm audio. Supported: MP3/MP1/MP2, OGG, WAV, AIFF, FLAC. Join several with ; (e.g. wav;mp3). Wildcards allowed: mp* matches mp2/mp3; *abc.mp* matches filenames ending "abc".
AlarmIndexDataPipe-delimiteddefault 2|1|2|2|2|1|1|2|1|1
Maps each fired slot to a 1-based folder index in AlarmPath. With AlarmPath = Adhan|Bleep and this default, slot 1 → Bleep (2), slot 2 → Adhan (1), and so on.
Supports per-day scope lines. Example — Fridays pull the first slot from folder 3:
2|1|2|2|2|1|1|2|1|1
*fr|3|1|2|2|2|1|1|2|1|1
AlarmIndexRulesRules (optional)default (empty)
A condition-based overlay on AlarmIndexData. Each rule is <folder-index>;<filter>,<filter>; first match wins. Filters: slot=, t=HH:MM[-HH:MM] (fire time), w=, nw=, m=, d=, ld, ! to negate. See Rules & Scopes.
AlarmFadeInMsInteger (ms)default 0
Fade the alarm up from silence over this many milliseconds. 0 = instant. 1000–3000 ms gives a gentle wake-up. Applies to P1 alarms only, not radio transitions.
AlarmRepeatCountInteger 1–100default 1
Play the same randomly-chosen file N times per trigger. The whole sequence is one alarm event — radio/ambience stay suppressed throughout. Useful for insistent wake-ups.
AlarmRepeatGapSeconds 0–600default 0
Silence between repetitions when AlarmRepeatCount > 1. 0 = back-to-back.
SequenceGapMsInteger (ms) 0–60000default 0
Silence between different files inside one announcement — between a bleep and the adhan, say. Different from AlarmRepeatGap, which is the gap between repeats of the whole announcement; both apply on the same fire.
SequenceMaxSecondsSeconds 0–3600default 600
Safety cap on one announcement (all files, all repeats). When reached the rest is skipped and the reason is logged. Stops a mistyped >all on a huge folder from holding the speakers for hours. 0 = no limit, for when a long announcement is deliberate.
Playing several different files per alarm
You do not add a list of files anywhere. You write it into the path you already have. Every AlarmPath entry — and TTSChimePath too — accepts:
<path>[>mode][*count]
<path> can be a folder (as always), a single file, or a folder with a filename pattern such as bell*.mp3. >mode decides how many files that path contributes:
>one — one file at random. This is the default, so a plain folder path behaves exactly as it always has and existing configs need no changes.
>all — every file in the folder, in filename order. Name them 1-intro.mp3, 2-adhan.mp3, 3-outro.mp3 to fix the order.
>shuffle — every file, in a fresh random order each time.
>seq — one file per alarm, stepping through the folder and wrapping at the end. This is how you play a different recitation each day. Multilarm remembers where it got to across restarts, and starts cleanly from the top if you add or remove files.
*count caps how many files come from that path: >shuffle*2 is two different random files, >seq*3 is the next three in the rotation.
Chain several sources with a plus sign surrounded by spaces:
{app}\Adhan\Bleep\intro.mp3 + {app}\Adhan + {app}\Adhan\Bleep\outro.mp3
That plays the intro bleep, then a random adhan, then the outro bleep — one uninterrupted announcement, with the radio suppressed throughout and the spoken text (if any) following at the end. The spaces matter, because + is a legal character in filenames: Rock+Roll is a folder name, a + b is a chain. Write \+ if a path genuinely contains a spaced plus.
Recipes
Bleep then adhan: {app}\Adhan\Bleep\ding.mp3 + {app}\Adhan ·
three-tone chime: set TTSChimePath to {app}\Adhan\Bleep>all ·
a different adhan daily: {app}\Adhan>seq ·
two random bleeps then an adhan: {app}\Adhan\Bleep>shuffle*2 + {app}\Adhan
A typo in a mode (>shufle) is reported as an error at startup and in the audio health check — it never quietly falls back to a random pick. The ambience file and zero-length files are excluded from a folder's contents, as they always were.
Test itPress Ctrl+T in the console (or hit Test Alarm on any dashboard) to play a random alarm immediately, optionally from a specific folder.
Watch itThere is a three-minute video walkthrough of everything on this page — folder modes, chaining with
+, a chime before speech, and the two timing fields — at
multilarm.com/help-video.html. The
HomeTwoPC-Daily sample pack on the downloads page is a working config that uses all of it.
Configuration · Advanced
Rules & Per-Day Scopes
The system that makes Fridays, Ramadan, weekends and seasons behave differently — without duplicating your whole calendar. This applies to DateAndTimeDataFormatInEffect, AlarmIndexData, TextData and their rule overlays.
Two mechanisms, one resolution order
Three positional fields (DateAndTimeDataFormatInEffect, AlarmIndexData, TextData) each support per-day scope lines. Three of them also have a matching rules overlay (OffsetRules, AlarmIndexRules, TextDataRules). They resolve in this order:
rules overlay → per-day scope line → unscoped default line
Per-day scope lines
Any of the positional fields may hold several newline-separated lines. A line with no prefix is the default. A line starting with *<scope>| overrides the default when today matches that scope. Most-specific match wins.
| Scope | Matches |
*tu| / *tuesday| | Every Tuesday (weekday) |
*15| | 15th of every month (day-of-month) |
*15-10| | 15 October (specific date; honours MonthFirst) |
*mo,we,fr| | Comma list of weekdays |
Example — Tuesdays get an extra output slot:
1-5|1|2|2+10|3-10|3|4|5-7|5|6
*tu|1-5|1|2|2+10|3-10|3|4|5-7|5|6|7
Editor noteThe visual Config Generator edits the default line and preserves your per-day lines in the raw value.
Rules overlays
For cross-cutting, condition-based overrides, use a rules overlay. Each rule is <payload>;<filter>,<filter>…. Rules are tried top-to-bottom; first match wins; if none match, the per-day/default value is used.
The filter vocabulary (shared by all three rule fields)
| Filter | Meaning |
slot=N / slot=N-M | Restrict to output slot index (1-based, or range) |
t=HH:MM / t=HH:MM-HH:MM | Restrict by the slot's time-of-day (point or range) |
w=mo-fr / w=mo,we | Weekday list or range (wraps across Sunday) |
nw=2mo / nw=lastfr | Nth weekday of the month |
m=1,3,12 / m=3-6 | Month list or range (also jan…dec) |
d=1-7,15 | Day-of-month list / range |
ld | Last day of the month |
!filter | Negate any filter (e.g. !w=sa,su) |
The three rule fields
OffsetRulespayload = time shift
Overlays DateAndTimeDataFormatInEffect. Payload is N/N-M/N+M (source slot + minute offset). Example — push the 3rd slot 15 min later on Fridays:
3+15;slot=3,w=fr
AlarmIndexRulespayload = folder index
Overlays AlarmIndexData. Payload is the alarm-folder index (or comma list) the slot resolves to.
TextDataRulespayload = text
Overlays TextData. Payload is the replacement announcement text (supports #ALARM+1#, #NOW# etc.). Examples:
Jumu'ah Mubarak! Khutbah begins at #ALARM+1#.;slot=7,w=fr
Ramadan Kareem. Iftar at #ALARM+1#.;m=3-4,slot=4
Why this mattersOne annual calendar plus a handful of rules handles Friday Jumu'ah, Ramadan Sehri/Iftar wording, weekend bell schedules, seasonal shift times and holiday silences — all without maintaining separate files for each. For whole-day-different behaviour, see
Config Switching instead.
Configuration · Speech
Text-to-Speech
Multilarm can speak your announcement text aloud — either with a built-in neural voice or by stitching together your own recorded words. It also displays the text on the console, colour-coded.
The two engines
◈
Kokoro neural TTS
A high-quality 82M-parameter neural voice that runs fully offline on the device — type any sentence and hear it spoken naturally. Bundled in the installer; no internet, no API keys, no per-use cost. Multiple built-in voices.
◫
Word-library TTS
Plays pre-recorded word files you record yourself (Ctrl+R). Perfect when you want a specific human voice — a particular reciter or a familiar staff member — saying the numbers and phrases.
Numbers, dates & money are spokenWith the neural voice, figures in your announcement text are read aloud naturally — "Alarm in 5 minutes" is heard as "Alarm in five minutes", "5:30" as "five thirty", "2024" as "twenty twenty four", "$5.50" as "five dollars and fifty cents", "1st" as "first", and symbols like % and ° as "percent" and "degrees". You can type plain digits and symbols; no need to spell them out.
TextToSpeechTrue/Falsedefault True
Master switch: read the console text aloud whenever it changes (driven by TextData).
TTSEngineBuiltin / Kokoro / KokoroFillsWords
Which engine speaks. Builtin = word library only. Kokoro = neural voice for the whole sentence. KokoroFillsWords = use your recorded words where they exist, and let Kokoro neurally synthesise any words you haven't recorded — the best of both.
KokoroVoiceVoice namedefault bm_george
The neural voice to use (a name, not a file path). Several voices ship in the bundle.
KokoroSpeed0.2–3.0
Speaking rate. 1.0 is natural; lower is slower/clearer, higher is faster. Default is 1.0 (the voice's natural pace).
KokoroSentenceSilenceSecondsSeconds
Pause inserted between sentences in neural synthesis.
The announcement text
TextDataDelimited stringdefault It is time for Esha…
The text shown/spoken for each slot, index-matched to DateAndTimeDataFormatInEffect. Each time marks the end of its display interval. Supports placeholders:
| Token | Becomes |
#ALARM+1# | The next alarm time |
#TIMETOALARM+1# | Time remaining until the next alarm |
#ALARM+2# / #TIMETOALARM+2# | The alarm after next / time to it |
#NOW# | Current time (24-hour) |
Supports per-day scope lines; for condition-based overrides use TextDataRules.
TextDelimiterCharacter(s)default |
Separates text entries. Must not appear inside an individual entry.
Word library settings (Builtin engine)
TTSPathFolderdefault {app}\TTS
Folder of per-word audio files. Top level only.
TTSFileFormatStringdefault mp3
Formats for word files (same syntax/support as AlarmFileFormat).
TTSChimePathFile pathdefault (empty)
An optional short attention chime played before each spoken announcement. Leave empty to disable. Takes the same source-spec grammar as AlarmPath, so a multi-tone chime is just a folder plus a mode: {app}\Adhan\Bleep>all plays every tone in filename order, with SequenceGapMs between them, before the speech starts.
TTSSpokenTextInventoryTrue / Falsedefault True
Also remember the words of announcements typed on the spot — a page from the dashboard, a message from the phone app, a line over MQTT — so they appear in the Pronunciation Workbench alongside the words from your config. Words only, held in memory, capped at a few thousand and evicted oldest-first; never the sentences, never the audio, nothing written to disk.
TTSDeriveHypotheses1–10default 5
How many readings the Workbench offers you to choose from after you record a word. This is not a speed setting: the device always works up a shortlist of eight, and this only decides how many of them you are shown. On a Raspberry Pi 400 the same word took 30.7 s at 2 and 30.4 s at 10, so lowering it will not make a slow device faster — it will only give you fewer readings to pick from. Raise it above 8 for an awkward name none of the first few gets right. Those extra candidates really are worked out, and on a long word each one adds about 3½ seconds. On a short word it costs nothing and changes nothing — there are only a handful of sensible ways to say it, and you are already being shown all of them.
TTSSyncPronunciationRecordingsTrue / Falsedefault True
Let the cloud dashboard and the Android app list, delete and send the short word recordings kept for teaching pronunciation. Off keeps them to the local network — the device refuses those three commands with a plain explanation, and the Workbench on your own network is unaffected. They are never played in an announcement either way.
Recording wordsTo cover any time, the word library needs numbers 0–20 plus 30, 40, 50 — about two dozen files. Record them in-console with
Ctrl+
R (see
TTS Recording). Leave ~250 ms of silence after each word for natural playback.
Speak Text (ad-hoc)
Beyond scheduled announcements, you can push arbitrary text to a device for immediate speech — from the dashboard's Speak Text card, the LAN endpoint POST /api/speak, or the cloud speak-text command. An optional per-message engine override (Builtin/Kokoro) applies just for that message. Playback queues at Priority 3, so a live alarm still wins.
Try it nowPress Ctrl+S to speak the current display text through your configured engine.
Fixing a word's pronunciation
If the neural voice says a word wrong — a person's name, a place, a product, or a tricky term — you can correct it yourself, with no phonetic knowledge. The editor is called Pronunciations and appears in three places: the device's own Web Remote dashboard, the cloud dashboard, and the Android app (a tile on the device screen).
It works by ear — you respell the word and listen:
- Word — type the word as it's actually written (e.g. Zayeed).
- Sounds like — type how it should sound, in plain letters (e.g. zah yeed).
- Preview — hear it. On the device dashboard it plays in your browser; on the cloud and app it plays on the device's speakers. Nudge the spelling and preview again until it's right.
- Save — from then on every announcement says the word your way.
Use Test a sentence to hear the fixed word inside a full sentence, and Your fixes to review, replay or remove any correction. There's nothing to record and no file to edit.
Good to know
- Fix a word once and its plural and possessive are handled automatically — fix webhook and you also get webhooks, webhook's.
- Corrections apply to the neural (Kokoro) voice. They're saved on the device (in
g2p.overrides.user.json next to the config) and survive app updates.
- Advanced users can switch on IPA mode to pin a raw phonetic string instead of a respelling.
- The single letter a can't be overridden — it's the English article.
The Pronunciation Workbench — reviewing every word
Fixing a word you have already caught is one thing; finding the ones you haven't is another. The Pronunciation Workbench lists every unique word this device can say — gathered from your announcement text, your schedule templates and, unless you turn it off, the ad-hoc announcements people type — and tells you where each pronunciation came from. Open it on the device's own network at http://<device>:<port>/pronunciations, or from the Pronunciations card on the Web Remote dashboard.
Each word carries a badge saying how it is being pronounced today:
- Guessed — the word is not in the dictionary and the voice is sounding it out from the spelling. These are the ones worth your time, and the page puts them first.
- From a related word — worked out from a word that is known (the plural, the possessive, a common ending).
- Dictionary — a known English word. Usually right, occasionally not: place names and surnames that look like ordinary words are the classic trap.
- Corrected by you — one of your own fixes.
Click a word to hear it, correct it the same way as above, and move on. The list is searchable and filterable, so “show me only the guessed ones” is one click.
Teaching a word by voice
Some words are easier to say than to respell. In the Workbench you can press Record, say the word once or twice into your phone or laptop microphone, and let the device work out the pronunciation for you.
It is worth being clear about what this does and does not do:
Your recording is never played on airMultilarm does not splice your voice into announcements. Your recording is used only as a target: the device measures it, searches for pronunciations that would sound like it, synthesises the best few in the Kokoro voice, and ranks them against what you said. You then hear the candidates, pick the one that sounds right, and it is saved as an ordinary dictionary entry — so every announcement stays in one consistent voice, and the plural and possessive follow automatically.
- Record — press it, say the word, press it again. Two or three takes of the same word are better than one; the device averages what your deliveries agree on.
- Work it out — the device thinks for a few seconds (longer on a Raspberry Pi) and offers a short list of candidates.
- Hear each candidate in the announcement voice, then Use this on the one that is right. That saves it exactly as a typed correction would.
- Forget recordings removes the takes for that word. The saved pronunciation stays.
If the browser will not offer a microphone — it only shares one over HTTPS or on localhost — use Suggest from spelling instead, which offers the same kind of candidate list worked out from the letters alone.
Good to know
- The recording comes from your browser's microphone, not the device's, so it never interrupts Push-to-Talk, a voice memo or anything playing.
- Only one word is worked out at a time — speech synthesis is the scarcest thing on a small device. Start another and the first is asked to stop.
- Recordings live beside the config in
g2p.recordings\, five takes per word at most. They are training material and are never played through the speakers.
- With
TTSSyncPronunciationRecordings on, the cloud dashboard and the Android app can list, delete and add those recordings too — so you can teach a word from your phone while standing in the hall.
- This is a different thing from Ctrl+R TTS Recording, which records whole words as audio files for the Builtin engine. Press P in that menu for a pointer to the Workbench.
After you change the voice
A pronunciation you taught by voice is not a fact about the word. It is the reading that made the voice you had at the time sound closest to your recording — the candidates were synthesised in that voice and ranked against your delivery. Change KokoroVoice and every word you taught this way stays exactly as it was, now tuned to a voice the device no longer uses. Nothing breaks and nothing warns you; the words are simply said with someone else’s vowels.
At the bottom of the Workbench, Re-check my recordings works every recording you still have stored through the voice that is loaded now, and lists only the words the new voice disagrees with. It is worth pressing once after any voice change.
Nothing is saved for youThe re-check never writes a dictionary entry. It reports what the new voice thinks, and each changed word opens in the ordinary editor with the new reading filled in but not saved — accepting it is the same deliberate Save as any other correction. Auto-applying would silently rewrite the pronunciations you took the trouble to teach, which is the very thing this is meant to show you.
Good to know
- It takes roughly half a minute per recorded word on a Raspberry Pi, and the first word of a run may also build that voice’s phoneme bank (a few minutes, once per voice). Start it and leave it — the page picks the run back up if you reload.
- It runs below the priority of everything else, so an announcement never waits behind it, and Stop ends it between words.
- Only one pronunciation job runs at a time. If you start a single word’s Work it out while a re-check is going, the re-check stands down — the person at the keyboard wins.
- Words with no stored recording are not touched. If you pressed Forget recordings on a word, there is nothing left to re-check it against, and its saved pronunciation stays as it is.
Configuration · Audio Output
Audio Devices & Mirroring
Choose which sound card plays, which records, and how to fan one stream out to many speakers at once.
PlaybackDeviceInteger ≥ -1default -1
Output device. -1 = system default · 0 = disable playback entirely · 1+ = a specific hardware output (1-based).
RecordDeviceInteger ≥ -1default -1
Input device for TTS recording, Voice Broadcast and Push-to-Talk. -1 = system default · 0+ = a specific input (0-based). Note: outputs are 1-based, inputs are 0-based.
Finding device indices — Ctrl+O
Press Ctrl+O in the running console to print every audio device, one table for playback and one for recording:
Playback (PlaybackDevice=-1, MirrorDevices=1|3):
0: Speakers (Realtek) [default] [init]
1: USB Audio DAC [init]
3: HDMI Output
Recording (RecordDevice=-1):
0: Microphone (USB) [default]
The first number is the index to enter in your config. Flags: [default] = OS default · [disabled] = present but unavailable (don't use) · [init] = currently in use by Multilarm. Ctrl+O is read-only.
Multi-speaker mirroring
MirrorDevicesPipe-delimited indicesdefault (empty)
Play every sound — alarms, radio, ambience, TTS, Voice Broadcast, PTT — simultaneously through one or more additional outputs alongside PlaybackDevice. The engine decodes once and attaches every target as a secondary output sharing the same clock, so all speakers are frame-aligned — never half a second apart.
Example: 2|4|7 mirrors to three extra devices. Volume, fade, crossfade, skip and stop apply to the whole group as one. If a mirror fails to initialise it's logged and skipped; the rest keep working.
Typical uses: line-out to an amplifier plus a USB speaker for another zone; built-in speakers plus Bluetooth for quiet hours; multiple sound cards wired across a building; FM transmitter plus an internet-stream encoder.
Mirroring vs zonesMirroring sends the
same audio everywhere. To run
different schedules per room, use multiple instances with
--config — see
Multi-Zone Deployments. The two combine: each zone can have its own mirror group.
Configuration · Virtual Radio
Virtual Radio
A fully automated background music station: shuffle your library with no repeats, crossfade between tracks, and have it on air only during the hours you choose. Or play a live internet stream.
PlayRadioLocal / Online / Disableddefault Local
Local — shuffle RadioFolder. Online — play RadioStreamURL, automatically falling back to the local folder if the stream drops (logged, with a radio.stream-fallback webhook; retried every ~10s). Disabled — radio off. Legacy True/False map to Local/Disabled.
RadioFolderFolderdefault {app}\Radio
Root of the radio library. Subfolders are included here (unlike alarm folders) and used for a smart shuffle: each round visits subfolders in fresh random order, one file per subfolder before any repeats — so genres interleave and nothing repeats until everything has played. Then it rescans and restarts.
RadioFileFormatStringdefault mp3
Formats included. Supports MP3/MP1/MP2, OGG, WAV, AIFF, FLAC, M4A. Join with ;; wildcards allowed.
RadioStreamURLURL
The internet stream to play when PlayRadio=Online (HTTP/ICY/Shoutcast-style streams).
RadioScheduleRulesTime-range rulesdefault 00:00-00:00,m=*
When the radio is on air. Each rule: HH:MM-HH:MM[|range][,filter][,!filter]. Multiple ranges per rule (merged on overlap); multiple rules separated by ;; on air if any rule matches. Filters are the same family as the rules engine (m=, d=, w=, nw=, ld, !). Ranges crossing midnight work; equal endpoints = full 24 hours.
# Mornings, lunch, evenings on weekdays:
07:00-09:00|12:00-14:00|17:00-21:00,w=mo-fr
# Weekday office hours + Saturday mornings, except August:
08:00-20:00,w=mo-fr;09:00-17:00,w=sa,!m=8
Console toolsCtrl+
N skips the current track;
Ctrl+
F test-plays a random radio file for 10 seconds. Every track (and every stream title) is logged to
Multilarm.playlist.log — always on, and automatically size-capped so it can't grow without bound. See
Monitoring & Logs.
Configuration · Fillers
Quote & Ambient Audio
Two gentle background layers (Priority 2): random spoken excerpts from a long recording, and ambient sound to keep speakers awake between events.
Quote playback
Plays a random complete spoken segment from a long audio file — detecting natural silence boundaries so excerpts start and end cleanly. Great for rotating reminders, hadith, safety messages or station idents.
PlayQuoteTrue/Falsedefault True
Enable periodic quote playback.
QuoteFileFiledefault {app}\Adhan\Quote.mp3
The long source recording. Works best with continuous spoken-word audio.
QuoteIntervalSecondsdefault 180
Time between quote plays.
QuoteScheduleRulesTime-range rulesdefault 00:00-00:00,m=*
When quotes are allowed (same syntax as RadioScheduleRules). The interval timer still fires, but playback is skipped outside matching windows. Example — quotes everywhere except 30-min windows around prayer times:
00:00-06:00;06:30-12:00;12:30-16:00;16:30-18:00;18:30-00:00
Zero config silence detectionBoundary detection is fully automatic — Multilarm scans the file once and caches results in a .boundaries.json sidecar next to it. Delete the sidecar to force a rescan. There are no threshold knobs to tune.
Ambient audio
Plays a short slice of an ambient file at intervals — mainly to stop amplifiers/speakers entering sleep or producing a pop when the next real sound starts.
PlayAmbienceTrue/Falsedefault True
AmbienceFileFiledefault {app}\Adhan\Ambience.mp3
The ambient source; a random slice is played each time.
AmbienceDurationSecondsdefault 10
How long each ambient slice plays.
AmbienceIntervalSecondsdefault 180
Gap between ambient plays (default: every 3 minutes).
Configuration · Volume
Volume & Transitions
Master level, smooth crossfades, and time-of-day volume overrides.
Volume0.0–1.0default 1.0
Master volume applied to all audio (alarms, TTS, quotes, radio, ambience). This is an application-level control — it doesn't change the OS volume. Set the OS to a comfortable max, then fine-tune here.
CrossfadeMsInteger (ms)default 1500
Crossfade duration between radio tracks (and when background resumes after an interruption). 0 = instant switching. 1000–2000 ms gives the most pleasant transitions.
VolumeProfileRulesTime-range rulesdefault (empty)
Per-schedule volume overrides. Same syntax as RadioScheduleRules, plus one extra filter volume=<0.0-1.0> that replaces the master volume while the rule is active. First match wins; evaluated every second. When no rule matches, master Volume applies.
# Quiet nights at 30%:
22:00-07:00,volume=0.3
# Quiet night + weekday lunchtime dip:
22:00-07:00,volume=0.3;12:00-14:00,volume=0.6,w=mo-fr
# December cap at 50%, otherwise quiet nights at 20%:
00:00-00:00,volume=0.5,m=12;22:00-06:00,volume=0.2
Gentle FajrCombine AlarmFadeInMs with a low-volume VolumeProfileRules window for a soft early-morning wake-up.
Configuration · Remote & Instances
Remote Control Settings
The config fields that enable and secure the remote interfaces. For how to use each interface, see the dedicated pages linked below.
Web Remote
WebRemoteEnabledTrue/Falsedefault False
Start the built-in HTTP server (dashboard + REST API). Restart required. See Web Remote.
WebRemotePort1–65535default 6580
TCP port. Restart required.
WebRemoteUsername / WebRemotePasswordStringdefault (empty)
Optional HTTP Basic Auth. When both are blank the server binds loopback-only (host machine only). Set both to expose on the LAN.
Cloud Relay
CloudDeviceToken64-char hexdefault (empty)
Paste the device token from your cloud dashboard to connect this instance to https://multilarm.com/cloud. Empty = cloud disabled. Treat it as a password. You don't have to edit the file by hand: press Ctrl+K at the console or run Multilarm --cloud-token <token> and it's written for you. See Cloud Relay.
AlertDiskFreeMinMBInteger (MB)default 500
Free-space floor on the drive that holds your audio library. When free space drops below this, the device raises a disk-low flag in its cloud status push (health.disk) so the dashboard can warn you before recording or uploads start to fail; it's also shown on the Ctrl+I status. Set 0 to disable the check entirely (the cloud then never sees diskLow=true and never fires the alert). See Cloud Relay.
AutoUpdateTrue/Falsedefault False
Opt-in automatic updates. When True the device checks the published version manifest (https://multilarm.com/multilarm/latest.json) about once a day and installs a newer build for its platform by itself — no dashboard command needed. It takes exactly the same safe path as the manual Update button: SHA-256 verified download, previous binary kept, and a watchdog that rolls back automatically if the new version fails to come back online. A build that has just failed is not retried for 48 hours, so a bad release can never spin in a restart loop. Left False, the device updates only when an owner clicks Update. The cloud dashboard's Software row toggles this switch — it is delivered to the device as this config field. See Cloud Relay.
External Triggers & MQTT
TriggerConfigPathPathdefault (empty)
The local trigger profiles/tokens JSON. Empty derives <configBaseName>.triggers.json next to the active config. See External Trigger API.
MqttEnabledTrue/Falsedefault False
Enable the inbound MQTT subscriber.
MqttHost / MqttPortString / Integerdefault port 1883
Broker host (no scheme) and port. Falls back to 8883 if MqttUseTls is on and the port is left default.
MqttUseTlsTrue/Falsedefault False
MqttUsername / MqttPasswordString
MqttBaseTopicStringdefault multilarm
Topic prefix: subscribes to <base>/trigger and <base>/trigger-audio/+; publishes to <base>/status and <base>/events.
Instance & config switching
InstanceNameFree textdefault config base name
A pure display label shown on the Hub grid, in the heartbeat file and on the Ctrl+I status. It plays no role in routing — instances are keyed by an auto-generated InstanceId. Set a meaningful name when running multiple zones (e.g. "Main Hall PA"). See Multi-Zone.
AlternateConfigfilename,rule pairsdefault (empty)
Load a completely different config on matching days (Ramadan, weekends, winter). Evaluated at midnight, first match wins. See Config Switching.
Tasks · Voice
Recording your TTS word library
A guided console mode (Ctrl+R) for recording the individual words the word-library engine stitches into announcements — in any voice you like.
What it records
On entry, all scheduled audio pauses (and restores on exit). Multilarm works out which words it needs from your TextData (placeholders stripped) merged with the number words Zero–Twenty plus Thirty/Forty/Fifty, de-duplicated. Words you've already recorded are skipped — only missing ones are prompted.
Step by step
- Press Ctrl+R.
- Choose a format (asked once):
0 WAV · 4 FLAC (lossless). (Press Ctrl+M to leave without recording.)
- For each word: press any key to start, any key to stop. Then:
- P — play back the take (loops until a key press)
- R — re-record
- S — save and move to the next word
- any other key — discard and move on
- Ctrl+M — discard and return to the console
Saves go to TTSPath as <word>.<ext>. Re-running later picks up only new/missing words; existing recordings are preserved.
For clear playbackLeave roughly 250 ms of silence after each word. Recording uses RecordDevice at 44.1 kHz stereo.
Prefer not to record?Use the
Kokoro neural engine instead and skip word recording entirely — or
KokoroFillsWords to record only the words you care about and let the neural voice cover the rest. See
Text-to-Speech.
Tasks · Voice
Voice Broadcast
Record a short clip, preview it privately, and — after explicit confirmation — broadcast it through the PA at top priority, just like an alarm.
From the console
Press Ctrl+B to enter Voice Broadcast mode:
- R — toggle recording on/off
- P — preview locally
- T — transmit (prompts Y/N, then plays through
PlaybackDevice + mirrors at P1)
- D — discard
- Ctrl+M — exit
The clip is stored as voicememo.wav next to the program, overwritten by each new recording.
From the browser (dashboard)
The Voice Broadcast card on the Web Remote and Cloud dashboards has Record / Preview / Transmit / Discard buttons plus an upload control. When you click Record on a secure page (HTTPS or localhost), the dashboard captures from your browser's microphone, builds a WAV, and uploads it — so you can speak from your phone or laptop, not just the device's mic. On plain HTTP it falls back to recording from the device's RecordDevice.
Confirmation is enforcedTransmit always requires explicit confirmation — the browser prompts, and the server itself rejects a transmit that doesn't carry {"confirm":true}. Accidental one-click broadcasts are impossible.
Upload formats: WAV, MP3, OGG, FLAC/AIFF. Size limit 16 MB (LAN) / 5 MB (cloud upload). The upload endpoint is rate-limited and honours Basic Auth if configured.
Tasks · Voice
Push-to-Talk (live mic)
Speak through the speakers in real time. The imam, the head teacher or the floor manager picks up a mic and the live audio passes straight through the PA — over every mirrored speaker at once.
How it behaves
- Routes the live input from
RecordDevice to PlaybackDevice + every MirrorDevices index.
- Always pre-empts an in-flight alarm — whoever holds the mic owns the speakers.
- Auto-stops after 30 s without a keepalive, and a hard 5-minute session cap, so a forgotten open mic can't run forever.
- Latency is auto-tuned per platform (tighter on desktop, slightly relaxed on Raspberry Pi). No configuration needed — PTT is always available.
Two ways in
🎙
Device-local
An operator at the device drives it via the Web Remote PTT controls (/api/ptt/start, stop, keepalive, status) using the device's own microphone.
☁
Browser mic over cloud
From the Cloud dashboard, your browser's mic streams to the device — speak to a remote site's PA from anywhere. (~5 s start-up latency over the cloud path.)
Preempt vs deferThe dashboard PTT card has a Priority selector. By default a live mic preempts; you can instead choose to defer behind an in-progress alarm, in which case the session waits (up to 10 min) for a clear moment before starting.
PTT fires ptt.started / ptt.stopped webhooks and audits each session. Acoustic feedback cancellation is intentionally not provided — position the mic away from the speakers.
Tasks · Voice
Talkback (call-in)
Push-to-Talk with the arrows reversed. Instead of the office speaking to the building, somebody in the building rings the office — a caretaker at the panel, a teacher at the front desk, a warden during a drill — and the operator can answer and hear the room.
Who can start a call
Only somebody at the device. There are exactly two ways to ring:
- The device's own
/talkback page, on the local network — open the Web Remote and click Talkback, or go to http://<device>:<port>/talkback. Type who you are and, optionally, one line about what it is about.
- Ctrl+W at the running console — rings the operator from the device itself; press again to hang up.
There is no "listen in" buttonThe Cloud dashboard can answer a call, decline it, listen while it is live and hang up. It cannot start one. No command exists that opens a microphone in a building from outside it, and this is deliberate: a remote listening feature that can be switched on without anybody in the room knowing is a surveillance feature, and Multilarm does not ship one. Room Listening and Recording are separate, consent-configured features with their own settings.
What happens on a call
- The call rings for up to 60 s unanswered, then gives up on its own.
- When the operator answers, the device plays an audible chime in the room. Nobody is listened to silently.
- A live call runs for at most 5 minutes, and ends early if the listener stops sending keepalives (a closed browser tab, a dropped link).
- Audio is buffered for seconds at a time and swept when the call ends. Nothing is recorded and nothing is stored — there is no file to find afterwards, on the device or in the cloud.
- Every request, answer, decline and hang-up is written to the audit log.
One microphone, one user
Talkback shares the device's single RecordDevice with everything else that needs an input, arbitrated by priority:
So a live PA announcement wins over a talkback call, and a talkback call wins over passive listening. If the mic is busy with something above it, answering is refused with a reason rather than silently failing.
Endpoints
| Route | Purpose |
GET /talkback | The local call-in page |
GET /api/talkback/status | Current state: idle / ringing / live, who, how long |
POST /api/talkback/request | Ring the operator (LAN only — no cloud twin) |
POST /api/talkback/answer · decline · hangup · keepalive | Call control |
GET /api/talkback/chunk?seq=N | Pull the next half-second of room audio as a WAV |
Over the cloud the same call is answered from the dashboard's Talkback (call-in) card; the device then streams raw PCM up to the relay and the operator's browser plays it back. Talkback state rides the ordinary status push, so a ringing call appears on the dashboard without any extra polling.
Reference
Console Keyboard Reference
Every shortcut available at the running console. Most are single Ctrl+key combinations at the main screen; a couple are scoped to sub-modes.
Playback & testing
| Key | Action |
| Ctrl+T | Test-play a random alarm from a random AlarmPath folder |
| Ctrl+A | Test-play AmbienceFile for AmbienceDuration |
| Ctrl+S | Speak the current display text via the configured TTS engine |
| Ctrl+F | Test-play a random radio file for 10 seconds |
| Ctrl+N | Skip the current radio track |
| Ctrl+L | List audio files played in the past hour |
Voice modes
| Key | Action |
| Ctrl+R | Enter TTS Recording mode (pauses all audio) |
| Ctrl+B | Enter Voice Broadcast mode |
| Ctrl+P | Live PA (push-to-talk): stream the local microphone straight to the speakers; press again to stop. Pre-empts any playing alarm; 5-minute session cap |
| Ctrl+W | Talkback: ring the operator from this device; press again to hang up. Rings for 60 s, 5-minute call cap, nothing recorded |
| Ctrl+M | Exit the current sub-mode back to the main console |
Diagnostics & devices
| Key | Action |
| Ctrl+I | Runtime status: uptime, current audio, radio, volume & profile, active config, Web Remote, Hub state & peers, Cloud Relay, relay board & emergency button state, next alarm |
| Ctrl+D | Dump the full console log buffer (up to 500 entries) |
| Ctrl+Y | Run the built-in diagnostics check-up battery inline — silent, safe, PASS/WARN/FAIL per check with fix hints |
| Ctrl+O | List all audio devices with indices & flags (for PlaybackDevice/RecordDevice/MirrorDevices) |
| Ctrl+G | Relay board bring-up test — pulses each channel and prints states. See Multilarm Box Hardware |
| Ctrl+K | Set or clear the Cloud Relay device token — paste it at the masked prompt and it's validated, saved to the config, and applied immediately (no restart). Empty input disables Cloud Relay; Esc cancels |
Hub & exit
| Key | Action |
| Ctrl+H | Promote this instance to Hub mode |
| Ctrl+Shift+H | Step down from Hub mode |
| Ctrl+X | Exit the program |
Why not Ctrl+V?Ctrl+V is reserved by the Windows console (paste), so Voice Broadcast uses Ctrl+B.
Deployment & Operations
Multilarm Box Hardware
Turn a Raspberry Pi into the complete brain of an institutional audio chain: PA announcements, a physical school/mosque bell via a USB relay board, a wired emergency broadcast button on GPIO, and zone-targeted announcements that mute the other zone's amplifier while they play. Everything ships disabled — a config without these fields behaves exactly as before.
What you need
- Relay board — a cheap CH340 USB relay (~£12, "LCTECH/SainSmart/ELEGOO USB relay"). A 4-channel board drives the bell circuit, two zone amp mutes, and an emergency indicator light; step up to an 8-channel board to run three or four zones (two extra zone mutes on channels 5 and 6).
- Emergency button — any momentary normally-open button wired between a GPIO pin (default GPIO 17, physical pin 11) and GND. Multilarm enables the Pi's internal pull-up itself — no resistor required.
- Audio out — the Pi's existing output, a USB DAC, or a HiFiBerry HAT (Pi 4/5 only — the HAT cannot seat on a Pi 400). Pick the device index with Ctrl+O as usual.
Enable it (config Group 16 — Hardware I/O, just six fields)
The hardware contract is fixed in code so there's almost nothing to configure: 9600 baud, channels 1=Bell, 2=ZoneMuteA, 3=ZoneMuteB, 4=EmergencyLed (plus 5=ZoneMuteC, 6=ZoneMuteD on an 8-channel board for four zones — wire the board to match), 500 ms button debounce and a 3 s hold-to-reset.
- Plug in the relay board and set
RelayPort (usually /dev/ttyUSB0) — the port is the on/off switch; leave it empty and the relay feature is completely off.
- Confirm the board's byte protocol on a test config first: set the port, press Ctrl+G — each channel should click. No clicks? Switch
RelayProtocol between A0 and FF and retry. Wrong protocol is harmless but the bell won't ring.
- Bell times go in
BellScheduleRules: 08:45,dur=1500,w=mo-fr;09:00,dur=1500,w=mo-fr; — 24-hour time, pulse length in ms, and the same date filters as radio rules. Bells are relay pulses only; they never interrupt announcements.
- For the button, set
EmergencyGpioPin (-1 = off, 17 = the documented wiring) and point EmergencyAudioFile at a short WAV/MP3 (record one with Voice Broadcast, or synthesise with the TTS engine). It plays on loop at top priority and every scheduled alarm, announcement, bell, quote and ambience is suppressed until reset — hold the button for 3 seconds, or press Reset on the web panel.
Field reference
RelayPortDevice pathdefault (empty) = relay off
The relay board's serial device and the feature's on/off switch — leave empty and the relay is completely off (like CloudDeviceToken). Usually /dev/ttyUSB0; the Box provisioning script also creates a stable /dev/multilarm-relay alias. Hot-reloads.
RelayProtocolA0 | FFdefault A0
Byte framing the board speaks. A0 = most LCTECH/SainSmart CH340 boards; FF = some ELEGOO boards. Confirm with Ctrl+G — a wrong choice is harmless (the board ignores the bytes) but nothing will click.
BellScheduleRulesSemicolon-delimited rulesdefault (empty) = no bells
HH:MM[,dur=ms][,date filters] per entry — fire time, pulse length (default 1000 ms, clamped 100–60000), and the same w=/m=/d=/nw=/ld filters as radio rules. Skipped fires (board off/unplugged) are logged. Hot-reloads — rules are re-read every minute. The Config Generator has a point-and-click builder for these entries.
EmergencyGpioPinBCM pin; -1 = offdefault -1
The button's GPIO and the feature's on/off switch (like PlaybackDevice's -1). GPIO 17 = physical pin 11 is the documented wiring; avoid 18–21 with a HiFiBerry fitted. Internal pull-up, 500 ms debounce and the 3 s hold-to-reset are handled in software. Hot-reloads.
EmergencyAudioFileFile path (tags OK)default (empty)
Looped at top priority during an emergency. Any playable format; short and clear loops best. Missing file → warning, and the emergency still suppresses the schedule and lights the LED (a silent emergency beats a crashed one).
ZoneRulesRules overlaydefault (empty) = play everywhere
Which announcements are zone-targeted; same overlay grammar as TextDataRules with ZoneA–ZoneD as the payload, e.g. ZoneA;slot=7 (up to four zones). First match wins; no match = all zones, zero relay activity. The Config Generator has a point-and-click builder for these rules.
RemoteEmergencyAllowedTrue/Falsedefault True
Master switch for the cloud dashboard / Android app Start emergency drill command. Left True, an owner can start an emergency broadcast remotely after confirming. Set False and the device refuses a remote start — only the physical button (EmergencyGpioPin) can begin one — while a remote reset is always honoured, so a stuck emergency can still be cleared from afar. The button itself and the LAN Web Remote Hardware panel are unaffected either way. Remote start is best-effort by nature (it rides the cloud command poll and needs connectivity); the physical button stays the authoritative path. Only relevant once the emergency feature is configured.
The web Hardware panel
When either feature is enabled, the Web Remote dashboard grows a Hardware card: live channel states (refreshes every 5 s), per-channel Open/Close/Pulse, a Ring Bell button with duration, and the emergency status — shown in red with a single Reset button while active. Test Emergency runs the complete chain (audio override, schedule suppression, LED) with no button wired, so you can drill the whole path before any hardware arrives. If the board is unplugged the panel shows "Not connected" within one refresh and everything else keeps running.
Emergency Scenarios & automatic CAP alerts
Beyond the single built-in emergency, you can define named scenarios — e.g. Lockdown, Evacuate, All-clear test — each with its own looped audio and its own set of relay channels (a beacon, a strobe, the LED). Drop a scenarios.json file next to the device's config (same folder and base name, e.g. Multilarm.scenarios.json):
{
"scenarios": [
{ "name": "Lockdown", "audioFile": "{app}\Adhan\lockdown.wav",
"relayChannels": ["EmergencyLed", "2"], "suppressSchedule": true }
],
"cap": { "enabled": false, "feedUrl": "https://…/alerts.atom",
"pollSeconds": 120, "minSeverity": "Severe",
"eventFilter": "Tornado,Fire", "scenario": "Lockdown" }
}
The built-in Emergency scenario always exists and reproduces the standard chain exactly. Fire any scenario from the cloud dashboard's Multilarm Box card (Emergency Scenarios section, owner-only, obeys RemoteEmergencyAllowed) or on the LAN via POST /api/scenario/fire with {"name":"Lockdown"}; POST /api/scenario/reset clears it.
CAP / IPAWS auto-fire. The cap block (off by default) polls a Common Alerting Protocol feed — national weather service, government or IPAWS — and automatically fires the mapped scenario when a real alert arrives at or above your chosen severity (and matching the optional event filter); a cancellation clears it. Multilarm only ever fires your own local, pre-defined scenario — it never plays anything the feed links to. Polling is outbound-only and capped. Leave enabled:false until you've tested your scenario with the dashboard Fire button.
Which feed do I point it at? Two are supported out of the box and were checked against the live services on 17 August 2026. Paste one into feedUrl:
| Where you are | Feed URL | Notes |
United States National Weather Service |
https://api.weather.gov/alerts/active.atom?area=TX |
Replace TX with your two-letter state code. Open US-government data — no account, no API key. Carries full cap:severity, cap:event and cap:msgType, so cancellations clear the scenario automatically. |
UK & Europe MeteoAlarm |
https://feeds.meteoalarm.org/feeds/meteoalarm-legacy-atom-united-kingdom |
Replace united-kingdom with your country (ireland, france, germany…). Free, no key. Aggregates the official warnings of 38 national met services. |
If you use MeteoAlarm you must display an attribution. Its data is licensed CC BY 4.0, which permits redistribution but requires credit — so wherever you show these alerts (a notice board, a display screen, a printed sign) include a line such as “Weather warnings © MeteoAlarm (meteoalarm.org), CC BY 4.0”. The NWS feed carries no such condition. This is a licence term, not a courtesy.
A quiet feed looks identical to a broken one — an area with no active warnings returns a valid but empty feed. To confirm your setup really works, temporarily point feedUrl at a country that currently has warnings, watch the console log the match, then switch back.
Zone-targeted announcements
Wire each zone's amplifier mute input to its relay channel — ZoneMuteA = ch2, ZoneMuteB = ch3, and (8-channel board) ZoneMuteC = ch5, ZoneMuteD = ch6. To play an announcement in one zone, Multilarm opens the mute relay of every other zone you use, so only the chosen zone is heard. ZoneRules decides which announcements are targeted, using the same overlay grammar as TextDataRules with the zone name as payload: ZoneA;slot=7 sends the Dhuhr announcement to Zone A only. Two zones run on a 4-channel board; three or four need an 8-channel board. A site that only uses ZoneA and ZoneB never touches the ZoneC/ZoneD relays. The mutes engage just before the announcement and always release afterwards — even if playback fails. Announcements matching no rule play everywhere with zero relay activity, and bells always ring everywhere.
Status, logs & events
- Ctrl+I shows two extra lines: relay board state (port, protocol, channel count, or the failure reason) and emergency button state (armed pin / active flag).
- Ctrl+G runs the relay bring-up test: pulses the bell and a zone mute, holds the LED for a second, prints every channel state.
GET /api/status carries a hardware block (relay/emergency enabled, available, emergencyActive) — so the cloud dashboard's status push sees emergencies too. GET /api/hardware/status has the full detail; see the API reference.
- Webhook events:
bell.fired, emergency.triggered, emergency.reset — see Outbound Webhooks.
- Every relay action, bell fire, emergency trigger and reset is written to
Multilarm.audit.log with its source (GPIO button, WEB panel, scheduled).
Provisioning a fresh Pi
The repo's scripts/ folder has the appliance tooling: build-multilarm-image.sh (one-shot idempotent setup of a fresh Pi OS Lite 64-bit (bookworm or trixie) — install dir, system service, relay udev rule, HiFiBerry overlay, CPU governor, log rotation), update-multilarm.sh (push new binaries, keep config), and multilarm-hardware-test.sh (on-site smoke test: relay click test for both protocols, audio, GPIO, service health). Run the smoke test before every go-live.
Safety firstBell circuits at mains voltage (240 V) must be wired by a qualified electrician. The relay contacts are rated 10 A/250 VAC, but cable identification and isolation are not a DIY job. 12 V bell circuits are safe to wire yourself.
Remote Control
Web Remote
A built-in web dashboard and REST API served straight from the device — control and monitor Multilarm from any browser on the same network. Ideal for a headless Raspberry Pi: run it from your phone, no SSH, no monitor.
In plain termsThe
Web Remote is a tiny website that Multilarm itself runs. You don't install anything and there's no separate server — the device hands out the control page to any phone or laptop on the same Wi-Fi when you visit its address. It comes in two halves that share the same engine: the
dashboard (the buttons and status a human sees) and the
REST API (the same actions, but addressable by other software). Reaching it over the internet instead of the local network is the job of the
Cloud Relay.
Enabling it
- Set
WebRemoteEnabled=True (and optionally change WebRemotePort, default 6580).
- Restart Multilarm (these two fields bind at startup).
- Browse to
http://<device-ip>:<port>/ — e.g. http://192.168.1.50:6580/.
Windows "access denied"?Run this once as administrator to allow network access without elevating the app each time:
netsh http add urlacl url=http://+:6580/ user=Everyone
The dashboard
- Status — uptime, current audio, radio state, volume & profile, next alarm (auto-refresh 15s).
- Controls — volume slider, Skip Track, Test Radio, Test Alarm (with a folder dropdown).
- Today's Schedule — remaining prayer/bell times with live countdowns.
- Console Log — the rolling buffer, for remote diagnostics.
- Config Editor — view/edit every field and Save & Reload (the full Config Generator is embedded at
/config).
- Voice Broadcast, Speak Text, Push-to-Talk and File Manager cards.
- Hardware — appears automatically when the Multilarm Box relay board or emergency button is enabled: live relay channel states (5 s refresh), per-channel Open/Close/Pulse, a Ring Bell button with duration, and the emergency status with Test/Reset (shown in red while an emergency is active).
REST API
New to the term? "REST API" in one minuteAn API (Application Programming Interface) is simply a way for another program to talk to Multilarm, instead of a person clicking buttons. REST is the most common style of web API: you make ordinary web requests — the same kind your browser makes — to short addresses called endpoints, and you get back a tidy machine-readable answer in JSON (a plain-text format that's easy for code to read). You ask for information with GET and you make something happen with POST. So GET /api/status means "tell me how you are" and POST /api/volume means "set the volume". Anything you can do on the dashboard, you can do from a script, a smart-home system, or a scheduled job — because the dashboard itself is just a friendly face over this same API.
All endpoints return JSON; base URL http://<ip>:<port>/api/.
| Method · Endpoint | Purpose |
GET /api/status | Live status JSON |
GET /api/config | All config fields |
POST /api/config | Update fields (only changed ones needed) |
POST /api/skip | Skip radio track |
POST /api/test-alarm | Play a random alarm (optional {folder}) |
POST /api/test-radio | Play a random radio file (10s) |
POST /api/volume | Set master volume {volume:0.0-1.0} |
GET /api/logs | Console log buffer (array) |
GET /api/schedule | Today's remaining schedule with countdowns |
GET /api/alarm-folders | Configured alarm folders |
POST /api/speak | Speak arbitrary text {text,engine?} |
/api/voice/* | Voice Broadcast (status/record/stop/preview/transmit/discard/upload) |
/api/ptt/* | Push-to-Talk (start/stop/keepalive/status) |
/talkback · /api/talkback/* | Talkback call-in page and call control (request/answer/decline/hangup/keepalive/status/chunk) |
/api/files/* | File Manager (roots/list/upload/delete) |
POST /api/trigger[-audio] | External Trigger API (LAN) |
/api/hardware/* | Multilarm Box: relay status/control, manual bell, emergency test/reset |
# Examples
curl http://192.168.1.50:6580/api/status
curl -X POST http://192.168.1.50:6580/api/volume \
-H "Content-Type: application/json" -d '{"volume":0.6}'
Automate itThe API is script-friendly. A cron job can dim the volume at night and restore it at dawn:
0 22 * * * curl -s -X POST http://localhost:6580/api/volume -H "Content-Type: application/json" -d '{"volume":0.3}'
0 6 * * * curl -s -X POST http://localhost:6580/api/volume -H "Content-Type: application/json" -d '{"volume":1.0}'
For security, firewall and internet-access details, see Web Remote Security.
Remote Control · Security
Web Remote Security & Networking
How to protect the Web Remote, configure the firewall, and reach it over the internet safely.
Built-in protections (automatic)
- Loopback-only when credentials are blank. If
WebRemoteUsername or WebRemotePassword is empty, the server binds to localhost only — unreachable from other devices. Set both to expose it on the LAN.
- Failed-auth lockout. 5 consecutive bad logins from an IP → 15-minute lockout. Address families (IPv4/IPv6 loopback) are normalised so the limit can't be bypassed.
- Rate limiting. Each IP may make at most 30 state-changing POSTs per 60s; excess returns HTTP 429. GET polling is unaffected.
- Constant-time credential comparison to defeat timing side-channels.
Authentication
Set both WebRemoteUsername and WebRemotePassword to require HTTP Basic Auth. Browsers prompt automatically; for API calls send the header or use curl -u user:pass.
Basic Auth is Base64, not encryptionIt's fine on a trusted LAN, but don't expose plain HTTP to the internet. Put HTTPS in front (reverse proxy) or use the
Cloud Relay instead.
Firewall
Multilarm auto-creates a firewall rule on startup (Windows Firewall inbound rule "Multilarm Web Remote"; ufw allow on Linux if active). If running unelevated and the rule fails, the server falls back to localhost-only. To open it manually:
# Windows:
netsh advfirewall firewall add rule name="Multilarm Web Remote" dir=in action=allow protocol=tcp localport=6580
# Linux:
sudo ufw allow 6580/tcp
Most Raspberry Pi setups have no firewall by default — nothing to do.
Access over the internet
Two options:
- Recommended: use the Cloud Relay — no port forwarding, no inbound connections, works behind any NAT, fully HTTPS.
- Port forwarding: forward an external port to the device, always enable auth first, use dynamic DNS for a stable hostname, and ideally front it with an HTTPS reverse proxy (Nginx/Caddy).
Remote Control
Cloud Relay
Control your devices from anywhere in the world — no port forwarding, no inbound connections to your network. The device reaches out; you drive it from a web dashboard.
How it works
Your browser ⇄ Cloud dashboard ⇄ Multilarm device (your PC / Pi)
The device polls the cloud over HTTPS every 5 seconds for pending commands, runs them locally, and pushes status back. Because every connection is outbound, it works behind any NAT/firewall with zero router setup. If the network drops, it retries with exponential backoff (5s → 60s) and resumes automatically.
Setup — link with a setup code (easiest)
No config files and no long token to copy. The device shows a short code; you type it into the dashboard.
- Get an account — set up personally after a quick payment conversation (see Plans & subscription below); your temporary password arrives by email and you set your own at first sign-in at
https://multilarm.com/cloud. Forgot it later? Use “Forgot your password?” on the sign-in page to reset it yourself by email — no need to contact us.
- On the device, get a setup code:
- Raspberry Pi — the first-boot wizard offers "Get a setup code and link online"; it shows the code on screen and links automatically.
- Windows / any console — run
Multilarm --claim (or press Ctrl+K and choose Claim online). A code like ABC-123 appears.
- In the dashboard, open My Devices → Enter code, type the code, and press Link device. (Shortcut:
multilarm.com/setup.)
- A short guided setup wizard opens — name the device, pick what it's for (mosque, school, care home, office/factory), and play a test sound. The device is now ONLINE. The code expires after 15 minutes; if it lapses, get a fresh one.
Install it as an app
The dashboard can be installed like an app — its own icon, its own window, no address bar — on iPhone and iPad, Android, Windows, macOS and Linux. It is the same dashboard, so there is nothing new to learn and nothing extra to keep updated.
- Android / Windows / macOS / Linux — click Install app in the dashboard's top bar (it appears only when the browser supports it), or use the install icon in the address bar.
- iPhone / iPad — open the dashboard in Safari, tap Share, then Add to Home Screen. On iOS only Safari can install.
Step-by-step instructions per platform live at multilarm.com/cloud/app/.
It does not work offline, deliberatelyWithout a connection the installed app shows a page saying so, and reloads itself when the connection returns. Nothing about your devices is cached — no schedules, no status, no audio. A control screen that shows the last state it happened to remember is worse than one that admits it does not know: you would act on a device list that might be hours old. And your devices never depend on any of it — every schedule and alarm runs on the device, and on site the
Web Remote works on the local network with no internet at all.
Setup — advanced: paste a token
For offline installs or scripting, you can still link with a device token instead of a code. In the dashboard's Advanced: token tab, click + Add Device, name it, and copy the 64-character token, then hand it to the device by any of:
- Console — press Ctrl+K, choose Paste a token, and paste it. It's validated, written into the config for you, and the relay connects immediately — no restart, no file editing.
- Command line (headless / scripted installs) — run
Multilarm --cloud-token <token>. It writes the token into the config file and exits; a running instance on the same config picks the change up within ~2 seconds, so this works over SSH on a Pi without stopping the service. Pass clear to disable Cloud Relay; add --config to target a specific zone's file. The dashboard's token reveal has Windows command / Linux command buttons that copy the complete ready-to-run line.
- Config file — set
CloudDeviceToken to the token directly.
Either way, watch for "Cloud relay started" then "Cloud: Connected…" in the console; the device shows ONLINE on the dashboard.
Keeping devices up to date is one click: when a newer Multilarm release is available the device's status card shows an Update button. The device downloads the new version, checks it, switches to it and confirms it came back online — and if anything goes wrong it automatically rolls back to the version it was running, so a remote device can never be left broken. By default nothing updates on its own — you're always in control. If you'd rather not think about it, the same Software row has an optional Auto-update switch: turn it on and that device checks for a newer release about once a day and installs it by itself, using the exact same verified download and automatic roll-back — and a release that has just failed is left alone for two days, so a bad build can never get stuck retrying. Leave Auto-update off to keep updates strictly one-click. It's per device, so you can switch it on for one machine first and enable the rest once you're happy.
The cloud dashboard also has two self-service shortcuts in its header: Get help (describe a problem — if a device is selected it attaches its status and asks the device to send a sanitised diagnostics bundle automatically, so you never have to dig out logs; secrets are stripped on the device first) and My account (your plan, cloud-access date, device usage and invoice history with downloadable PDFs). New accounts also get an offline email alert switched on by default so you're told if a device goes quiet.
The Configuration card also keeps a Config history: a rolling set of daily snapshots of each device's settings. If an edit doesn't work out, you can view a previous configuration and restore it in one click — your device's cloud token and LAN passwords are always kept as they are, never overwritten.
Plans & subscription
Cloud Relay is a subscription service set up personally — there is no self-service signup or card payment. Getting started: (1) contact hello@multilarm.com (or the website contact form) with how many devices you run; (2) pay by bank transfer or invoice; (3) your account is created and a temporary password is emailed to you (valid 7 days); (4) sign in at https://multilarm.com/cloud — you'll be asked to set your own password immediately, then the dashboard unlocks. Plans are device-count tiers — one price per tier covers a set number of devices (1, up to 3, up to 6, up to 15 or up to 50) plus an escalating support level, billed monthly or yearly; each account has a device limit matching its tier. Current prices are at https://multilarm.com/pricing.html, a calculator that totals a whole site over three, five or ten years — licence, hardware, commissioning, optional cloud and optional maintenance — is at https://multilarm.com/cost-calculator.html, and the licence agreement those prices are sold under — including what personal and sole-operator use covers — is published in full at https://multilarm.com/terms.html. Renewals work the same way — arrange the next payment and your access date is extended — and you never need to watch the calendar yourself: about a month before your access date a renewal invoice (PDF) is emailed automatically, with polite reminders as the date approaches, and every confirmed payment is answered with an emailed receipt. If a payment is late you get a clear grace-period notice well before anything pauses. If a subscription lapses, the device keeps running all of its local schedules, bells and emergency triggers unchanged — only remote control, app push and email alerts pause; the dashboard shows a renewal banner (with a grace window after the end date), and the device's poll responses carry "subscription":"expired". Accounts created before the subscription system launched stay free.
What the dashboard offers
Multi-device management
Each device is a card with live status and its own controls — add, rename, remove.
Batch operations
Select several devices to skip/test/set-volume at once, push one config to many, or compare two configs side-by-side and selectively copy fields.
Voice, Speak & PTT
Record/upload & broadcast, push spoken text, and live browser-mic Push-to-Talk to any device.
Cloud config editor
Edit a device's full configuration from the cloud — without exposing its Web Remote to the internet.
Email alerts
Per-user alerts on device-offline, missed alarm or missing audio — by email (SMTP), phone push, webhook, or free Telegram.
Audit log viewer
A central ledger of who did what, to which device, from where.
Roles: Admin vs Operator
Admins own devices and manage everything. Operators are invited by an admin (via a one-time setup link) to perform granted tasks — transmit audio, test alarms — but cannot add/remove/rename devices, edit configs or manage alerts. Operators only see their own actions in the audit log.
Security
- All traffic is HTTPS; device tokens are 64-char cryptographically-random hex.
- Passwords hashed with bcrypt (cost 12); login rate-limiting; CSRF protection; prepared SQL;
httponly/secure/SameSite=Strict cookies.
- Commands expire after 60 seconds if the device is offline.
Use both at onceRun the LAN Web Remote (fast, local) and the Cloud Relay (anywhere) simultaneously. Keep the device token secret — if it leaks, remove the device and re-add it for a fresh token.
Remote Control
Android app
A native Android app gives you the whole cloud dashboard on your phone — built for big, clear buttons and large readable text, so you can run an alarm, speak an announcement or go live on the PA one-handed.
Installing & signing in
- Install it from the Google Play link the operator gives you (it ships on a private internal track), then open Multilarm.
- Sign in with the same username and password you use on the cloud dashboard. Turn on Keep me signed in to skip the login next time.
- Your devices appear on the home screen with an online dot. Tap one to control it. Admin logins see everything; operator logins see only the day-to-day controls on the devices shared with them.
What you can do
▶
Status & quick actions
See what's playing and the next alarm; Skip, Test Alarm, Test Radio, and a big Volume slider.
📅
Schedule
The upcoming alarm times resolved from the device's loaded config, for the next few days.
🗣
Speak text
Type a message and the device says it aloud; pick a voice and speed for that announcement.
📢
Voice broadcast
Record a clip on this phone's microphone (or upload an audio file from the phone), preview it in your ear, then transmit it through the device's speakers. The clip is captured on the phone, so it works even when the device itself has no microphone. To talk live instead of sending a recording, use Live PA.
📁
Audio files
Browse, upload and delete the device's audio (with a guard before deleting TTS words).
🎙
Live PA
Talk live through the device's speakers from your phone's mic. Sessions end after 5 minutes.
Owner-only: the full Configuration editor, TTS word-library fill, External Triggers, Batch actions across devices, Email alerts, Operator management, Audit log, and adding, renaming, removing and revealing the pairing token of devices.
Multilarm Box hardware is on the device's own panelIf a device is a
Multilarm Box, its physical bell, emergency button and zone mutes are operated from that device's
local Web Remote Hardware panel (on the same network as the box), not from the cloud — so the phone app and the cloud website don't show those controls. Everything else (alarms, announcements, voice broadcast, live PA, files, config) works remotely as normal.
Handy extras
- Push notifications — get a notification on your phone within seconds when a device has a problem: it goes offline, misses an alarm, is missing an audio file, runs low on disk, or has a speaker/audio-device fault. Turn it on under Email alerts → Phone notifications (owners): tick which events should push, switch on Notify this phone, allow notifications when prompted, then use Send test notification to confirm. It works even when the app is closed, and tapping a notification opens that device. Runs alongside email/webhook alerts. A lightweight on-device offline check still runs as a fallback on phones without Google Play services.
- Telegram alerts (free) — get the same offline / missed-alarm / missing-audio / disk / audio-device alerts in a Telegram chat, with no SMS provider or extra cost. Under Email alerts → Telegram (owners): create a bot with
@BotFather in Telegram to get a bot token, send your new bot any message, then get your numeric chat ID from @userinfobot. Paste both in, tick Send alerts to Telegram, and press Test Telegram to confirm. Add several chat IDs (comma-separated) to alert a whole team; group and channel IDs are negative. Fires alongside email, webhook and phone push for any enabled event.
- Slack, Microsoft Teams or Discord (free) — the same alerts can land in a chat channel your team already watches. Create an incoming webhook in your own workspace and paste the URL under Email alerts → Slack / Microsoft Teams / Discord (owners); tick Post alerts to a chat channel and press Test chat to confirm. Slack: Apps → Incoming Webhooks → Add to Workspace. Teams: the channel’s Workflows → Post to a channel when a webhook request is received, or a classic connector. Discord: Edit Channel → Integrations → New Webhook. Multilarm installs no app and holds no workspace token — just the URL, stored encrypted and never shown again, because anyone holding it can post into your channel. The platform is detected from the URL rather than a dropdown, so a Teams URL can never be sent a Slack-shaped message (which Teams accepts with a success code and then displays as nothing). Fires alongside email, webhook, push and Telegram for any enabled event.
- Visual display (wall/TV) — turn any cheap TV, tablet or Pi-with-monitor into a full-screen display for a device: a big clock, the next prayer or bell with a live countdown, the Hijri date (mosque mode), an optional scrolling notice, and an automatic full-screen emergency takeover when the device's emergency is active. Set it up under Dashboard → Display (owners): pick the device, choose Mosque / School / Factory mode and a theme, optionally add a notice ticker, then open the public link on the screen (bookmark it full-screen / kiosk). The link is read-only — it can show status but never control the device — and you can rotate it any time to revoke old screens.
- Digital signage you already own — if the building already runs signage (Xibo, Yodeck, ScreenCloud, Rise Vision, OptiSigns and anything else with an RSS or ticker widget), it can carry Multilarm without a second screen or a second player. The Dashboard → Display card shows a signage feed URL beside the display link: paste it into the player’s RSS/ticker widget and it shows an active emergency first, then your notice text, a warning if the device has stopped reporting, and the next scheduled event. Add
&format=json or &format=txt for players that prefer those. It uses the same read-only token as the display link, so Rotate link revokes both at once. A player that can show a web page does not need the feed — give it the display link instead and it gets the full screen.
- Notify people (mass notification) — alert a whole list of people at once across email, phone push and Telegram, with an “I’m safe” acknowledgement link so you can see who has responded. Under Dashboard → Notify People (owners): add recipients (email and/or Telegram chat ID, optional group tags), then compose a message, pick the channels and send. A live board shows sent / failed / acknowledged counts. Email uses your Email Alerts SMTP settings, Telegram uses the bot token there, and push goes to your own registered phones — no SMS provider needed.
- Big text everywhere — increase your phone's system font size and the whole app scales with it.
- In-app help — a plain-language guide to every screen lives under the Help icon.
Same account, same rulesThe app uses your existing cloud account and respects the exact same permissions as the website — operators can't reach owner-only tools, and every action is audited.
Remote Control
Multilarm Hub
Running several zones on one machine? Promote any instance to a Hub and get a single dashboard that controls every other instance on the host.
Turning it on
- At launch:
Multilarm.exe --hub (or -H).
- At runtime: Ctrl+H to promote, Ctrl+Shift+H to step down. While off, the
/hub route returns 404.
How instances find each other
Every instance writes a heartbeat file every 15 seconds to %LOCALAPPDATA%\Multilarm\instances\<id>.json (id, name, port, host, credentials, active config, timestamps). The Hub enumerates that folder, keeps entries seen within the last 60 seconds, and renders one card per live peer. Files are removed on clean shutdown.
What the Hub dashboard does
- Lists every live peer with status (playing / radio / volume / uptime / active config).
- Per-peer controls: volume slider, Skip, Test Alarm, Test Radio, an Active Config dropdown (rewrites that peer's
AlternateConfig so its midnight evaluator picks the chosen file), and an "Open" shortcut to that peer's own dashboard.
- All peer calls go through the Hub as a reverse proxy, which injects each peer's Basic Auth server-side — so credentials never reach the browser.
Additive & safeHub mode changes nothing about an instance's own behaviour — it keeps running its alarms, radio and cloud relay exactly as before. Only one hub per host is needed.
See also the System Tray companion, which uses the same heartbeat folder to surface running instances on Windows.
Integration
External Trigger API
Let other systems fire Multilarm: a nurse-call button, a school timetable engine, a building-management system, a factory line controller. Three transports converge on the same action set.
In plain termsA
trigger is an instruction that arrives
from the outside and makes Multilarm do something — play a clip, stop, change volume — with no person standing at the device. The point is to plug Multilarm into equipment a building already owns. The same five actions can arrive over three different "roads" (called
transports), and you pick whichever road your other system already speaks: a direct web request on the local network, the same request bounced through the cloud, or a message bus called MQTT. This is the mirror image of
webhooks, which send events the
other way — out of Multilarm into your systems.
The action set
All transports can request: play-random, play-file, stop, volume, and transmit-upload (stage an audio clip then broadcast it at Priority 1).
Three transports
Cloud HTTP
POST https://multilarm.com/cloud/api/trigger.php with JSON {key, vars?}, or .../trigger-audio.php (multipart, audio body). Authenticated with a Bearer token minted in the dashboard's External Triggers card. Supports rate-limiting and idempotency via request_id.
LAN HTTP
POST /api/trigger and POST /api/trigger-audio on the Web Remote — same payload shape as the cloud endpoints (just swap the URL). Always exposed when the Web Remote runs, but gated entirely by the tokens in your trigger file: no tokens defined → every request gets 401.
MQTT
What is MQTT? MQTT is a lightweight messaging system that's the common language of building automation, smart buildings and the "Internet of Things" — nurse-call panels, sensors, BMS controllers and PLCs very often already speak it. Instead of devices calling each other directly, they all connect to a shared post office called a broker (e.g. Mosquitto or HiveMQ). A device publishes a short message to a named topic (think of a topic as a labelled mailbox like multilarm/trigger), and anyone who has subscribed to that topic receives it. Multilarm acts as a subscriber: it listens on your broker for trigger messages and fires the matching action. If your other equipment already publishes to a broker, this is usually the least-effort way to connect it.
Mechanically: the MQTT bridge subscribes to <base>/trigger (JSON) and <base>/trigger-audio/<key> (raw bytes) on your broker (opt-in via MqttEnabled + MqttHost). It publishes heartbeats to <base>/status and per-fire envelopes to <base>/events (so your dashboards can confirm it's alive and see each fire). The broker provides connection identity; Multilarm's client_id is the stable multilarm-<InstanceId>.
Tokens & profiles
The LAN/MQTT surfaces are governed by a local <configBaseName>.triggers.json file (override with TriggerConfigPath): trigger profiles, bearer tokens, a per-token key allowlist and a per-minute rate limit. The cloud surface is backed by dashboard-managed tokens. Every fire is audited.
Example useA ward's nurse-call panel POSTs a key to /api/trigger; Multilarm plays the matching announcement folder over the corridor speakers — no human at the device.
Integration
Outbound Webhooks
The mirror image of triggers: Multilarm pushes events to your systems as they happen, so dashboards, logs and automations stay in sync.
Events
Multilarm fires HTTP webhooks for key events, including:
tts.spoken — an announcement was spoken
- alarm-fired events
ptt.started / ptt.stopped — live-mic sessions
radio.stream-fallback — an online stream dropped and the device fell back to the local library
bell.fired — a Multilarm Box bell pulsed the relay (payload: time + duration)
emergency.triggered / emergency.reset — the emergency broadcast started or cleared (payload: source — GPIO button or WEB panel)
Point these at your logging stack, a chat channel, a home-automation hub or a monitoring dashboard. Webhook dispatch runs before the engine-specific TTS path, so receivers learn about a spoken event regardless of which TTS engine produced it.
Configuration
OutboundWebhooksJSON arraydefault (empty)
A JSON array of endpoint objects. Empty or [] turns the feature off. Each endpoint takes a Url plus optional fields:
Url — the receiver. Must be HTTPS, or a plain-HTTP address on a private/LAN IP.
Secret — when set, each POST is signed (HMAC) so the receiver can verify it came from you.
Events — an array of event names to send to this endpoint (e.g. ["alarm.fired","emergency.triggered"]). Omit to receive every event.
Headers — extra HTTP headers to add (e.g. an API key).
TimeoutMs / Retries — per-endpoint delivery tuning.
[{"Url":"https://hooks.example.com/multilarm","Secret":"s3cr3t","Events":["alarm.fired","tts.spoken"]}]
Edits are hot-reloaded — an unchanged endpoint keeps its in-flight delivery queue across config saves. The easiest way to build this array is the Config Generator, which writes the JSON for you.
Cloud alerts vs webhooksThe
Cloud dashboard can
also send email/webhook alerts for offline/missed-alarm/missing-audio conditions — that's a separate, server-side safety net. Device webhooks are real-time and local.
Integration
SIP Paging
Pick up any desk phone, dial an extension, and speak through the PA. SIP paging connects Multilarm to the phone system you already have, so making an announcement takes no special hardware, no app and no training.
This is a paid add-onSIP paging needs an active Cloud Relay subscription with SIP enabled on your account, plus a separate Multilarm SIP companion that you download from My Account in the cloud dashboard. It is not part of the installer, and it is not in the winget or Chocolatey packages. That is deliberate: the licence for the underlying SIP library restricts where the software may be used, and per-account distribution is how we keep to it. Settings 76–83 have no effect on a device that does not have the companion installed.
How it behaves
A page is treated as an announcement like any other, which means it obeys the same priorities you already rely on:
- The virtual radio ducks for the duration and resumes afterwards.
- An alarm or emergency broadcast still wins. If one is already playing, the call is answered but the caller is not heard until it finishes — a phone call never cuts off an adhan or an evacuation message.
- Only one live source at a time. If someone is already using Push-to-Talk from the dashboard, an incoming page is refused as busy, and vice versa.
- Zone routing applies as configured, so a page reaches the same speakers your announcements do.
Setting it up
- Ask whoever runs your phone system to create an ordinary extension for Multilarm — it needs no special privileges, only the ability to receive calls. Give it an obviously non-human name such as
multilarm-pa.
- Download the Multilarm SIP companion for your platform from My Account in the cloud dashboard, and install it on the device that should answer the extension.
- Fill in settings 76–83 in the Configuration Generator (section “SIP paging”), or in the cloud config editor.
- Dial the extension from a handset and speak. If nothing is heard, run the companion with
--check: it prints the configuration, whether it can reach Multilarm, whether the account is entitled, and who is on the allow-list.
Who is allowed to page
SipAllowedCallers (80) decides which extensions may make an announcement — 201|202, a wildcard like 30*, or * for anyone.
An empty list rejects every callThis is the opposite of how the other settings behave, and it is on purpose. Something that can speak to an entire building should not answer the world simply because a field was left blank. You have to say who is allowed — even if what you mean is “everyone” (*).
Remember that caller ID comes from your phone system, and a PBX that accepts calls from outside can be made to present any number at all. If external calls can reach this extension, keep the list tight and ask your phone provider to block inbound calls to it.
Firewalls and ports
The companion listens on UDP port 5060 by default (setting 83, SipLocalPort). Keep it fixed unless something else on the device already uses that port — your firewall rule needs an address that stays valid, and some phone systems send calls to a set address and port rather than to whatever contact the device last registered from. Setting it to 0 lets the operating system pick any free port, which changes on every restart and quietly invalidates the rule you wrote.
Field reference
SipServerhost[:port]default (empty) = SIP paging off
Address of the phone system (PBX) this device registers with. Port defaults to 5060. Empty means SIP paging is off — the same presence-is-enabled convention as RelayPort. Has no effect on a device without the Multilarm SIP companion installed.
SipUsernameStringdefault (empty)
The SIP account Multilarm signs in as — an ordinary extension with no special privileges, only the ability to receive calls. Give it an obviously non-human name such as multilarm-pa so its purpose is clear in the phone system's logs.
SipPasswordString (secret)default (empty)
Password for SipUsername. Treated as a site secret like WebRemotePassword and MqttPassword: stored in the device config, never returned by the status or config APIs (a read shows ********), and stripped from the configuration snapshots kept in the cloud. Restoring an older snapshot leaves the live password untouched rather than blanking it.
SipExtensionStringdefault (empty)
The number staff dial to make an announcement, e.g. 700. This is documentation rather than routing — the call reaches Multilarm because your phone system sends it to the SipUsername account. Recording it here lets the dashboard, the diagnostics report and the commissioning certificate tell an engineer what to dial without digging through the PBX.
SipAllowedCallersListdefault (empty) = reject every call
Which extensions may page. Separate with | or ,; a single trailing * is a wildcard — 201|202, 30*, or * for anyone. Empty rejects every call, deliberately the opposite of the other settings: something that can speak to an entire building should not answer the world because a field was left blank.
SipAutoAnswerTrue/Falsedefault True
Left True, a permitted call is answered immediately and the caller is live on the PA. Set False and every inbound call is refused — a convenient temporary stop during an exam or an event without clearing SipServer and losing the rest of the configuration. Being answered is not the same as being heard instantly: if an alarm or emergency broadcast is playing, the page waits for it rather than cutting it off.
SipRegisterExpirySecondsInteger (seconds)default 120
How long each registration with the phone system lasts before renewal. Two minutes suits almost every site; change it only if your phone system asks for something specific. Shorter notices a dropped registration sooner but adds traffic to the PBX; longer is quieter, but after a phone-system restart there can be a gap where a call to the paging extension goes nowhere. Values outside 30–3600 are clamped.
SipLocalPortInteger (port)default 5060
The UDP port the SIP companion listens on. Leave at 5060 unless something else on the device already uses it — see Firewalls and ports above for why 0 (OS picks a free port) is a poor choice.
If the internet goes down
Paging keeps working. The companion confirms your subscription when it starts and roughly twice a day after that, and if it cannot reach the cloud it carries on for a fortnight on the last confirmed answer. Only a definite “this account no longer has SIP” stops it. An outage at our end must never become an outage in your building — the same principle as the rest of Multilarm, which runs its whole schedule with no internet at all.
What it does not do (yet)
Paging is one-way: the caller speaks, the building listens. Multilarm never sends audio back down the call, so the paging extension cannot be used to listen in on a room. Two-way intercom is a separate feature and is not part of this.
Configuration · Listening
Room Listening & Recording
Four settings (84–87) that let the device hear the room as well as speak to it. They cover two genuinely separate things, and you can use either without the other.
Both are off until you turn them onA brand-new install listens to nothing and records nothing. Both also need a microphone: on a device with no capture input every setting on this page is inert, and the device will tell you so in Diagnostics rather than failing quietly.
The two things, and why they are not the same
Listening measures. Every twenty seconds it captures a few seconds of room audio, works out how loud it is, checks whether it matches the standard fire-alarm pattern, and deletes the clip before analysing it. What survives is a handful of numbers. Nothing is written to disk, nothing is sent to the cloud, and there is no control anywhere that plays any of it back — not because playback is disabled, but because the audio no longer exists.
Recording keeps. It writes WAV files to disk that persist, can be copied, and can be listened to by anyone with access to the device. That is a different thing with different consequences, so it is a separate setting you have to turn on separately. Turning on listening does not start recording, and vice versa.
The settings
| # | Setting | Default | What it does |
| 84 | ListenEnabled | False | Measure room loudness and detect the fire-alarm pattern. Keeps no audio. |
| 85 | AdaptiveGainEnabled | False | Raise announcements in a noisy room, lower them in a quiet one. Needs 84. |
| 86 | RecordScheduleRules | empty | When to record the room. Empty means never. |
| 87 | RecordRetentionDays | 14 | Delete recordings older than this. 0 keeps everything for ever. |
What listening is actually for
- Matching announcements to the room — the measurement feeds
AdaptiveGainEnabled, so a message set for an empty hall stays audible once it fills up, and does not startle anyone at six in the morning.
- Automations — the room is louder than / quieter than triggers become available, so you can act on a room going quiet or getting noisy.
- Hearing a smoke alarm nobody else is there to hear — an unstaffed site, out of hours. This is pattern matching on the ISO 8201 / NFPA 72 “T3” temporal code, not a general sound classifier: it wants three short bursts and a pause, so a continuous tone at the same pitch does not trigger it, and neither does the same rhythm at a very different pitch.
It notifies, it does not decideDetection raises an alert. It does not evacuate a building, silence anything, or fire a scenario on its own. Treat it as a second pair of ears on an empty site, never as a substitute for a certified fire-detection system.
Adaptive gain, and the line it will not cross
Adaptive gain moves the volume of scheduled announcements, the virtual radio and ambience within a bounded range around your configured Volume, and returns to it as the room settles. The bound is why a single slammed door cannot drive the PA to maximum.
It never quietens an alarm or an emergency broadcastAnything at alarm or emergency priority plays at its configured volume no matter what the room sounds like. A system that could talk itself down to a whisper during a fire would be worse than no system at all, so this is enforced in the audio engine rather than left to configuration.
With ListenEnabled off, adaptive gain has no measurement to work from and changes nothing at all. It does not warn about this, so if volumes are not moving, check 84 first.
Scheduled recording
RecordScheduleRules uses exactly the same grammar as the radio and quote schedules — nothing new to learn:
| Rule | Meaning |
09:00-10:00,w=mo-fr | The ten o’clock hour, weekdays only |
09:00-10:00,w=mo-fr;14:30-15:00 | …and an afternoon slot every day as well |
00:00-00:00,m=* | Continuously |
Files land in a Recordings folder beside your config, named by start time (rec-20260824-090000.wav), and are reachable through the Audio File Manager like any other audio.
- Recording never interrupts playback. It only reads the microphone; announcements, alarms and emergency broadcasts use the output and are untouched by it.
- It does yield the microphone. There is one capture device and four things want it, ranked by whether a person is waiting: Push-to-Talk, then Voice Broadcast, then scheduled recording, then room listening. If an operator starts talking mid-recording, the recording stops and the part already captured is kept — a truncated recording is more use than none. Recording in turn takes the microphone from listening.
Retention, and the one setting that can fill a disk
The tidy-up runs whenever nothing is being recorded, so a device left alone keeps its own disk in order. RecordRetentionDays = 0 means keep everything for ever, and it is honoured exactly — which is worth doing the arithmetic on before you choose it. Continuous recording at the default quality is roughly 0.3 GB a day, so a year is over 100 GB. On an SD card that ends in a full disk, and a full disk is a device that cannot write logs, cannot update, and may not start. If you need indefinite retention, copy the files somewhere else on a schedule instead.
Before you turn listening on
A device that listens to a room is something the people in that room are generally entitled to know about — in a workplace, in many places, something you are required to tell them. That it keeps no audio is a good answer to give them, not a reason to skip the conversation. Recording raises the bar again, because the files persist. Where you are and who is in the room decide what is permitted; the software cannot make that judgement for you, and does not try.
Setting it up
- Plug in a microphone and set
RecordDevice (44) to its index — press Ctrl+O at the console to list the inputs.
- Turn on Listen to the room (84) in Settings, or field 84 in the Configuration Generator, section Room listening & recording.
- Leave it a few minutes, then check the reported room level looks plausible before relying on it.
- Only then turn on adaptive gain (85), so you can tell the two apart if something looks wrong.
- Recording is independent: set 86 when you want it, and set 87 to a retention you can actually store.
Remote Control
Audio File Manager
Upload, browse and delete the audio that drives your schedules — from the browser, over LAN or cloud — with no SSH or RDP. Built for commercial operators who can't (and shouldn't) log into the box.
What you can manage
The File Manager exposes named roots derived live from your config: each AlarmPath folder, plus TTS, Radio, Ambience, TTSChime and Quote. You can list a root (and its subfolders), upload new files, and delete existing ones.
Where it lives
- Web Remote: a File Manager card, backed by
GET /api/files/roots, GET /api/files, POST /api/files/upload, POST /api/files/delete.
- Cloud: the same operations via the
files-roots / files-list / files-upload / files-delete commands; uploads stage on the cloud and the device fetches them on its next poll.
Rules & safety
- Allowed extensions:
.mp3 .wav .ogg .flac .aif .aiff. Upload cap 16 MB.
- Deleting a TTS word requires an explicit
confirmTts:true — removing word audio breaks announcements, so it can't happen by accident.
- Path safety rejects directory traversal and any symlink/junction segment.
- Every action is written to two ledgers: cloud
audit_log (intent) and the device's Multilarm.audit.log (execution).
- Roots are addressed by stable label, read live per request — so config changes are picked up instantly.
Companions
System Tray Companion (Windows)
A small Windows tray app that auto-discovers every running Multilarm instance on the machine and gives you a right-click menu to each one — handy when you run several zones.
- Discovers live instances via the same
%LOCALAPPDATA%\Multilarm\instances\*.json heartbeat folder the Hub uses.
- A per-instance submenu talks to each device's loopback Web Remote.
- Single-instance guarded — only one tray app runs at a time.
It's a standalone binary; install it on the host machine where your instances run.
Deployment
Multi-Zone Deployments
Run two or more independent Multilarm instances on one machine, each driving its own zone — room, speaker set or output device — with its own calendar, volume and audio chain.
The building block: --config
Each instance loads its own config file via --config <path>. Give every zone:
- Its own config file (
--config).
- A distinct
PlaybackDevice (and RecordDevice if it uses voice features).
- A distinct
WebRemotePort (two servers can't share a port).
- A distinct
CloudDeviceToken (each zone is its own device in the cloud).
- A meaningful
InstanceName (e.g. "Main Hall PA") so the Hub grid is readable.
- Optionally its own
MirrorDevices to fan that zone across several speakers.
"C:\Program Files\Multilarm\Multilarm.exe" --config zones\livingroom.xml
"C:\Program Files\Multilarm\Multilarm.exe" --config zones\bedroom.xml
"C:\Program Files\Multilarm\Multilarm.exe" --config zones\kitchen.xml
The processes run independently — they don't share state and don't need to know about each other. The only cross-instance coordination is making sure ports and devices don't clash.
Manage them togetherPromote one instance to a
Hub for a single fan-out dashboard, or use the
tray companion. Mirroring (same audio everywhere) and zoning (different audio per room) combine freely.
One amp, two zones?If your zones share a single PA amplifier with zone mute inputs (rather than separate output devices), a single instance can do it with relay-muted
zone targeting instead — see
Multilarm Box Hardware.
Deployment
Multi-Room Audio
Several Multilarms, one sound, played at the same moment. One device is the sending device; the others are rooms that follow it while keeping their own schedule, their own emergency button and their own settings.
In plain termsThis is the opposite of
multi-zone. Zones let different parts of a building hear
different things. Multi-room lets them hear the
same thing, in step — a school bell that lands together down a long corridor, a shop playing one radio station across three floors, a warehouse announcement that doesn't echo out of sync between two ends of the building.
What gets sent
Everything the sending device plays: scheduled items, spoken announcements, the Virtual Radio, quote and ambient audio, a live microphone. It is a copy of that device's finished output, taken after mixing and after volume, so what a room plays is exactly what the sending device is playing — no second schedule to keep in agreement.
Turning it on
Six settings, config Group 21 — Multi-room audio (fields 88–93), all of them off out of the box. A device that has never been told about multi-room opens no port and behaves exactly as it always has.
| Setting | On the sending device | On a room |
RoomsRole | Server | Room |
RoomsPort | Same number on every device (default 7451). Discovery uses the next port up, 7452/UDP. |
RoomsGroupKey | Same word on every device, or blank to let any Multilarm on the network join. |
RoomsServerHost | ignored | Blank finds the sender automatically; fill it in across subnets or where UDP broadcast is blocked. |
RoomsBufferMs | The deliberate delay, same on every room (default 400). |
RoomsName | — | A label for the logs, e.g. Kitchen. Blank uses InstanceName. |
You need exactly one sending device. Two on one network is not something the software can detect for you: each room joins whichever it finds first, and the building ends up playing two things. Changing the role takes effect on restart, because switching it live would mean taking the audio path apart underneath whatever is playing.
A room keeps its own life
Joining a group adds a source of sound; it never takes the local one away. A room still runs its own schedule, still answers its own emergency button, still serves its own Web Remote. If it loses the network it goes quiet rather than guessing — two ends of a building announcing different things is worse than one end announcing nothing — and rejoins by itself when the network comes back.
What “in step” honestly means
Rooms are matched to each other. Each room measures the round trip to the sending device, corrects its own clock against it, and plays each chunk at the moment the sender stamped it. Two rooms therefore agree to within roughly the sum of their clock errors, and each error is bounded by half a round trip — a few milliseconds on an ordinary wired or good wireless network.
What is not corrected is the delay your own sound card or amplifier adds after Multilarm hands the audio over, which differs from one piece of hardware to the next. Identical hardware in every room gives the tightest result. We publish no single figure because the number belongs to your network, not to ours — so the software measures it for you:
Multilarm --rooms-probe # find the sender, measure, print JSON
Multilarm --rooms-probe 192.168.1.50:7451 30 # that sender, for 30 seconds
It joins as a silent observer — it deliberately plays nothing, because a measurement that broadcasts into a building is one nobody runs twice — and prints one line: clockOffsetUs, bestRttUs, arrivalJitterUs, how many chunks arrived after their play moment at the current buffer, and roomToRoomBoundUs, the honest bound on how far two rooms can be apart. Exit code 0 measured, 2 could not reach the sender, 3 refused.
Choosing the buffer
400 ms (default) — comfortable on wired or good wireless, unnoticeable for scheduled sounds, announcements and music.
- Raise towards
800–1500 if a room stutters or drops out. That is almost always a weak or busy wireless link, and a bigger buffer is the correct fix.
- Lower towards
150–250 only on wired networks and only where the delay genuinely matters, such as a live microphone whose operator can hear a distant speaker while talking.
Set the same value everywhere: two rooms with different buffers are, by definition, out of step by the difference.
Checking it is working
Press Ctrl+I on either device for a one-line summary, or read rooms in GET /api/status on the Web Remote — the sending device reports how many rooms are following, a room reports which device it follows, its clock offset, round trip, and how many corrections and gaps it has seen.
Local network onlyThe stream is uncompressed audio and is not meant to cross the internet. The group key is a check on who may join, not encryption: the audio travels the local network in the clear, deliberately, because it is programme audio the building is already broadcasting aloud through its speakers. If the audio itself must be private, multi-room is not the feature to use.
Concepts & Configuration
Output Tuning — EQ, Delay and Limiter
Per-output tone correction, speaker time-alignment and peak limiting, built in. This is the work a separate rack DSP is normally bought and wired in for, and it is the difference between announcements that are merely loud and announcements a room can actually understand.
In plain termsThree jobs. Tone corrects the speakers you were given rather than the ones you would have chosen. Delay holds the near zone back so a word from the far zone does not arrive as an echo behind it. Limiter stops peaks from clipping, which is how horn drivers die. Each output — the main one and every mirror — gets its own chain, because a corridor and a hall are not the same room.
It is off, and off means untouched
Everything here is off out of the box, and when it is off the audio is bypassed sample for sample — not passed through a chain set to neutral. An install that never opens this section sounds bit-identical to one built before the feature existed, and costs nothing per sample. That is deliberate: nobody should discover new processing on a working system they did not ask to change.
This is not a safety functionThe chain can attenuate, delay and limit audio. It can never start, stop or silence anything, and no setting in it can prevent an alarm or an emergency broadcast from sounding. Multilarm is not fire-alarm equipment.
The chain, in order
Signal passes through in this order, per output: pre-gain → up to eight tone filters → delay → limiter → hard ceiling. The ceiling is always there, whatever the limiter is set to, and when it has to act it is counted rather than hidden.
One ordering detail worth knowing: the processing runs after the multi-room tap. A room listening over a Multilarm link therefore hears the programme audio, not this device's amplifier correction and speaker delay — which would otherwise be corrections for a building it is not in.
DspEnabledTrue / Falsedefault False
The master switch for the whole chain, on every output. Takes effect immediately — no restart.
DspGainDb−24 to +24 dBdefault 0
A fixed trim applied first. Use it to match outputs to each other, not to set overall volume — that is Volume and the volume profile. The everyday case is a second amplifier that is simply louder than the first: trim it a few dB and the two zones finally sound like one building. Every 6 dB is roughly a halving or a doubling. Boost is available but gives the limiter more to do; if you need a lot of it, the amplifier's own gain control is the honest fix.
DspEqBandsup to 8 bandsdefault (empty)
Each band is type,frequency,gain,width, bands separated by semicolons.
| Part | Values |
type | peak, lowshelf, highshelf, lowpass, highpass |
frequency | 20 Hz up to just under half the sample rate |
gain | −24 to +24 dB. Ignored by lowpass / highpass, which cut rather than tilt |
width | The Q. 0.707 is the gentle textbook default, 3 is surgical, 18 is a notch |
# A starting point for speech through ceiling speakers:
highpass,120,0,0.707;peak,3150,4,1.2;highshelf,8000,-6,0.707
That drops the rumble the speakers cannot reproduce anyway, lifts the consonant range that carries intelligibility, and takes the hiss off the top. These are the standard cookbook filters every measurement rig implements, so a curve worked out on other equipment behaves here as you expect.
A band the device cannot read rejects the whole spec and clears the EQ, with the reason written to the log. Half-applying it would be worse: a typo that quietly leaves the high-pass off a 100 V line is exactly the sort of fault that is discovered by ear, in front of a room.
DspOutputDelayMsbar-separated ms, 0–500 eachdefault (empty)
Holds each output back so speakers at different distances reach a listener together. The order is positional: entry one is PlaybackDevice, and each entry after it lines up with MirrorDevices in the order listed there. A short list is not an error — outputs it does not mention are simply not delayed.
# main output on time, second held 35 ms, third held 70 ms
0|35|70
Sound covers roughly a metre every 3 ms, and you delay the sound that would otherwise arrive first. A zone 20 m closer to the listener than another wants about 60 ms on the near zone. Getting this right is the difference between a corridor that echoes every word and one that just sounds loud.
DspLimiterEnabledTrue / Falsedefault False
Catches peaks that would otherwise clip and rides them down instead. A clipped waveform carries far more high-frequency energy than the speech it came from, and that energy arrives at the tweeter or compression driver as heat — on a 100 V line feeding fixed installation speakers, a limiter is cheap insurance for hardware nobody wants to get a ladder out for.
It grabs in about 1.5 ms and lets go slowly, so one loud syllable does not duck the rest of the announcement behind it. Measured on the bench holding a 1 kHz tone 2.9 dB down, the distortion it adds is about 0.01 % THD — inaudible, and orders of magnitude below what clipping the same peak would have produced.
DspLimiterThresholdDb−30 to 0 dBFSdefault −3
Where the ceiling sits, in decibels below full scale. -3 catches genuine peaks and leaves everything else alone. -6 is a firm limit for a delicate speaker run or an amplifier you know is undersized. Below about −12 the limiter starts squashing normal speech rather than protecting against peaks — if you need that much, turn the amplifier down instead.
DspLimiterReleaseMs10–2000 msdefault 120
How long the limiter takes to let go once the loud passage has passed. Under about 50 ms sounds louder and busier and can pump audibly between words; over about 400 ms sounds smooth but leaves the whole announcement quieter after a single bang. 120 suits speech and music alike, and this is the one control here worth leaving alone until something specifically bothers you.
Checking it is working
The diagnostics report, per output, the worst gain reduction the limiter actually applied and how many periods hit the hard ceiling, since the last read. That distinguishes a threshold doing its job occasionally from one fighting the material all day — and a non-zero clip count means something upstream is too hot, whatever the limiter is doing about it.
Every rejected EQ spec and every failed setting is written to Multilarm.error.log with the prefix MaOutputDsp:.
Deployment
Play to This Device
Turn a Multilarm into something a phone, tablet or PC on the same network can send sound to — a playlist, a recorded message, a radio app — without anyone touching the Multilarm itself. It appears in the “play to” or “cast” list of any app that speaks DLNA.
In plain termsThe building already has speakers and an amplifier driven by Multilarm. This lets a member of staff play something through them from their own device, for the length of a lunch break or a school fair, and then stop. It is the opposite direction of travel from
multi-room: there, Multilarm sends; here, Multilarm receives.
What it is, exactly
Multilarm advertises itself on the local network as a UPnP AV / DLNA MediaRenderer (a DMR). Anything that can send to one — Android's built-in cast picker in many media apps, VLC, BubbleUPnP, foobar2000, Windows' “Cast to Device”, Kodi, Plex, most NAS media servers — will see it by name and can start, pause, resume, stop and set the volume.
What it deliberately is not
| Protocol | Supported | Why |
| DLNA / UPnP AV | ✔ Yes | Published open specification, implemented here from that specification. Widest reach of the four by a distance. |
| AirPlay 2 | ✘ No | Every working open implementation is GPL-licensed, and the official route needs an Apple MFi licence. Neither is compatible with how Multilarm is licensed. |
| Bluetooth A2DP | ✘ No | The Linux Bluetooth stack it would need is GPL-licensed. See below for the route that does work. |
| Spotify Connect | ✘ No | Requires a commercial Spotify SDK licence. |
Those are honest answers rather than a roadmap. Where a phone must reach the speakers over Bluetooth, pair it with the operating system on the machine running Multilarm and let the paired audio arrive on the sound card's line input — Multilarm then treats it as an ordinary input, and nothing in it needs a Bluetooth stack of its own.
Turning it on
Four settings, config Group 22 — Play to this device, all off out of the box. A device that has never been told about this opens no port and advertises nothing.
| Setting | Meaning |
CastEnabled | True to appear on the network. Takes effect on restart. |
CastName | The name people will see in their cast list, e.g. Main Hall Speakers. Blank uses InstanceName. |
CastPort | The port its description and control pages are served on (default 7453). Discovery itself uses the standard 1900/UDP. |
CastAllowFrom | Blank means any device on the network may send. Otherwise a comma-separated list of addresses (192.168.1.50) or prefixes ending in a dot (192.168.1.). |
Where cast audio sits
What a phone sends becomes the background sound, in exactly the place the Virtual Radio occupies. Everything that already governs background audio therefore governs this too, with no new rules to learn: scheduled items and announcements duck it, P1/P2/P3 priority interrupts it, the volume profile shapes it, and an emergency overrides it outright. A phone can never make the building quieter than the schedule intends.
Casting and the Virtual Radio want the same channel, so whoever asked most recently wins: starting a cast stops the radio, and when the cast stops the radio resumes at its next tick.
There is no password in this standardDLNA has no authentication of any kind — that is a fact about the protocol, not a gap here. Anyone who can reach the port can play sound through the building. On a network where that matters, set
CastAllowFrom, or leave
CastEnabled off and use the
Web Remote, which does have accounts.
Checking it is working
Press Ctrl+I for a one-line summary, or read cast in GET /api/status on the Web Remote — the name being advertised, the port, whether anything is playing, and how many sessions have been served since start-up. Every start, stop and refusal is written to the log with the line prefix Play to this device:.
If a phone cannot see it: discovery is a multicast message, and many wireless networks block multicast between clients or put phones on a guest network that cannot reach the server. That is a network setting rather than a Multilarm one, and it is the cause in nearly every case.
Deployment
Date-Scoped Config Switching
When a day is wholly different — Ramadan, weekends, a winter timetable — swap the entire config file automatically at midnight, rather than overloading one file with rules.
AlternateConfigfilename,rule pairsdefault (empty)
Pipe-joined filename,rule pairs. The filename is a bare name (resolved next to the executable) or a path tag ({app}, {data}…). The rule uses the same time+date filter syntax as RadioScheduleRules. Evaluated once at midnight (and at startup), top-to-bottom, first match wins. No match → the default Multilarm.config.xml.
# Ramadan config all of March; weekend config on Sat/Sun otherwise:
multilarm.ramadan.xml,00:00-23:59,m=3|multilarm.weekend.xml,00:00-23:59,w=sa,su
# Friday config from a subfolder; winter config from AppData:
{app}\configs\multilarm.friday.xml,00:00-23:59,w=fr|{data}\Multilarm\multilarm.winter.xml,00:00-23:59,m=12-2
Important behaviours
- Because evaluation is daily, use the date filters (
m=, d=, w=, nw=, ld) to pick days; time ranges other than full-day rarely matter.
- The
AlternateConfig field is never overwritten by a switched-in file — this prevents switch loops.
- Missing/unreadable files log a warning and fall back to default; the process never crashes.
- Live edits while an alternate file is active are saved back to that file — so changes made on a Ramadan day update the Ramadan config, not the default.
- The active file shows on the Ctrl+I status and in
/api/status as activeConfig.
Rules vs switchingUse
rules & scopes for a few different slots on certain days; use config switching when an entire day's setup (folders, voices, radio, volume) differs.
Operations
Monitoring, Logs & Diagnostics
How to see what Multilarm is doing — live and after the fact.
Live status
Press Ctrl+I for a runtime summary: uptime, current audio, radio state, volume & profile, active config, Web Remote (port/firewall/auth/URL), Hub state & peer count, Cloud Relay (connection + recent log), and the next alarm. The console title bar shows a live countdown to the next alarm, and output is colour-coded — alarms red, radio cyan, speech yellow.
Log files
| File | Contents |
Multilarm.error.log | Handled & unhandled exceptions, one line each: [HH:mm:ss] file:line — Type: message |
Multilarm.playlist.log | Every radio track played, timestamped |
Multilarm.audit.log | Execution record of remote actions, prefixed LAN / CLOUD / LOCAL / LIVE |
A rolling in-memory buffer (up to 500 entries) is viewable with Ctrl+D or via GET /api/logs.
Self-limiting logsAll three on-disk logs are always written and auto-curtailed — each is capped at 2 MB, then rolled to a single .1 backup (the previous backup is discarded) and a fresh file started. So total disk use per log stays bounded (~4 MB) even on a device left running for months. There are no knobs to configure; the playlist log in particular can't grow without limit from continuous radio stream-title entries.
Startup health check
On boot, Multilarm validates every configured audio path and reports per-folder file counts (e.g. AlarmPath [C:\Adhan]: 42 file(s) found), flagging missing or empty paths as warnings. At midnight it logs the new day's schedule load.
Built-in diagnostics (check-up battery)
Multilarm can test itself and tell you what is working and what is not — designed for commissioning a new site and for checking a deployment after changes. Around 30 checks run in sections (Deployment · Clock & schedule · Audio · Cloud & network · Hardware) and each result is PASS / WARN / FAIL / SKIP with a plain-language "what to do" hint on every problem. The standard battery is silent and safe — it decodes audio files without playing them and never moves a relay.
- On a phone or laptop (recommended for site visits): open
http://<device-ip>:6580/diagnostics (also linked from the device dashboard). Press Run checks for the silent battery, or Guided commissioning mode to also step through the audible tests one at a time — test tone on every output, alarm sample, live spoken announcement, microphone loopback (you record 3 s and hear it back), relay pulse and bell — answering Heard/seen it? Yes/No after each. Then Download report (JSON) or Print report for the commissioning record.
- At the device console: press Ctrl+Y for the silent battery inline.
- From a script / after deployment: run
Multilarm --diagnose (add --json for machine-readable output, --section <name> to limit it). The exit code is the number of failed checks, so installers and deploy pipelines can gate on it. If the program is already running, the command automatically tests the live instance.
- Remotely, before a site visit: the cloud dashboard's Diagnostics card runs the silent battery on the device and shows the same report (owners only).
A check that cannot reach hardware says soSome hardware can only be spoken to by one program at a time — a USB relay board on a serial port is the clearest case. If a check runs alongside the already-running Multilarm (which is what happens when a deploy script calls --diagnose while the service is up), it cannot open a port the live instance is holding. That is reported as a WARN explaining exactly that, not a FAIL, and it points you at the running instance instead — its Hardware panel, /api/hardware/status, or /api/diagnostics/run. A board that is genuinely missing, with nothing else running, still fails. Whatever the operating system said when the port would not open is now always quoted in the message, so you are never left guessing between “broken board” and “wrong port name”.
Commissioning engineers can then import the downloaded report into a job in the Commissioning Portal — machine-verified checks pre-fill the checklist (your own answers are never overwritten) and the customer's certificate gains a "Device self-test" summary line.
Automatic fault detection
Between check-ups the device watches itself continuously — no setup needed. A background sweep (every 5 minutes) plus live error monitoring detect problems in the program (repeated errors, speech engine not ready, duplicate processes), peripherals (output device unplugged or shuffled, relay board unresponsive, emergency button unarmed, disk low, missing audio files) and the network (cloud connection failing, device token rejected, MQTT broker unreachable). Detected faults:
- appear as an Attention row on the cloud dashboard and a red banner at the top of the
/diagnostics page (with first-seen time and occurrence count);
- are included in every status update, diagnostics report and support bundle;
- trigger an alert email for serious (FAIL-severity) faults when Email Alerts are enabled;
- clear themselves automatically when the problem goes away — each raise/clear is also logged as a
[diag] line in Multilarm.error.log.
Auditing
Remote actions are recorded in two ledgers: the cloud database audit_log (intent — who asked) and the device's Multilarm.audit.log (execution — what actually ran). Together they give a tamper-evident trail across the boundary.
Reporting a bug
Please includeWhen reporting an issue, attach Multilarm.error.log and Multilarm.config.xml from the program's root directory — they tell us almost everything we need.
Operations
Multilarm Network
The companion that watches the network your device sits on — and tells you whose fault it is when a site stops working.
What it is for
When a site goes quiet, the expensive question is not what broke but whose problem is it. Multilarm Network answers that: it separates a fault in Multilarm from a fault in your network from a fault at your internet provider, and says so in plain English rather than leaving you to interpret a graph.
Nothing to installIt is part of every Multilarm installation. The Windows installer registers it as a service named Multilarm Network; the Linux packages and the Raspberry Pi image install it as a companion service beside the main one. It reuses the device's existing cloud pairing — no second token, no second login, no extra configuration.
It does nothing until the device is paired
With no CloudDeviceToken, the monitor logs one line and sleeps: no scanning, no network traffic, no cloud calls. That is precisely why it can be installed everywhere by default. On an unpaired machine it costs you an idle background process and nothing else, and it starts working by itself the moment the device is paired — there is nothing to go back and switch on.
What it reports
| Finding | What it means |
| Internet down | The router answers but nothing beyond it does. Your equipment is fine — this is one for your provider. |
| Router not responding | The gateway itself has stopped answering. Usually the router, its power, or the cable to it. |
| Watched device offline | Something on your watch list — a camera, till, access point — has dropped off the network. |
| Speed below plan | Measured throughput is well under the download speed you told it you pay for. |
| Latency trend | Response times have drifted meaningfully worse than this site's own established baseline. |
| New device | Something has appeared on the network that was not there on the previous scan. |
Every device it finds is listed with its address, manufacturer, published network name where there is one, and a best guess at what kind of thing it is. Manufacturer names come from a copy of the official IEEE hardware registry (about 53,000 manufacturers) built into the program, so naming works with no internet at all and nothing about your equipment is sent anywhere to identify it.
“Randomised address” is not a faultModern phones and laptops deliberately disguise their hardware address for privacy, so there is genuinely no manufacturer to look up. The monitor labels these as randomised rather than guessing — a confidently wrong manufacturer is worse than an honest blank.
Where to see it and what to tune
Everything appears in the cloud dashboard under the device's Network view, and anything serious reaches you through the alerting you already use — email, Telegram and phone notifications — with nothing extra to configure. From the same place you can turn monitoring off, change the scan and speed-test intervals, set quiet hours so the speed test avoids your busiest period, enter the speeds your plan promises, and choose which devices go on the watch list.
Limits, deliberately
- Read-only. It cannot change, block or disconnect anything on your network. There is no remote-control surface at all.
- It does not read your traffic — only which devices are present and whether they answer.
- Unprivileged on Linux and the Raspberry Pi, and it opens no port for anything to connect to.
- No AI. Every verdict comes from a fixed rule set, so the same situation always gives the same answer, and nothing about your network goes to any AI service.
- It can never interfere with an announcement. It is a separate process from the audio engine on purpose — a scan that stalls cannot delay a prayer time, a bell or an emergency broadcast.
Turning it off
Switching monitoring off in the dashboard stops the scanning while leaving it installed, which is almost always what you want. To stop the background service too: on Windows set the Multilarm Network service to Disabled in Services; on Linux or a Pi run systemctl --user disable --now multilarm-netmon, or sudo systemctl disable --now multilarm-netmon on an appliance-style install. The main Multilarm service is unaffected either way.
Operations
Incidents & Drills
The response plan you write once, calmly — and fire in one tap when there is no time to think.
Not a fire alarmMultilarm is not fire-alarm equipment and is not certified to any fire-alarm standard. Its emergency voice alerts complement a certified fire detection and alarm system; they do not replace one. The incident tools run and record a response — they do not detect anything.
Where it lives
In the cloud dashboard, under Incidents in the top bar. It sits alongside the existing Notify People card, which is unchanged — a one-off message to everybody still works exactly as it did. An incident is the structured version for the situations where the order people are told in, and the proof of what happened, both matter.
What a template holds
- The message — subject and body, written in advance instead of typed under pressure.
- An escalation ladder — who is told first, and which group is added after how many seconds if nobody has acknowledged. Wardens immediately, duty managers after two minutes, everyone after five. Nobody who has already acknowledged is contacted again.
- Which devices react — a scenario to play on the speakers and a line of text for any connected display. The display's previous message is restored when the incident closes.
- When to stop — how many acknowledgements end the escalation, whether to re-send the current stage on a timer, and how long before the incident closes itself.
Starting one
Each template offers three buttons. Start runs it for real. Drill runs the whole chain but prefixes every message and display line with DRILL — THIS IS A TEST. Silent drill notifies people but plays nothing and changes no display, so the human chain can be tested during business hours. The Android app can start the same incident, which makes a phone in a pocket a panic button.
Recipients acknowledge with one tap on a link in the message — no login. The Incidents page shows the current stage and the running acknowledgement count.
The after-action report
Every incident, drill included, produces a printable report: when it started and who started it, every stage that fired, everyone contacted on every channel, who acknowledged and when, what played and where, your closing note, and a full time-stamped ledger. Each incident keeps its own copy of the plan it ran, so editing or deleting a template later never rewrites history.
Set up recipients firstAn incident notifies people, so add them in the dashboard's Notify People card and give each one a group tag (for example wardens, managers). Those tags are the rungs of the escalation ladder.
Operations
Desktop Alerts
The same warning the speakers give, on every screen you point at the device — and a way for whoever is sitting there to say “seen it”.
Not a fire alarmMultilarm is not fire-alarm equipment and is not certified to any fire-alarm standard. A message on a screen is a notice, not a detection system.
Speakers reach people who can hear them. A machine room, a call centre in headsets, a deaf colleague, a night shift with the volume down — those people need the message somewhere else. Every Multilarm device publishes a small notice board of what is happening right now, and three different things can read it.
Any screen with a browser
Open http://<device>:6580/alert on a spare monitor, a tablet, a signage player or a kiosk browser. It stays quiet and dark until something happens, then fills the screen. Nothing to install, no cloud account, no internet — it talks only to the device on your own network. An Acknowledge button appears when the alert asks for one.
Windows: the tray companion
The Multilarm tray app watches every instance on the machine and now watches their alerts too. A live alert takes the screen over; when the device says it is finished, the takeover closes itself. It never steals your keyboard, so a half-typed message survives it.
- Take the screen over during an alert — on by default; turn it off and the tray goes back to a balloon tip only.
- Show a banner instead of full screen — a strip across the top, for a till or a control room that must stay usable.
- Play a tone on a new alert — off by default. The building's speakers are the alarm; a room of chirping PCs helps nobody.
Linux: the alert agent
multilarm-alert-agent.sh does the same job with whatever the machine already has — a kiosk browser on a desktop, a notification if there is no browser, and a message to every terminal on a headless box. It needs only sh and curl, and runs happily as a systemd --user service.
What sets an alert off
- The device — the emergency button, or an emergency started from the web panel. No cloud account is involved at any point.
- A cloud incident or drill — including a silent drill: silent means make no sound, not say nothing.
Acknowledging
An acknowledgement from a screen counts towards the incident's target exactly like a tap on an emailed link, and it is listed in the after-action report with the machine it came from. Raised while the internet is down, it waits on the device and is delivered when the line comes back.
A screen can only ever agreeThe notice board is read-only apart from acknowledgements. A watching screen cannot start, stop, silence or delay anything — which is why it needs no password. The worst a compromised monitor can do is claim somebody read the message.
Getting Started
Sample Deployments
Multilarm ships with four worked example configurations plus two ready prayer calendars. Copy a sample's Multilarm.config.xml into your program root (and extract its audio folders) to start from a real-world setup.
🏠
Care Home — Daily
A RecurEveryDay setup: the same announcements, mealtime reminders and ambient music every day.
🏫
School — Weekly
A RecurEveryWeek setup: period bells and assembly calls on a Mon–Fri timetable.
🏋
Gymnasium — Monthly
A RecurEveryMonth setup: day-of-month scheduling for a leisure-centre PA.
🏡
Home, two PCs — Daily
A house rather than a building: the player machine serves the Web Remote to a second PC, with an audio sequence, a chime before speech, and quiet-hours volume rules.
Prayer calendars
- Default — UK (Sheffield): a full-year prayer-time calendar, used out of the box.
- Bangladesh: drop-in replacement config with Bangladesh prayer times against the default Adhan audio.
How to apply a sampleCopy/replace Multilarm.config.xml from the chosen Samples folder into the application root and extract its audio folders there. Restart (or let hot-reload pick it up).
Tools
The Configuration Generator
A visual editor for Multilarm.config.xml — every one of the 97 settings with its own reference text and validation, plus a set of sub-tools that build the hard values for you: a whole year of prayer times from your coordinates, a calendar import, a visual schedule grid, and point-and-click builders for every rule field. You never have to hand-edit XML, and you never have to memorise a rule grammar.
If you only read one thingNearly every “how do I…” question about setting Multilarm up is answered by a button in this tool. The table under
Which tool for which job below maps the common jobs to the exact control that does them. Prefer to watch? There is a 16-minute narrated tutorial at
multilarm.com/help-video.html, chapter 4 covering the wizard, the prayer-time calendar generator and the calendar import.
Which tool for which job
| What you want to do | Use this |
| Set up a new device from scratch | Start from a preset → Guided setup wizard |
| Get prayer times for your own city | Salat Calendar Generator (field 9) |
| Load a term timetable / event calendar you already have | Import .ics |
| See and edit the year as a grid instead of one long string | ☷ Visual Builder (field 9) |
| Add one time, or the same times across many days | Quick Entry Builder / Calendar Quick Add |
| Shift every time by a few minutes (DST, a clock correction) | ± Apply to All in the Quick Entry Builder |
| Write a schedule rule without learning the syntax | the Rule Builder on that field |
| Change a device that is already running, in place | Live Connection — Load / Save from a running Multilarm |
| Play several files for one announcement | Audio sequences section (source specs) |
| Find the right audio device number | press Ctrl+O at the device console, then the MirrorDevices pill editor |
| Print a timetable for the notice board | 🖶 Print Calendar PDF in the Salat generator |
Four ways to open it
- On the web —
multilarm.com/multilarmconfiggenerator.html, nothing to install.
- Offline file —
MultilarmConfigGenerator.html ships with Multilarm; open it in any browser with no internet at all. (Opened straight from disk, browsers block location auto-detect — type your coordinates instead.)
- On the device — the Web Remote serves the same page at
/config, so you edit against the running instance.
- In the cloud dashboard — the same editor and the same guided wizard when you claim or configure a device.
Getting a config in and out
- 📂 Load Config XML — open an existing
Multilarm.config.xml and every field, list and rule editor fills itself in.
- 💾 Save Config XML — writes the file. Every tag is always written, whether you touched it or not, so a saved file is always complete and never half-migrated.
- ↺ Reset to Defaults — back to the shipped defaults.
- Live Connection — type the device address (e.g.
http://192.168.1.50:6580) plus the Web Remote username and password if you set them, then ⬇ Load from Multilarm to pull its current settings, and ⬆ Save to Multilarm to push your changes back. The device notices the change and applies it in about two seconds — no restart, except for the four restart-required fields.
A sticky bar at the bottom of the page keeps Save, Reset, Localhost and Import .ics in reach however far you have scrolled.
Working the form
- 20 collapsible sections covering all 97 settings, with Expand All / Collapse All. Fields are numbered, and the numbers match this manual’s configuration reference.
- Every field has a Help button opening its full reference — what the value means, its type, range and default, worked examples, and the console shortcuts that test it.
- Validation as you type — out-of-range numbers are called out with the allowed range rather than silently saved.
- Path-tag buttons on every path field insert
{app}, {home}, {docs}, {data}, {local} and {temp}, which resolve at runtime — the way to write one config that works on Windows, Linux and a Pi.
- List editors for multi-value fields: alarm folders as rows, file formats as tags, mirror devices as removable pills (with a raw pipe-delimited box underneath for bulk paste).
The schedule (field 9) — five ways to fill it
DateAndTimeData is the one field that carries a whole year, so it has a toolkit of its own. A Mode badge above the box always shows which format is in force — Full Year, or the day/week/month recurrence you picked in section 1 — and every tool below writes in that format, reading your DateIdentifier, DateDelimiter and TimeDelimiter live so custom delimiters are respected.
1. Raw Text
The string itself, editable. Paste a timetable straight in. A panel underneath watches for duplicate dates and turns red if a date appears twice, with a Merge & dedupe duplicates button that folds them into one entry and drops repeated times. Format Help opens the full grammar for every recurrence mode with examples.
2. Visual Builder
The same data as a grid: one row per date, one column per alarm slot. Navigate month by month, set how many time columns you want, and fill a whole column down the month in one go. + Fill Month and ▶▶ Fill Year create the empty rows for you; 🗑 Clear Month empties one. ↶ Undo and ↷ Redo cover every edit (with a counter, so you can see how far back you can go), and ↓ Sync to Raw / ↑ Re-parse Raw move between the two views whenever you want.
3. Quick Entry Builder
Build one entry at a time: pick the date (or day-of-month, or weekday — it follows your recurrence mode), add times one by one, and ✓ Insert Entry. Also here:
- Bulk Add — paste comma-separated times and they all join the list.
- ⇅ Sort All — put every entry in order.
- ± Apply to All — shift every time in the whole field by ±N minutes. Times wrap correctly past midnight. This is the one-click fix for a clock correction or a seasonal adjustment.
4. Calendar Quick Add
The same times across many days at once. Choose the scope — whole year, a single month, or a custom date range — then narrow it by weekday with one click (All, Mon–Fri, Weekend, Clear). Add the times (or paste them), choose Merge (keep what is there, add these) or Replace (clear the matched days first), and ✓ Apply to Matched Days. This is how a school year of bells, or a Ramadan-only pattern, goes in without typing 300 lines.
Why it asks for a yearDateAndTimeData stores day and month only — no year, because the same calendar recycles. Calendar Quick Add asks for a year purely to work out which weekday each date falls on.
5. Generate or import it
The Salat Calendar Generator (below) computes a year of prayer times from your coordinates, and Import .ics (below) reads a calendar file you already have. Both write straight into this field.
Prayer times — the Salat Calendar Generator
You do not need a prayer timetable from anywhere else. The generator works your times out from your own location, in the browser, with the calculation library built into the page — nothing is fetched from any prayer-time website, so it works with no internet connection at all.
- Enter your latitude and longitude, or tap 📍 Detect Location. (Auto-detect needs the page served over HTTPS or localhost; opened straight from disk, browsers block it — type the coordinates instead. Sheffield, UK is
53.3833 / -1.4667.)
- Choose the year.
- Choose your calculation method — MWL (Muslim World League), ISNA (North America), Egypt, Makkah (Umm al-Qura) or Karachi — your Asr madhab (Shafi or Hanafi, Hanafi giving the later Asr) and your high-latitude rule.
- Press Generate Calendar, then ✓ Insert → Replace Field 9 to write the year into
DateAndTimeData, or + Append to Field 9 to add it after existing entries.
You get all six daily times — Fajr, Sunrise, Dhuhr, Asr, Maghrib and Isha — in the same *D-M|HH:MM|… format the shipped configs use. Before you insert anything you can ▶ Show/Hide Calendar Preview to read the generated year month by month, and there are three ways to take it away with you:
- 📋 Copy Raw — the raw string to the clipboard.
- ↓ Download .txt — the same as a file.
- 🖶 Print Calendar PDF — a clean printable timetable, ready for the notice board.
UK settingsAbove 48°N the sun never dips far enough for the angle-based methods, so pick the 1/7 of Night high-latitude rule. MWL + Hanafi Asr + 1/7 of Night is the combination most UK masjids use. If your masjid follows its own printed sheet instead, you can still paste that straight into Field 9.
Import .ics — a calendar you already have
If your schedule already exists as a calendar — a school term timetable, a shift rota, a booking calendar exported from Outlook, Google Calendar or Apple Calendar — you do not need to retype it. 📅 Import .ics reads the file and turns the events into alarm times.
- Choose the .ics file. Repeating events are fully expanded, so “every Monday during term” becomes each actual date.
- Pick the years to import. If the file spans several, you get a year list (with Select earliest, Select all and Clear). Nothing is ticked by default — you choose deliberately.
- Set a default time for all-day events. Events with no time of their own (and each day of a multi-day event) use this.
- Choose how it merges with what is already in field 9: Replace (clear and write), Append (keep existing dates, add new ones, skip duplicates), or Merge (for a date that exists in both, combine the times).
You can also let the import set up the fields around the schedule — each one optional, each one a tick box:
- Auto-set the Recur mode (day / week / month / year) to match the shape of what you imported.
- Set announcement text (20) from the event summaries, so each event announces itself.
- Append TextDataRules (22) so dates that do not fit the usual pattern still get their own wording.
- Set FormatInEffect (10) to match the largest number of times any day needs.
- Append OffsetRules (11) for time variants.
- Append AlarmIndexRules (15) so different event categories play from different audio folders.
Safe by designThe rule fields are only ever appended to — the import never deletes a rule you wrote — and duplicate entries are skipped rather than doubled.
Rule builders — compose, preview, add
Multilarm’s rule fields are compact, which makes them powerful and easy to mistype. Every one of them has a builder: you pick the parts from dropdowns, watch the generated rule string appear live underneath, and only then add it to the list. You can always still type the raw value if you prefer.
| Builder | Field | What you get |
| Schedule Rule Builder | Virtual Radio (38), Quote (42), Volume Profile (48) | Time windows with date filters. Overlapping windows inside one rule are consolidated automatically; several rules join with ; and the schedule is on if any of them matches. Volume Profile adds a volume level to the window. |
| Quick Rule Builder | Offsets (11), Alarm Index (15), Announcement Text (22), Zones (71) | Overlay rules — payload plus filters, first match wins — with a live preview and a reset. |
| Bell Rule Builder | Bell Schedule (68) | A fire time, a pulse length, and the days it rings. |
| Alternate Config Builder | AlternateConfig (65) | Which config file takes over on which dates. |
All of them share the same filter vocabulary, so learning it once covers everything: m= months, w= weekdays, d= day of month, nw= the Nth weekday of a month, ld the last day, t= a time or time range, slot= which alarm slot — and a leading ! negates any of them. The full grammar is in Rules & scoping.
Announcement text placeholders
The announcement text field (20) has insert buttons for the placeholders Multilarm fills in when it speaks: #ALARM+1# and #ALARM+2# (the time of the next alarm and the one after), #TIMETOALARM+1# and #TIMETOALARM+2# (how long until them), and #NOW# (the current time). Click rather than remember.
Audio sequences — several files for one announcement
The last section documents the source-spec grammar, which is what lets one alarm play more than one file — a bleep before the adhan, a three-tone motif, a different recitation each day. There is no list to fill in: the grammar lives inside the alarm-folder and chime-path fields themselves.
- A path can be a folder, a single file, or a folder plus a filename pattern (
bell*.mp3).
>one picks one at random (the default, so nothing changes for existing configs), >all plays every file in filename order, >shuffle plays them all in a fresh random order, and >seq plays the next one each time and remembers its place across restarts — a different recitation every day.
*count caps how many files come from that path.
- Chain sources with a plus sign surrounded by spaces:
{app}\Adhan\Bleep\intro.mp3 + {app}\Adhan plays the bleep, then a random adhan, as one announcement.
Two safety settings sit with it: SequenceGapMs (74) is the silence between the different files inside one announcement — not to be confused with AlarmRepeatGap, which is the silence between repeats of the whole thing — and SequenceMaxSeconds (75) is a hard ceiling on one announcement, ten minutes by default, so pointing >all at a folder of 400 tracks cannot hold the speakers for hours.
Typos are caught, not guessedA misspelled mode such as >shufle is reported as an error at startup and in the health check. It never silently falls back to a random pick.
Guided setup wizard
New to Multilarm, or setting up a device for a common use case? Pick a use case from the “Start from a preset” bar at the top of the generator (or click ⚙ Guided setup…) and the guided wizard opens straight away — it’s the same wizard the Cloud dashboard runs when you claim a device. Instead of facing every field at once, the wizard asks a short series of plain-English questions and fills the form in for you:
- Pick what the device is for — Mosque / Masjid, School / College, Care home, or Office / Factory.
- Answer a few common settings, grouped into a handful of steps — for example the device name and announcement voice, master volume, bell / signal times (add each time and choose which days they ring), how many zones you have, quiet overnight hours, and whether an emergency broadcast can be started remotely.
- Set day preferences per rule — every schedule the wizard builds (bells, background audio, quiet hours) has its own independent day choice: Every day, Only these days (e.g. Mon–Fri), or Every day except (e.g. dim the volume overnight except at weekends). So different rules can run on different days. Each zone gets its own day choice too, so one wing can be addressable only on weekdays while another is every day.
- Go further when you need to (all optional, off by default): give each bell its own pulse length (e.g. a longer assembly bell), add a second quiet-hours tier (e.g. evening 60% then overnight 30%), and choose background audio from a local folder or an online stream URL.
- Review & apply — the wizard shows exactly which fields it will set (everything else stays at its default), then fills them into the form.
Behind the scenes the wizard turns your answers into the right values automatically — a list of bell times plus a weekday choice becomes a Bell Schedule, a quiet-hours window becomes a Volume Profile rule, and a zone count seeds the zone targeting rules and tells you which relay board you need (a 4-channel board covers 2 zones; 3–4 zones need an 8-channel board). It is a starting point, not a mode: once applied, every field is ordinary and fully editable, and you can still use the quick Apply preset button if you just want the defaults with no questions.
Same wizard, three placesThe identical guided flow runs when you claim a device in the
Cloud dashboard, and a compact version runs on first launch at the device console — so you get the same guided path however you set up.
The ten presets
Each use case in the “Start from a preset” bar is a complete, sensible starting configuration, and each tells you what it expects you to do next:
| Preset | Sets up | Then you… |
| Mosque / Masjid | Five daily prayer alarms with pre-recorded adhan and spoken English announcements. | Generate your prayer times and point the adhan folder at your recordings. |
| School / College | Weekday class-change bells with example times, spoken announcements, an overnight silent window, and a lockdown broadcast. | Wire the relay board and set the serial port; record the lockdown message. |
| Care home | Gentle meal and activity announcements at a slower speaking rate, dimmed overnight volume, optional calm daytime background audio. | Point the background-audio folder at your music if you enabled it. |
| Office / Factory | Weekday shift and break signals with example times, live push-to-talk, and an emergency broadcast. | Wire the relay board and set the serial port. |
| Home | Spoken reminders, evening background music and a silent night, at a gentle volume. No relay board or emergency button. | Add your reminder times. Personal, non-commercial use at home is free. |
| Cafe / Restaurant | Background music through opening hours, a spoken last-orders announcement, and silence overnight. | Point the background-audio folder at your own music and check your public-performance licence. |
| Gym / Leisure centre | Background music, spoken class-start and closing announcements, zones, and an emergency broadcast. | Add one announcement line per class; wire a relay board if you want the studio addressed separately. |
| Warehouse / Distribution | Shift and break signals at full volume, slower speech for a noisy floor, and an emergency broadcast. | Wire the relay board and set the serial port; record the emergency message. |
| Church / Chapel | A chime before each Sunday service with a gentle fade-in, spoken notices, and silence in between. | Set your service times; if your bell is a recording, schedule it from the alarm folder instead. |
| Museum / Gallery | Quiet ambient audio between announcements and a closing-time sequence at low volume. | Schedule the fifteen- and five-minute closing warnings and supply your own ambience audio. |
Fill defaults only next to the wizard button drops the preset’s raw values into the form without asking any questions — for when you intend to hand-tune everything anyway.
Two names for everything
Every setting has two correct names: the one the configuration file, this manual and the support runbook use (“48. VolumeProfileRules”), and the one you would use if you had just plugged in a Raspberry Pi (“Quiet hours”). The generator shows one at a time. The 💬 Plain English button at the top switches to 🔧 Expert, which puts the field numbers and internal names back exactly as they were — hover any plain label to see its number without switching. The choice is remembered on that computer.
It is a display setting and nothing more. It changes no field, no value and no saved file: a configuration written in plain-English mode is byte for byte the same file as one written in expert mode. Help panels always keep the numbers, because that is what support and the documentation quote back to you.
The same plain vocabulary is used across the rest of the product:
| Where you may see… | It means | Which is |
| Slot, field 9 string | Scheduled item | One entry in the date-and-time list — a time something happens. |
| Alarm path | Sound | The audio file or folder played at a scheduled item. |
| TTS path / TTS engine | Spoken message | Text the device speaks aloud, generated on the device. |
| P1 / P2 / P3 / background | Emergency / Announcement / Music | Playback priority: an emergency interrupts an announcement, which ducks the music. |
| Zone rules | Rooms | Which areas a given sound goes to. Needs a Multilarm Box relay board to switch real speaker lines. |
| Trigger, scenario | Automation, Routine | Something that happens in response to an event rather than a clock time. |
| Passthrough | Live mic | Speaking live through the system from a microphone or a phone. |
| Relay channel | Bell output | A dry-contact output that rings a real bell or sounder. |
| Volume profile | Quiet hours | Times of day the volume is reduced or silenced. |
| Instance | Device | One installation of Multilarm, named so you can tell sites apart. |
Things worth knowing
- It preserves your advanced rules. The visual editors edit the default line of scoped fields and keep your per-day scope lines intact in the raw value, so a config that uses advanced rules round-trips safely.
- Every tag is always written. A saved file is complete, so a config saved here can never be missing a setting a newer build expects.
- Location auto-detect needs HTTPS. Opened as a local file, browsers refuse the location request and report it as denied. That is normal — type your coordinates.
- February should have 29 days in a full-year schedule, so leap years are covered.
- Apply daylight-saving shifts after the changeover dates — or turn on UK Daylight Savings (8) and let Multilarm do it.
- Cleaning a pasted timetable is easiest in a text editor or Word before it comes anywhere near the tool (use
^p for a line break in Word’s Find/Replace).
- Four fields need a restart to take effect, whichever way you save: Web Remote enabled, Web Remote port, playback device and record device. Everything else hot-reloads in about two seconds.
Concepts & Configuration
Music Library, Playlists & Rotation
In plain termsVirtual Radio shuffles a folder, and for most sites that is enough. This page is for the sites where it is not: where the background music should know what a jingle is, avoid playing the same artist twice in ten minutes, drop a reminder in every twenty tracks, and keep a record of what actually went out.
The library
Point Multilarm at one or more folders and it reads what is already in your files — title, artist, album, genre, year, BPM — along with the length, sample rate and channel count. WAV files are also measured for peak and average level, clipping, and silence at the start and end, so you can find the one track that is twice as loud as everything else before a room does.
Your files are never modified. When you correct a title or set a cue point, that correction is stored in Multilarm's own index, not written back into your audio. If another program manages the same library, nothing Multilarm does can damage it. Reset on any track throws the correction away and goes back to whatever the file itself says.
Scanning is incremental: a file whose size and timestamp have not changed is skipped, so a second scan of a large library is quick. You can cancel a scan at any time, and a cancelled scan never removes anything from the index.
Cover art is found, not copied. When a track carries an embedded picture, the scan records where in the file it sits rather than pulling the image out — so a five-thousand file scan is not spent decoding pictures nobody has asked to see, and there is no thumbnail cache to go stale when you re-tag your library. The dashboard fetches a cover straight from the audio file the moment it needs one. MP3, FLAC and MP4 art is served this way; Ogg Vorbis art is detected but not served, because it is stored encoded inside a comment. A track with no reachable picture simply shows a placeholder.
Four reports worth running
- Unreadable — files that could not be decoded. Usually a corrupt download or a format nothing supports.
- Missing metadata — no title or no artist. These are the tracks that make a now-playing display look broken.
- Clipped — recorded too hot. On a PA these are what people describe as “harsh”.
- Duplicates — the same track in two places, which is why it seems to come round too often.
Playlists
A static playlist is a running order: these tracks, in this sequence. A smart playlist is a description — “genre is Ambient, and it has not played in the last fourteen days, longest first, limit thirty” — that Multilarm works out fresh every time it is asked. You can match on genre, artist, album, title, comment, track type, path, year, BPM, duration, play count and days since last played.
Playlists can contain other playlists, up to six levels deep, so a “Daytime” list can be built out of “Morning”, “Lunch” and “Afternoon”. A playlist that would end up containing itself is refused when you save it, rather than accepted and then looped over forever — and a refused save leaves the playlist exactly as it was, so nothing is half-changed behind the refusal. A smart rule that names a field or an operator Multilarm does not have is refused the same way, with the valid ones listed, instead of being quietly turned into a rule that matches something else.
Two limits can be set on any playlist, and either may be left at zero. A count limit keeps the first N tracks; a duration limit keeps as many as fit inside N minutes. Set both and whichever bites first wins. A track whose length has not been measured yet counts as zero rather than being dropped — a slightly short hour is recoverable, a missing track is confusing.
An entry can also be marked play once. It plays, and is then taken out of the playlist and the file saved. The removal happens after it has gone out, never before, so if the machine loses power between the two you get a repeat rather than a gap. Use it for the one-off notice that should not still be in the list next week.
Freezing a smart playlist pins what it resolves to right now. Use it before an event that has to be identical every time it is rehearsed — a freeze means the running order cannot change under the people rehearsing it. Unfreeze to let it move with the library again.
Rotation
The rotation picks what plays next across several playlists at once, each with a weight, so you can say “mostly this, occasionally that” without maintaining one giant list. Three separation rules keep it from sounding repetitive:
- Do not play the same track within N minutes (default 180).
- Do not play the same artist within N minutes (default 45).
- Do not repeat anything within the last N tracks (default 20).
- Do not play the same album within the last N tracks (default 0, meaning off). Six to ten is the useful range — that is roughly where a record stops announcing itself. It is off by default so that a rotation already running on your device behaves after an update exactly as it did before.
An interrupting playlist fires on a count or a clock — every 20 tracks, or every 30 minutes, optionally only between certain hours. That is the jingle, the station ident, the hourly safety reminder.
The rule that outranks the restIf the separation rules leave nothing at all to play, Multilarm relaxes them rather than going quiet: first the album window, then the artist window, then the track window, then all repeat rules, oldest-played first. The album window goes first because it is the weakest of the four — hearing one record twice inside ten tracks is a smaller fault than hearing one song twice. Every relaxation is written to the log. Silence on a PA is a fault; hearing the same artist twice is a Tuesday.
Underneath all of that sits a safety folder. It is used only when the rotation has nothing at all to apply a rule to — no playlists, none enabled, or nothing in them that still resolves to a file, which is what a wiped index or an unmounted music share looks like. Multilarm then reads that one folder directly, without the library index, and plays something from it. The reason is written to the log every time, so the fault is visible rather than merely survived. Leave it blank and nothing changes.
Slots: different music at different times of day
A rotation on its own plays the same programme at eight in the morning and at five in the afternoon. A slot says: between these clock times, on these days, the rotation draws from these playlists — and hands back to the ordinary rotation the moment it ends. Slots may run across midnight, and where two overlap the one with the higher priority wins; on a tie the narrower window wins, because a two-hour exception inside an all-day slot is always meant to be the exception.
- Intro and outro playlists — an ident as the slot opens and another as it closes, without those lists sitting in the rotation for the rest of the day. Each fires once. If there is nothing playable in it the ident is missed rather than retried, because an ident that retries becomes a loop.
- Autoload — the slot works out its content a set number of seconds before it goes on air (a minute, by default), so the first track starts on time instead of after a library query. On a small machine with a cold index that is the difference between a clean handover and a couple of seconds of nothing.
- Linked slots — give two slots the same link key and they share one running order and one position in it, so “the nine o’clock hour, repeated at three” genuinely repeats instead of rolling the dice again.
- Overrun — on by default, meaning the last track of a slot is allowed to finish past the end. Turn it off and the slot will not start a track that cannot finish in time. If nothing is short enough it starts one anyway and runs long: a slot boundary never outranks the rule about silence.
Nothing here can make a building quietSlots choose. They do not play, stop, or set a volume, and there is no setting in them that silences anything. A slot that is misconfigured, points at an empty playlist, or names a playlist that has been deleted gives you the ordinary rotation — never silence.
What is on air
Whatever starts playing is announced in one place, so the log, the outbound webhooks, the dashboard and the public page can never disagree. Open /onair on the device for a plain page showing the current track and how far through it is — suitable for a screen in a foyer. That page and its /api/nowplaying feed carry the track details only: never a file path, a folder, or anything else about the installation.
The play log
Every track that goes out is written to a CSV, one file per month, under a PlayLog folder beside your config. You can export a date range in a plain format, or in the shape that PRS, PPL or SoundExchange returns ask for. Announcements are left out of the music totals so a return is never overstated, and fields Multilarm genuinely does not know — ISRC, label, service name — are left blank rather than guessed.
To be clearThis produces the paperwork. It does not give advice: whether you need a music licence, which one, and what it costs is between you and the licensing society. Multilarm has no opinion and makes no claim.
Podcasts, in and out
In: subscribe to an RSS feed and new episodes are downloaded and indexed automatically, so a daily bulletin or a weekly talk can go straight into a playlist without anybody copying files. Downloads are size-capped, must be audio, and are checked before they are used. Nothing downloaded plays by itself — a playlist or a schedule still has to ask for it.
Out: turn on publishing and whatever scheduled recording has captured is offered at /podcast.xml as an ordinary podcast feed, so an assembly or a talk can be handed to whoever missed it, in any podcast app.
Listening on the LAN
You can optionally let somebody on the same network hear what the PA is playing, in a browser, with nothing to install: turn the listen endpoint on, set a listen key, and open /listen on that port.
Said plainly, because it mattersThis is not internet streaming. It sends uncompressed audio — roughly 1.5 Mbit/s per listener (1.54 measured on a Raspberry Pi 400 at 48 kHz), up to 32 listeners — which is fine across a building's own network and wholly unsuitable across the internet. If what you actually want is an internet radio station with public listeners, use a product built for that — LibreTime or AzuraCast: Multilarm is not one, and will not pretend to be.
Two refusals are deliberate. It will not open itself to the network without a listen key — it checks and refuses rather than hoping the connection fails. And it will not start while multi-room is sending, because both need the same single audio tap inside the engine; stop one to use the other.
Three streams, so a phone on a weak signal is not stuck with the full one. Alongside /listen.wav there is a mono stream at half the bandwidth, and a low-bandwidth one at a quarter. They carry the same audio; a listener picks whichever their connection can hold, and a stream nobody is on costs the device nothing.
Quiet does not have to sound like a dead line. Point the listen endpoint at a short WAV and any gap in the programme carries that instead of silence, on a loop. It is heard only by listeners — the building itself stays quiet, which is the whole point of a hold loop on a PA. The file has to match the engine's sample rate and channel count exactly, and be 16-bit WAV and under a minute; anything else is refused with a note rather than quietly resampled into something that sounds wrong. To find out what to match, read the listen status before you start it — it reports the engine’s own sample rate and channel count whether the endpoint is running or not, so the file you build from those two numbers is accepted first time.
You can see how many people listened, without knowing who. Connections, peak, listening time and a “unique today” figure are kept per hour and per day. No address is ever written to disk, nothing is sent anywhere, and the play log is deliberately not joined to it. The “unique” count uses a salted fingerprint held in memory for the day only — enough to stop one listener counting five times, and not built to identify anybody.
Putting it on your own site. /player.html is the player on its own, sized to drop into an iframe. /schedule.html shows the week exactly as your music slots already describe it — it is derived, so it can never disagree with what actually plays. If you point the endpoint at a stylesheet, both pages pick it up, so an embed can be made to match the site around it.
Listen again
Anything scheduled recording has captured can be listed and played back in a browser, and so can any track in the library — served from where the file already sits, with nothing copied or converted. Seeking works, so somebody can jump to the part of an assembly they wanted rather than sitting through it.
Where everything is kept
| What | Where |
| The library index | <config name>.library.json |
| Playlists | <config name>.playlists.json |
| Rotation settings | <config name>.rotation.json |
| Music slots (dayparts) | <config name>.musicslots.json |
| Podcast subscriptions | <config name>.podcasts.json |
| Listener counts | <config name>.listenstats.json |
| What went out | PlayLog\playlog-YYYY-MM.csv |
| Downloaded episodes | Podcasts\<feed name>\ |
All of these sit beside your config file, and all of them are optional. A device that has none of them behaves exactly as it did before this feature existed — nothing here changes how alarms, announcements or the existing radio folder work.
Reference
Command-Line Arguments
| Flag | Effect |
-c, --config <path> | Load an alternate config file at startup — the foundation of multi-zone deployments. |
-H, --hub | Start with Hub mode on. |
--cloud-token <hex> | Write the 64-character Cloud Relay device token into the config file and exit (pass clear to disable Cloud Relay). Safe while the device is running — it picks the change up within ~2 seconds. Combine with --config to target a specific zone's file. Ideal for headless installs and scripted provisioning. |
--diagnose | Run the built-in diagnostics battery and exit — the exit code is the number of failed checks, so scripts and deploy pipelines can gate on it. Add --json for machine-readable output or --section <name> to limit it. If the program is already running, the live instance is tested (no second engine is started). |
--rooms-probe [host[:port]] [seconds] | Measure a multi-room group and exit. Joins as a silent observer — it plays nothing — and prints one line of JSON: clock offset, best round trip, arrival jitter, chunks that missed their play moment at the current buffer, and the honest room-to-room bound. With no address it finds the sending device itself. Exit code 0 measured, 2 could not reach the sender, 3 refused. |
-h, --help, /? | Print help and exit. |
-v, --version | Print the version (1.yyyy.Mdd.Hmm) and exit. |
Behaviour
- A relative
--config path resolves against the program folder; a missing parent directory is created.
- With no
--config, the default Multilarm.config.xml is used.
- A non-default config shows in the console title:
Multilarm <version> [<config-name>].
- The chosen file is what hot-reload watches, what config POSTs write to, and what
AlternateConfig switches sit on top of.
- Unknown flags print help and exit non-zero.
Reference
Tips & Troubleshooting
Everyday tips
- The Multilarm website (docs, Cloud dashboard and Configuration Generator) has a Light / Dark toggle in the bottom-right corner — switch to Light mode if a screen is hard to read in bright sunlight. Your choice is remembered per device/browser.
- The website also has a site-wide Search — the button in the bottom-right of any page, the Search link in the navigation, or the / key. It searches every page (titles, headings and text) and works offline.
- Alarms always win — they interrupt radio, quotes, ambience and TTS instantly, and everything resumes after. No timing config needed.
Volume is app-level; set the OS volume to a comfortable max first, then trim with Volume.
CrossfadeMs of 1000–2000 ms gives the nicest radio transitions; 0 switches instantly.
- Hot-reload lets you tune
Volume, CrossfadeMs, AlarmFadeInMs, profiles and quote/ambience settings live.
- For phone control on the LAN, ensure phone and device share Wi-Fi, find the device IP (
ipconfig / hostname -I), and browse to http://<ip>:6580/.
- On a Pi, the Web Remote means you never need SSH or a keyboard — manage everything from your phone.
- The
/api/schedule endpoint feeds prayer-countdown displays and home-automation dashboards.
- In the cloud, use Push Config to sync prayer times across many devices, and Config Compare to find drift between two devices.
Troubleshooting
| Symptom | Check |
| No sound at all | Is PlaybackDevice set to 0 (disabled)? Run Ctrl+O to confirm the right index; press Ctrl+T to test. |
| Wrong speaker / silent mirror | Re-check indices with Ctrl+O; a failed mirror is logged and skipped (others keep working). |
| Web Remote unreachable from another device | Both WebRemoteUsername and WebRemotePassword must be set (else it's loopback-only). Check the firewall rule. |
| Changes not taking effect | Was it a restart-required field (WebRemoteEnabled/WebRemotePort/PlaybackDevice/RecordDevice)? Others hot-reload in ~1.5s. |
| TTS sounds robotic / words missing | Use Kokoro or KokoroFillsWords, or record the missing words (Ctrl+R). |
| Browser mic won't record | getUserMedia needs a secure context — use HTTPS or localhost, or the cloud dashboard. |
| Device shows offline in cloud | Check CloudDeviceToken and connectivity; the relay retries with backoff and reconnects automatically. |
| Config got reset | A missing/corrupt tag is rewritten with its default — keep a backup of working configs. |
When in doubtRun the
diagnostics check-up battery first —
Ctrl+
Y at the console or
/diagnostics from a phone — it tests most of the rows above automatically and gives a fix hint for every failure. Then
Ctrl+
I confirms what's actually live, and
Ctrl+
D dumps the recent log.
Reference
Frequently Asked Questions
Does Multilarm need an internet connection?
No. Every core function — scheduling, playback, even neural text-to-speech — runs fully offline on the device. The cloud is an optional convenience for remote control, never a requirement.
Can it run on a Raspberry Pi?
Yes — that's a primary target. It runs headless on ARM 32/64-bit, auto-starts at boot via systemd, and is controlled entirely from your phone through the Web Remote.
Is it only for the Adhan?
No. The same engine runs school bells, factory shift sirens, care-home announcements, hotel/gym background music and hospital paging. See Use Cases.
How many speakers / zones can it drive?
Mirror one schedule to any number of output devices (frame-aligned), and run any number of independent zones side-by-side with --config. See Mirroring and Multi-Zone.
Can the imam / a member of staff speak live over the PA?
Yes — Push-to-Talk passes a live mic straight through the speakers, and Voice Broadcast records-then-broadcasts with confirmation.
Do I have to record words for text-to-speech?
No — use the built-in Kokoro neural voice. Or record only the words you want in a specific human voice and let KokoroFillsWords cover the rest.
How do I handle Fridays / Ramadan / holidays?
Small differences: use per-day rules & scopes. Whole different days: use config switching. Both are automatic.
Is it free?
It's free for individual, non-commercial personal use. Any business, institutional or commercial use requires a commercial licence. See Licensing.
What audio formats are supported?
MP3 (and MP1/MP2), WAV, OGG, FLAC, AIFF, and M4A for radio — plus online streams for the virtual radio.
Is any of it GPL-licensed?
No. The audio engine (miniaudio + libFLAC) and the neural TTS (Kokoro + dictionary G2P) are all permissively licensed — relevant for commercial-deployment audits.
Legal & Contact
Licensing, Contact & Credits
Licence summary
Multilarm is proprietary software © 2026 Ali Muhammad (multilarm.com). You get a free, non-exclusive licence for personal, non-commercial use — private projects, personal productivity and learning, with no commercial benefit and no redistribution.
Commercial use needs a licenceAny use within a business, organisation or institution; in products, services or client work; for internal operations; or by contractors in paid work requires a commercial licence. Email
hello@multilarm.com with subject "Multilarm Commercial License Enquiry".
The source code is proprietary and confidential; reverse-engineering and redistribution are prohibited. The software is provided "as is" without warranty. The plain-language summary above is for convenience only — the full agreement below is the operative text.
Full licence agreement
MULTILARM SOFTWARE LICENSE AGREEMENT — Copyright © 2026 Ali Muhammad (multilarm.com). All Rights Reserved.
This License Agreement ("Agreement") governs use of the Multilarm software ("Software"), developed and maintained by the owner of multilarm.com ("Licensor"). The Software's source code, design, architecture, algorithms, and all associated intellectual property are PROPRIETARY and CONFIDENTIAL, and you may NOT access, view, copy, or distribute the source code; reverse engineer, decompile, or disassemble the Software; or attempt to derive source code from binaries or any distributed form.
You are granted a free, non-exclusive, non-transferable license for PERSONAL, NON-COMMERCIAL use only, provided that use is solely by you as an individual, does not generate revenue or support any commercial or professional activity, and does not involve redistribution, sublicensing, sale, or sharing of the Software; personal use includes private hobby projects, personal productivity, and learning with no commercial benefit.
Any COMMERCIAL use requires prior written permission from the Licensor, including but not limited to use within a business, organisation, or institution; in products, services, or client work; for internal business operations, tools, or workflows; by freelancers or contractors in paid work; integration into commercial products; or providing services for a fee; commercial licenses are granted at the Licensor's sole discretion and may involve fees and additional terms; to obtain a license, contact hello@multilarm.com with the subject "Multilarm Commercial License Enquiry."
Redistribution of the Software, in whole or part, in any form, is strictly PROHIBITED without prior written consent.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT, AND THE LICENSOR MAKES NO GUARANTEES REGARDING RELIABILITY, ACCURACY, OR SUITABILITY. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSOR SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING LOSS OF DATA, BUSINESS INTERRUPTION, OR LOSS OF PROFITS) ARISING FROM USE OR INABILITY TO USE THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
This license remains effective until terminated and terminates automatically upon breach; upon termination, you must cease all use and destroy all copies. This Agreement is governed by applicable law, and disputes are subject to the exclusive jurisdiction of the courts at the Licensor's registered location. For licensing enquiries or questions, contact hello@multilarm.com.
By using the Software, you acknowledge that you have read, understood, and agree to be bound by this Agreement. © 2026 Ali Muhammad · multilarm.com. All Rights Reserved.
Contact
When reporting bugs, please include Multilarm.error.log and Multilarm.config.xml.
Help on any pageEvery page on
multilarm.com has a
Help button in the bottom-right corner. It opens
Multilarm Assist:
browse help topics, ask a question in plain English, or follow a step-by-step fix for the common
problems (device offline, no sound, claim codes, wrong bell or prayer times, text-to-speech,
updates, billing). It answers from the documentation inside your own browser — instant, private,
no AI service involved — and if it can't help, the same window emails support with your
conversation attached. Signed in to the Cloud Dashboard, that email also carries an automatic
diagnostic snapshot of the device you select.
The same assistant is built into this
manual — the Help button in the corner of this page — and works with no internet at all.
Spelling mistakes are fine, it starts suggesting matching questions once you have typed a few
letters, it offers alternatives when your question could mean two things, and a short follow-up
(
“what about Windows?”) keeps its place in the conversation.
After you write to usHowever you get in touch —
contact form, Help window, or email to hello@multilarm.com — you receive an
immediate acknowledgement with a reference number and a few links that often
answer the question straight away. Quote that reference if you write again, and reply within the
same email thread so the whole conversation stays together. A small, fixed list of very
common questions (where to download it, is there a phone app) may be answered automatically and
immediately, so you are not left waiting a day for a link — any such reply says plainly that it
was sent automatically. Everything else is written and checked by a person,
normally within one working day; anything about pricing, licensing, safety, security or your
account always is. Ask for a human in your message and you will get one.
Support the project
Multilarm is built and maintained by one person. If it serves your community, a donation helps keep it going — PayPal @AliMuhammadK62.
Acknowledgements
With thanks to my wife and children, to the sites hosting the Adhan and bleep recordings, and to the open-source projects behind the engine — miniaudio, libFLAC, Kokoro and OpenPhonemizer.
This help file documents Multilarm v1.2026. It is generated from the current source and supersedes earlier Multilarm_Documentation revisions. © 2026 Ali Muhammad · multilarm.com.
Getting Started
Downloads
Where to get Multilarm, its resources and its tools. The official home for all downloads is multilarm.com/multilarm.
Application binaries
| Platform | Package |
| Windows | Windows x64 binaries |
| Linux (ARM 32-bit) | Linux ARM 32-bit binaries |
| Linux (ARM 64-bit) | Linux ARM 64-bit binaries |
| Resources | Zipped resource file (Adhan, bleep, ambient, TTS audio) |
Standalone binary?If you download the executable + resource ZIP instead of the installer, extract the resources into the program's root directory. multilarm_audio.dll already ships next to Multilarm.exe, so no system-folder copy is needed.
Installer
- Windows (MSI): the recommended Windows install — bundles the .NET runtime, the audio engine and the default audio library. See Installation.
- Linux / Raspberry Pi: the one-line installer
sudo curl -s -L https://bit.ly/multilarm-linux | bash.
Configuration & samples
- Configuration Generator —
MultilarmConfigGenerator.html, the visual editor (see Configuration Generator).
- Default config —
Multilarm.config.xml with the UK (Sheffield) prayer calendar, for the application root.
- Bangladesh prayer times — drop-in
Multilarm.config.xml for the application root.
- Sample applications — the daily / weekly / monthly worked deployments (see Sample Deployments).
Checking that a download is genuine
Multilarm is only ever distributed from multilarm.com, winget and Chocolatey. A copy from anywhere else is not ours, whatever it is called.
Every release file is listed with its SHA-256 fingerprint at multilarm.com/multilarm/SHA256SUMS. That list carries a digital signature of its own, made with a key kept off the web server — so even someone who broke into the website could not publish matching fingerprints for a tampered file.
To check a file you have downloaded:
- Windows:
certutil -hashfile Multilarm.exe SHA256
- Linux / Raspberry Pi:
sha256sum -c SHA256SUMS
Fingerprint does not match?Do not run the file. Delete it and email hello@multilarm.com — a mismatch means the copy did not come from us.
“The Multilarm program file has changed”
Multilarm checks its own program file every time it starts. If the file has changed and it was not one of our updates, it records a fault, shows it on the Diagnostics page (check D-A12) and in the Cloud Relay dashboard, and emails the account owner.
Multilarm keeps running when this happens. A public address system that refused to start because a file changed would leave a building without its announcements, and that is the worse outcome — the message exists so that somebody looks, not to stop the system.
If you did not replace the program yourself, treat that device as untrusted: reinstall from multilarm.com (checking the fingerprint as above), change the device's login password, and rotate its Cloud Device Token from the dashboard.
Two-factor authentication on your Cloud account
Your Cloud Relay account can play audio in your building, so anyone who learns its password can too. Two-step verification removes almost all of that risk and takes about a minute to set up.
In the dashboard, click 2FA in the top bar, then Start setup. You will need an authenticator app — Google Authenticator, Microsoft Authenticator, Aegis, 1Password and Bitwarden all work. Choose “enter a setup key” in the app, type in the key shown, then enter the 6-digit code the app produces. Nothing is switched on until that code is accepted, so a mistyped key cannot lock you out.
You are then shown ten recovery codes. Write them down and keep them somewhere other than your phone; each works once, and they are the way back in if the phone is lost. Only hashes are stored, so they genuinely cannot be recovered for you.
Devices are unaffectedYour Multilarm devices keep running to schedule whether or not anyone is signed in to the dashboard. The cloud is for control and reporting — it is never something your alarms depend on.
Rotating a device token
A device token is the credential that lets the Cloud Relay control that device. If it has been shared too widely, an installer has moved on, or you suspect a device has been tampered with, replace it: in the dashboard, find the device and click 🔄 Rotate token.
The old token stops working straight away. The device shows as offline until you set the new token on it — press Ctrl+K at its console, or use the ready-made --cloud-token command the dashboard offers.
Alarms keep runningA device with no working cloud token still plays its full schedule. Rotating a token costs you remote control until you finish the job — it never costs you the announcements.
Reference
API & Command Reference
The complete request/response reference for the LAN Web Remote REST API, the Cloud Relay command set, and the cloud batch API. All responses are JSON (UTF-8).
Web Remote REST API
Base URL: http://<multilarm-ip>:<port>/api/ (default port 6580). Endpoints honour Basic Auth and the rate limit when configured — see Web Remote Security.
GET /api/status
Current status.
curl http://192.168.1.50:6580/api/status
{
"uptime": "0.03:22:15",
"playing": "0103.mp3",
"radio": "ON - playing",
"volume": "75%",
"volumeRaw": 0.75,
"volumeProfile": "ACTIVE (30%)",
"activeConfig": "Multilarm.config.xml",
"cloudRelay": "ON - connected",
"firewall": "auto",
"webRemoteAuth": "basic",
"alarmInfo": "Next: Dhuhr at 12:11",
"hardware": { "relayEnabled": false, "relayAvailable": false,
"emergencyEnabled": false, "emergencyArmed": false,
"emergencyActive": false }
}
GET /api/config
All configuration fields as key/value pairs.
curl http://192.168.1.50:6580/api/config
{ "AlarmFadeInMs": "0", "CrossfadeMs": "1500", "PlayRadio": "Local",
"Volume": "0.75", "WebRemoteEnabled": "True", "WebRemotePort": "6580", ... }
POST /api/config
Update one or more fields (send only the ones you change). Persisted to the active config file and applied immediately (except restart-required fields).
curl -X POST http://192.168.1.50:6580/api/config \
-H "Content-Type: application/json" \
-d '{"Volume":"0.5","VolumeProfileRules":"22:00-07:00,volume=0.3"}'
{ "ok": true, "updated": 2 }
POST /api/skip
Skip the current radio track. { "ok": true }
POST /api/test-alarm
Play a random alarm at full priority. Optional body {"folder":"…"} to choose a specific alarm folder.
curl -X POST http://192.168.1.50:6580/api/test-alarm \
-H "Content-Type: application/json" -d '{"folder":"C:\\Multilarm\\Adhan"}'
{ "ok": true }
POST /api/test-radio
Play a random radio file for 10 seconds. { "ok": true }
POST /api/volume
Set master volume (decimal 0.0–1.0).
curl -X POST http://192.168.1.50:6580/api/volume \
-H "Content-Type: application/json" -d '{"volume":0.6}'
{ "ok": true, "volume": 0.60 }
GET /api/logs
The console log buffer (up to 500 timestamped strings).
[ "[08:00:01] Loading schedule for 10/04/2026",
"[08:00:01] AlarmPath [C:\\Adhan]: 42 file(s) found",
"[08:15:30] Radio: Now playing Track01.mp3", ... ]
GET /api/schedule
Today's remaining schedule — time, label (from TextData) and minutes until each alarm. Useful for external countdown displays and home-automation dashboards.
[ {"time":"12:11","label":"Zuhr","minutesUntil":45},
{"time":"15:44","label":"Asr","minutesUntil":258},
{"time":"18:30","label":"Maghrib","minutesUntil":423} ]
GET /api/alarm-folders
Configured alarm folders (from AlarmPath).
[ "C:\\Multilarm\\Adhan", "C:\\Multilarm\\Bleep" ]
POST /api/speak
Speak arbitrary text immediately (queued at Priority 3). Body {text, engine?} where engine is "Builtin", "Kokoro" or empty (device default). Returns HTTP 400 if text is empty. See Text-to-Speech.
POST /api/voice/upload
Accept a complete audio file (e.g. from the browser-mic path) and store it as voicememo.wav. Accepted content types: audio/wav, audio/mpeg, audio/ogg, audio/aiff. Max body 16 MB.
curl -X POST http://192.168.1.50:6580/api/voice/upload \
-H "Content-Type: audio/wav" --data-binary @memo.wav
{ "ok": true, "size": 283964 }
Errors — 400: empty body, unrecognised format, or > 16 MB · 429: rate limit (30 POSTs / 60 s / IP) · 401: Basic Auth required/incorrect.
GET /api/hardware/status
Live Multilarm Box hardware state — relay availability, per-channel open/closed, emergency state. Channel rows reflect the fixed map even while the board is unplugged.
curl http://192.168.1.50:6580/api/hardware/status
{ "relay": { "enabled": true, "available": true, "port": "/dev/ttyUSB0",
"protocol": "A0", "lastError": "",
"channels": [ {"id":1,"name":"Bell","open":false},
{"id":2,"name":"ZoneMuteA","open":false},
{"id":3,"name":"ZoneMuteB","open":false},
{"id":4,"name":"EmergencyLed","open":false} ] },
"emergency": { "enabled": true, "available": true, "gpioPin": 17,
"active": false, "lastTriggeredUtc": null },
"audio": { "playbackDevice": -1 } }
POST /api/hardware/relay/pulse · /open · /close
Manual relay control. Body {"channel": 1-4}; pulse also accepts "durationMs" (default 1000, clamped 100–60000). Returns {"ok":true}, or {"ok":false,"reason":"relay board not connected"} when the board is absent — never an error page.
curl -X POST http://192.168.1.50:6580/api/hardware/relay/pulse \
-H "Content-Type: application/json" -d '{"channel":1,"durationMs":1500}'
POST /api/hardware/bell
Ring the bell manually — same code path as a scheduled BellScheduleRules fire (so it proves the production chain). Optional body {"durationMs":1500}.
POST /api/hardware/emergency/test · /reset
test triggers a full emergency broadcast (looped audio at top priority, schedule suppressed, LED on) with no hardware needed; reset clears it. No body required. Both are audit-logged. Warn anyone in earshot before testing.
Other endpoint families
| Family | Endpoints |
| Voice Broadcast | GET /api/voice/status · POST /api/voice/record · /stop · /preview · /transmit (requires body {"confirm":true}, else HTTP 400) · /discard |
| Push-to-Talk | POST /api/ptt/start · /stop · /keepalive · GET /api/ptt/status |
| File Manager | GET /api/files/roots · GET /api/files?root=<label>&sub=<rel> · POST /api/files/upload (raw body + X-Root-Label/X-File-Name headers) · POST /api/files/delete ({root,name,confirmTts?}) |
| Triggers | POST /api/trigger (JSON) · POST /api/trigger-audio (multipart) — see Trigger API |
Cloud Relay commands
Each device polls GET /api/poll.php every 5 s with its X-Device-Token header; the server returns zero or more pending commands. After any state-changing command the device immediately pushes a status update (in addition to the 30-second heartbeat). On error it backs off exponentially (5s → 10s → 20s → 40s → 60s cap). Recognised actions:
| Action | Payload & effect |
skip | none — skip current radio track |
test-alarm | optional folder path; else random folder from AlarmPath |
test-radio | none — random radio file for 10 s |
volume | decimal 0.0–1.0 — set master volume |
get-config | none — returns full config as the result |
update-config | JSON field updates; applied + persisted to the active file |
get-status | none — same JSON as GET /api/status |
get-schedule-range | returns a multi-day schedule view |
speak-text | {text, engine?} — immediate spoken playback (P3) |
voice-record / voice-stop | start / stop server-side Voice Broadcast recording |
voice-preview | play the current recording locally (not mirrored) |
voice-transmit | requires {"confirm":true} — broadcast at P1 over playback + mirrors |
voice-discard | drop the current recording |
get-voice-status | returns {isRecording, hasRecording} |
voice-upload | carries {upload_id, filename, size, mime}; device fetches the staged blob (single-shot, 5 MB) on next poll |
files-roots / files-list / files-upload / files-delete | Audio File Manager — see File Manager |
tts-fill / tts-voices | TTS word-fill & available neural voices |
ptt-start / ptt-stop / ptt-status / ptt-keepalive | device-local Push-to-Talk |
ptt-stream-start / ptt-stream-stop / ptt-stream-status | browser-mic-over-cloud PTT streaming |
talkback-status / talkback-answer / talkback-decline / talkback-hangup / talkback-keepalive / talkback-chunk | Talkback call control. Note there is no talkback-request: a call can only ever be started at the device |
trigger-fire / trigger-audio | External Trigger API actions |
Cloud batch command API
POST /api/batch-command.php — send one command to several devices at once. Requires a logged-in session; all target devices must belong to the authenticated user (ownership is verified before anything runs — all-or-nothing).
// request
{ "device_ids": [1, 3, 5], "action": "volume", "payload": "0.5" }
// response
{ "ok": true, "command_ids": { "1": 42, "3": 43, "5": 44 } }
Allowed batch actions: skip, test-alarm, test-radio, volume, update-config. Errors — 405: not POST · 400: missing/invalid device_ids or action · 403: a device isn't owned by the user.
Cloud console log lines
The relay logs to the console (cyan) and Multilarm.error.log: "Cloud relay started", "Cloud: Connected to …", "Cloud: Disconnected — …", "Cloud: Received N command(s)", "Cloud: Command [action] completed", "Cloud: Status pushed". Use Ctrl+I for the live relay status.