Emergency Multicast Alert

Frogman tool: fm_sangoma_emergency_alert · Sangoma / DPMA / P-Series

Summary

One Frogman command broadcasts a pre-recorded message to every Sangoma / DPMA P-Series phone listening on a named multicast paging zone. Phones auto-answer and play the recording at full volume regardless of state (idle, on-call — via interrupt level). Designed for K-12 lockdown / shelter-in-place / fire announcements where SIP unicast paging cannot scale.

Tool
fm_sangoma_emergency_alert
Permission
write (requires confirm: true)
Trigger surfaces
CLI, HTTP API, MCP, chat parser, GraphQL
Replaced
PushLockdown.php + PushSangomaZone.php (orphans removed; had flite/sox shell-outs)
Status
Built, in-walls verified, send-leg verified on dev box. End-to-end audio requires LAN test.

How it works

Architecture flow

Chat / CLI / MCP Frogman tool Recordings BMO + endpoint_multicast AMI Originate Asterisk MulticastRTP LAN multicast (239.x.x.x) P-Series phones

What the tool does, step by step

  1. Resolve recording. Accepts a System Recording id or display name. Looks it up via \FreePBX::Recordings()->getAllRecordings() and pulls the filename (e.g. custom/ldown).
  2. Resolve zone. Reads the named zone from the EPM endpoint_multicast table. Defaults to LOCKDOWN. Returns the IP, port, codec, priority, and interrupt level.
  3. Dry-run preview. Without confirm: true, returns a structured preview showing exactly what would happen.
  4. Fire. With confirm: true, issues an AMI Originate:
    Channel: MulticastRTP/basic/<ip>:<port>
    Application: Playback
    Data: <recording filename>
    Async: true
  5. Asterisk emits the RTP stream to the configured multicast group. P-Series phones whose DPMA-provisioned config subscribes them to that group auto-join the IGMP group on boot, hear the stream, and play it.

Where the parts live

PieceLives inManaged by
Multicast zones (IP/port/codec)endpoint_multicast DB tableHuman, via FreePBX EPM GUI
Phone subscription (which zones to listen on)/etc/asterisk/dpma/phone_configs/<ext>-1/<MAC>.cfgEPM template → DPMA, on rebuild
System recordingsrecordings DB table + /var/lib/asterisk/sounds/<lang>/custom/Human, via FreePBX System Recordings GUI
Send channel driverchan_multicast_rtp (built into Asterisk)Asterisk core
Frogman toolTools/SangomaEmergencyAlert.phpFrogman

Why LAN-only

Multicast (224.0.0.0/4) is a network-layer feature designed for one-to-many delivery on a shared broadcast domain. Three independent reasons it cannot work over a WAN / cloud setup:

  • ISPs do not route multicast. Default consumer and cloud-provider routing tables drop 239.x.x.x packets. Multicast routing (PIM, MSDP) requires explicit configuration that no commercial ISP offers to standard customers.
  • Multicast does not survive NAT. The 1:N delivery model has no concept of source/destination port translation. Carrier-grade NAT, home routers, and SBCs all drop or fail to forward multicast.
  • Phones must be in the same IGMP-aware L2 segment. A managed switch with IGMP snooping forwards multicast to subscribed ports; without that, the multicast group either floods or is silently dropped depending on the switch.
What this means for K-12 deployment: the natural fit. Schools run an on-prem PBX or an on-campus FreePBX appliance, and the phones are on the same campus LAN (often VLANed by classroom). Multicast paging works perfectly in that topology.
What this means for cloud-hosted PBX with remote phones: multicast paging is not viable. SIP unicast paging (the standard \FreePBX::Paging() module) is the only option, and it caps out around tens of phones before SIP fanout overhead becomes painful.

One-time setup (human, via FreePBX GUI)

Frogman cannot create multicast zones — there is no public BMO setter and CLAUDE.md rule 3 forbids writing to other modules' tables. This is a single-step setup per zone, done once per template.

  1. FreePBX admin → ConnectivityEndpoint Manager
  2. Open the relevant template (e.g. digium_default for Sangoma P-Series)
  3. Multicast tabAdd Zone
  4. Recommended values for LOCKDOWN:
    Name
    LOCKDOWN
    IP address
    239.255.0.1 (site-local multicast range)
    Port
    10000
    Codec
    PCMU (G.711 µ-law — universally supported)
    Priority
    1
    Interrupt level
    2 (interrupts active calls)
  5. Save, then Rebuild Configs
  6. Phones reload on next DPMA poll, or run asterisk -rx "digium_phones reconfigure phone <ext>-1" to force immediately
  7. Optionally add additional zones: FIRE (239.255.0.2), SHELTER (239.255.0.3), ALL_CLEAR (239.255.0.4) on subsequent ports

Then create your System Recording: AdminSystem RecordingsAdd Recording. Record a short clear message (8–15 seconds typical for a lockdown announcement).

Commands

Chat (preferred)

list multicast zones
emergency alert <recording>
emergency alert <recording> on zone <zone>
lockdown <recording>

CLI

# list zones
fwconsole frogman:tool fm_list_sangoma_multicast_zones '{}'

# dry run (preview)
fwconsole frogman:tool fm_sangoma_emergency_alert '{"recording":"Lockdown"}'

