Skip to content

Tail Messages

Larry K. Aycock edited this page Aug 9, 2026 · 3 revisions

Tail Messages

Unkey-triggered, reactive to node activity.

Features

  • Reliable unkey detection — uses the Asterisk Manager Interface (AMI) for real-time, event-driven unkey detection that fires at the actual unkey (before the courtesy tone), giving a seamless native-feel tail message; falls back to the legacy rpt stats kerchunk counter if AMI credentials aren't available
  • Network keyup support — with AMI active, the optional NetworkKeyupTrigger setting also fires tail messages after a connected AllStar node unkeys (not just local RF)
  • Rotating messages — cycles through a list of announcement files in order with a configurable minimum interval between plays
  • SkywarnPlus WX integration — when weather alerts are active, plays the SkywarnPlus wx-tail.wav file instead of the normal rotation (WX always takes priority) — see SkywarnPlus Integration below
  • Optional day/time-window gating per entry — a rotation entry can be restricted to specific days of the week and/or a time-of-day window (e.g. a net-announcement tail message that's only eligible Tuesday evenings); entries without gating stay eligible all the time
  • Per-entry enable/disable — disable individual rotation entries without removing them (herald toggle-rotation <name> or the web UI Status toggle); disabled entries are skipped during the unkey cycle
  • Reorderable rotation — move a rotation entry earlier/later in the cycle from the web UI or CLI, no remove-and-re-add needed
  • Adjustable TTS speech speed — a 0.5x2.0x slider per entry in the Add/Edit form, see Voices & TTS Engines

Entries can be edited in place (name, text, voice, schedule, speed) via herald edit-rotation or the web UI, instead of removing and re-adding.

Node targeting for multinodes= setups: any rotation entry can optionally carry a Node override, targeting a specific node number for playback instead of the daemon's own configured Node.

How It Works

herald uses the AMI (loopback, no internet involved) for real-time unkey detection, polling XStat every 0.5 seconds and watching for the RPT_RXKEYED variable to transition from 1 to 0 — this fires at the actual unkey, before the courtesy tone. When NetworkKeyupTrigger is enabled, it also polls SawStat and fires on any connected node's PTT transitioning 1 to 0. If AMI credentials aren't available, the daemon falls back to polling the rpt stats kerchunk counter (local RF unkeys only, fires after the courtesy tone).

When an unkey is detected, the daemon checks in priority order:

  1. Minimum interval — if not enough time has passed since the last tail message, skip
  2. Scheduled announcement in progress — if one just started playing, skip this unkey; it isn't counted against MinInterval, so the tail message simply retries on the next unkey
  3. SkywarnPlus WX alert — if the wx-tail.wav file is larger than SilenceThreshold bytes, an alert is active
  4. Rotation — otherwise, play the next eligible file in the rotation list (skipping any with Days/TimeStart/TimeEnd gating that doesn't currently match) and advance the index

A newly-appeared or changed WX alert always plays immediately, taking priority over the rotation. But a persistent alert (unchanged since it last played) alternates with the rotation on each unkey instead of playing every single time, so a long-running alert doesn't shut the rotation out entirely. As soon as the alert changes or a new one appears, it jumps back to the front of the line.

SkywarnPlus Integration

No changes to SkywarnPlus are required; Herald just reads the existing wx-tail.wav file SkywarnPlus already generates:

  • No active alerts: wx-tail.wav is a small silent file (~1644 bytes)
  • Active alerts: wx-tail.wav contains the weather alert audio (typically 50KB+)

Set SilenceThreshold: 5000 (the default) to reliably distinguish between the two. This works with any classic SkywarnPlus install (any fork, or the original archived Mason10198/SkywarnPlus upstream).

SkywarnPlus-NG integration

SkywarnPlus-NG is a separate, independent rewrite with its own tail-message file (silent when clear, TTS'd alert audio when active — default path /var/lib/skywarnplus-ng/data/wx-tail.wav). Point WxTailFile straight at that path — this is also WxTailFile's new default, so a fresh install already matches NG's default location.

The one thing NG does differently: it rewrites that file on every poll cycle regardless of whether the alert set actually changed (classic SkywarnPlus only rewrites on a real change). Since the WX/rotation alternation relies on the file's mtime to know "is this genuinely new," that would make WX replay on every unkey instead of ever alternating with rotation. So when running NG, also set:

TailMessage:
  SkywarnPlus:
    Enable: true
    WxTailFile: /var/lib/skywarnplus-ng/data/wx-tail.wav   # matches NG's own default
    NGEnable: true
    NGApiBase: http://127.0.0.1:8100   # NG's local dashboard API - default port
    NGPollIntervalSec: 30

With NGEnable on, Herald separately polls NG's local /api/alerts on its own schedule purely to detect a genuine change in the active-alert set, and uses that instead of the file's mtime for the alternation decision. Leave NGEnable off if you're on classic SkywarnPlus.

There is no dedicated Time & Weather weather provider for reading a classic SkywarnPlus install's already-fetched data — use a TimeWeather.Weather.Provider directly (see Time & Weather Announcements) if you want weather announcements independent of your SkywarnPlus setup. If you're running NG and also ASL3-SkywarnPlus-NG-Bridge for Allmon3/Supermon alert panels, set TimeWeather.Weather.SnapshotEnable: true so Herald writes the current-conditions snapshot that bridge's Allmon3 panel reads.

herald Commands

Command Description
sudo herald add "<text>" [--name <name>] [--voice <voice>] [--days daily|d1,d2] [--time-start HH:MM] [--time-end HH:MM] [--node <n>] Generate TTS WAV and add to rotation
sudo herald add-file <path> [--name <name>] [--days daily|d1,d2] [--time-start HH:MM] [--time-end HH:MM] [--node <n>] Copy an existing WAV into rotation
sudo herald edit-rotation <name> [--new-name <n>] [--text "<text>"] [--voice <v>] [--file <path>] [--days ...] [--time-start HH:MM] [--time-end HH:MM] [--node <n>] Edit an existing rotation entry in place
sudo herald reorder-rotation <name> <up|down> Move a rotation entry earlier/later in the cycle
sudo herald toggle-rotation <name> Enable or disable a rotation entry without removing it
herald list List rotation + scheduled announcements (flags entries with a missing file)
sudo herald remove <name> Remove a rotation entry
sudo herald play <name> Test-play an announcement on the node immediately (always local)

--days/--time-start/--time-end restrict a rotation entry to specific days-of-week and/or a time-of-day window; leave unset for an entry that's always eligible. --node targets a specific node number instead of the daemon's configured Node.

Example Config

TailMessage:
  Enable: true
  MinInterval: 300
  NetworkKeyupTrigger: false
  Rotation:
    - /etc/asterisk/scripts/herald/announcements/tail-messages/tail1.wav
  SkywarnPlus:
    Enable: true
    WxTailFile: /var/lib/skywarnplus-ng/data/wx-tail.wav
    SilenceThreshold: 5000

See Configuration Reference for every field.

Clone this wiki locally