# fire (with confirm)
fwconsole frogman:tool fm_sangoma_emergency_alert '{"recording":"Lockdown","confirm":true}'

# fire to a non-default zone
fwconsole frogman:tool fm_sangoma_emergency_alert \
  '{"recording":"Fire Alarm","zone":"FIRE","confirm":true}'

HTTP / MCP

Tool auto-registers in the Frogman HTTP catalog and MCP server — same parameter shape as the CLI invocations above.

Diagnostic CLI (Asterisk)

# confirm channel driver loaded
asterisk -rx "core show channeltypes" | grep MulticastRTP

# verify what DPMA wrote to a phone's config
grep -A2 multicastpage /etc/asterisk/dpma/phone_configs/101-1/*.cfg

# force a phone to reload its config
asterisk -rx "digium_phones reconfigure phone 101-1"

# direct-fire test (bypasses Frogman, uses stock sound)
asterisk -rx "channel originate MulticastRTP/basic/239.255.0.1:10000 \
  application Playback demo-congrats"

In-walls routing analysis

CLAUDE.md hierarchy: BMO → AMI → GraphQL → direct DB read. Each piece of this tool maps to a documented surface; no shell-outs, no fwconsole, no writes to another module's tables.

ActionSurfaceRule
List system recordings\FreePBX::Recordings()->getAllRecordings()BMO Rule 1 — in walls
Resolve recording filename\FreePBX::Recordings()->getFilenameById()BMO Rule 1 — in walls
Originate multicast call$astman->Originate(...)AMI Rule 2 — in walls
Read multicast zonesSELECT FROM endpoint_multicastDB read Rule 4 — documented exception (no BMO surface)
Why the DB read is fine. CLAUDE.md rule 3/4 explicitly allows reads of other modules' tables when no BMO method exposes the data. Recon confirmed zero PHP code in EPM touches endpoint_multicast — the GUI is the only writer; everything else is client-side JavaScript. Frogman never writes to this table.
What was removed. The previous orphan PushSangomaZone.php shelled out to flite and sox for TTS rendering. Both shell-outs were flagged by the wall-audit. The new tool drops TTS entirely — emergency announcements should be pre-recorded for clarity and brand consistency anyway.

Troubleshooting checklist

If the dry-run preview is correct but phones stay silent, work through this list:

  • Are PBX and phones on the same LAN? Compare ip -4 addr on the PBX with the contact IP from asterisk -rx "pjsip show contacts". If different public IPs, multicast cannot reach them. (This is the dev box state.)
  • Is the channel driver loaded? asterisk -rx "core show channeltypes" | grep MulticastRTP. Should show MulticastRTP — Multicast RTP Paging Channel Driver.
  • Did the channel actually go up? Right after fire, run asterisk -rx "core show channels". You should see a MulticastRTP/<ip>:<port> channel in Up state running Playback(...).
  • Does the phone's config include the zone? grep -A2 multicastpage /etc/asterisk/dpma/phone_configs/<ext>-1/*.cfg. If the <multicastpage> block is empty, EPM did not push the zone — revisit the template's Multicast tab and Rebuild Configs.
  • Has the phone reloaded since the zone was added? asterisk -rx "digium_phones reconfigure phone <ext>-1" sends an immediate reconfigure message. Without it, phones pick up the new config on next DPMA poll (minutes).
  • Is the LAN switch IGMP-aware? Some unmanaged switches flood multicast (works but loud); some managed switches with IGMP snooping but no querier silently drop it. tcpdump -i <iface> host 239.255.0.1 on the phone-side network confirms whether packets arrive.
  • Codec mismatch? Recording must transcode to PCMU. soxi /var/lib/asterisk/sounds/en/custom/<file>.wav — 8 kHz mono works directly; higher rates transcode automatically; weird codecs may fail silently.

Limitations & next steps

Current limitations

  • LAN-only (architectural; not fixable in code).
  • Sangoma / DPMA P-Series only. Yealink / Polycom / Grandstream multicast subscriptions need a separate EPM template tab and are not currently exposed by this tool. Cross-brand support is a future build.
  • One-shot fire. No "ongoing" multicast stream, no scheduled drills, no all-clear chaining yet. Each invocation plays one recording end-to-end.
  • No screen takeover. Audio only. DPMA can push a "Lockdown" overlay via SIP NOTIFY but that's a separate composition layered on top — future build.

Roadmap candidates

  • Flic2 button integration. Wireless physical panic buttons (flic.io/flic2). Trigger fm_sangoma_emergency_alert via Flic Hub HTTP webhook → Frogman API token → instant lockdown without dialing. Natural fit for classroom deployment.
  • Phone App soft-key panic button. Sangoma-only XML app pinned to a soft key on every P-Series phone. Single press, optional confirm screen, fires the same tool.
  • Scheduled drills. Cron-style schedule using the existing Frogman scheduling surface to drive monthly fire / lockdown drills with a different "drill" recording.
  • SIP NOTIFY screen takeover. Layer PJSIPNotify AMI action on top to push a lockdown screen overlay alongside the audio.
  • All-clear chaining. A second tool fm_sangoma_all_clear that fires the all-clear recording on the same zone and clears any screen overlay.