Mast for ham radio
Get paged for the spots you can't miss, and know when the shack Pi goes down.
Mast Pager is a pager app for iPhone and Android, and it's free: no price, no account, no subscription, no server to run. For a ham it does two jobs. A spot from HamAlert, the free service that watches the cluster, the RBN, SOTAwatch and pota.app for you, rings the phone even on silent and keeps ringing until you answer. And the Pi in the shack checks in with Mast every minute, so you find out when it goes down instead of finding out the next time you key up.
when the HamAlert app isn't enough
HamAlert's own app is free and it works. Use it for the everyday stuff. The problem is the one spot you need, an ATNO or a park you've been after all year. It shows up at 3 a.m., or while the phone is face down on the bench on silent. The notification pops once and sits there. You see it an hour later and the station is QRT.
| When the spot lands and… | HamAlert app | Mast |
|---|---|---|
| the ringer switch is off | silent banner | rings anyway on a page |
| you don't hear the first one | that was it | rings again every 60 s until you tap Acknowledge |
| you sleep through it | nothing | a second phone in the house starts ringing too, iPhone or Android (Mast calls it the escalation partner) |
| you want to know it was delivered | you don't | the alert screen shows fired, delivered, repeated, acknowledged |
Keep the everyday triggers on HamAlert's push. Send the few you'd get out of bed for to Mast. Every trigger has its own tick-boxes for what to do with a match: the app, Threema, telnet, URL. So both can be on at once.
what it costs: nothing
Mast Pager is free on the
App Store
and on
Google Play,
for iOS 17.2 and Android 12 or later.
Install it and make a channel; there's nothing to sign up for. On its own the app gives you
2 channels, 1 vital and 1,000 messages a month, and everything on this page fits in that:
a spots channel for HamAlert and a shack-pi vital for the Pi.
Heartbeats don't count as messages, so a Pi checking in every minute uses none of the 1,000. Only the spots that page you do, and 1,000 a month is about 30 a day.
If you want more, a free Tissue Mast account takes an email address and no credit card: 10 channels, 10 vitals and 10,000 messages a month, and your channels survive a reinstall or a new phone. You sign in from the app's settings and nothing you've set up changes. The paid plans are for checking websites every 30 seconds and for teams taking turns on call. Nothing on this page needs one.
how a spot gets to your phone
There's nothing to build. When a trigger matches, HamAlert can fetch a web address of your choosing. In Mast, every channel has a web address of its own; a channel is one pager line with its own name, its own volume and its own address. Paste the Mast address into HamAlert and you're done.
HamAlert watches the spots. Mast rings the phone. You supply the address in the middle.
setup, about ten minutes
You need Mast Pager on your phone (free, on iPhone or Android, no sign-up) and a HamAlert account with at least one trigger set up. If you already get HamAlert pushes, you're halfway there. Three things to do: make a channel, decide how hard it should ring, and paste the address into HamAlert.
Make a channel in Mast
In Mast, go to Channels and tap New channel. Call it spots.
Set Highest priority to Page. That's the loudest the channel is allowed
to get; if you leave it lower, the page you ask for in the next step gets turned down to
match. The send address shows up as soon as the channel exists. Tap Copy.
spots
loud · 12 messages
Send address
Anyone with that address can page you. Treat it like a password. If it gets out, rotate it in the app; the old one keeps working for seven days so nothing breaks mid-swap.
Tell it how hard to ring
When a spot matches, HamAlert fetches your address with the spot details added on the end. Anything you've already written on the end stays there, and that's where you say how loud, how often, and for how long. Paste the address from Mast into the box, pick how you want to be woken, and copy the result.
Address builder
page rings through a silent phone. On an iPhone that takes one switch in the
app's Settings, Critical alerts → Turn on, off until you press it; after that a
page also gets past Do Not Disturb. On Android it
plays the pager tone on the alarm stream, so vibrate and silent don't stop it, and the
Waking you row in the app's Settings checks the rest for you, with a
Fix button for each thing that would keep a page quiet. A page always has to be
acknowledged, meaning you tap it to say you've seen it, so that box is locked for it.
"Give up after: never" is fine for a shack alarm and wrong for a spot that's gone in half
an hour.
If you're comfortable in a terminal (Terminal on a Mac, PowerShell on Windows), you can test the address right now. If not, skip this and use HamAlert's Simulate button in step 3, which does the same job.
curl -d "title=spots works" "https://mast.tissue.dev/mk_1c9f7d3e6b4a8052d7f31e6c9b40a2e58d7c3f16?priority=page&ack=required&retry=60&expire=900"
Anything after -d reaches Mast as a field. title= is the line you
see on the lock screen, body= the smaller one under it. Mast answers with a
single line, {"ok":true,"id":"mm_…","state":"queued"}, and the phone rings a
second or two later. Tap Acknowledge to stop it.
Paste the address into HamAlert
- Sign in at hamalert.org and open Destinations.
- Under URL notifications, set the method to GET (the plain kind of web request; the other two are for programmers) and paste the address. Save.
- Open the trigger you care about. In its list of actions, tick URL next to the push one. Save that too.
- Open Simulate from the menu at the top. It's a page of its own: type in a callsign, a frequency, a mode and a source, and send it. HamAlert runs that spot past your own triggers as if it had come off the cluster, and everything ticked fires, URL included. The phone should ring in a few seconds.
That's it. Every trigger with the URL action ticked now pages you through Mast. HamAlert allows one address per account, so all of them ring the same way. If you want one trigger to page and another to just beep, that's the optional last section, two channels from one HamAlert URL.
what it looks like when it fires
HamAlert writes a one-line title for each spot and Mast shows it as is. Here's what a SOTA spot and a POTA spot look like on the lock screen.
MAST · SPOTS 03:12
SOTA HB9DQM/P on HB/BE-101 (14.062 CW)
repeating every 60s · gives up 03:27
Acknowledge Resolve Mute channel 1h
MAST · SPOTS 16:40
POTA K1ABC in US-1518 (14.0620 CW)
repeating every 60s · gives up 16:55
Acknowledge Resolve Mute channel 1h
Acknowledge is right on the notification, one tap, no unlock. Resolve next to it says it's over, and Mute channel 1h quiets that channel. On an iPhone the page also sits in the Dynamic Island until it's answered, and the Action button, Siri or a Shortcut acknowledges it without the phone leaving your pocket. Until then it comes back every 60 seconds, and a second phone, if you've set one up as escalation partner, joins in. After 15 minutes it stops. That's about right: a POTA spot ages off pota.app 30 minutes after the last respot, and a SOTA pileup is mostly done in five.
DELIVERED · 03:12 · unacknowledged
SOTA HB9DQM/P on HB/BE-101 (14.062 CW)
spots · page
Tap the notification and you get this screen: when it fired, that it reached the phone, and how long it's been ringing. Then decide if it's worth turning the rig on.
settings that decide whether it rings
Everything you put on the address can also be set as a default on the channel in the app, and the channel's ceiling always wins. Four settings to check before the first real spot.
| Setting | Where | What it does |
|---|---|---|
| Highest priority | on the channel | The ceiling. Ask for page on a channel capped at loud and you get loud. |
| Quiet hours | on the channel | Hours when normal and loud wait until morning. page ignores them. In your own time zone. |
| Escalation partner | on the channel | A second phone with Mast Pager, iPhone or Android, that starts ringing after a delay if you haven't acknowledged. Free, no account on either phone. |
| Critical alerts (iPhone) | once, in the app's Settings | Lets page ring through the silent switch and Do Not Disturb. Off until you press Turn on under this device; iOS asks once per install. If you said no, it's Settings → Notifications → Mast → Critical Alerts on the iPhone. |
| Waking you (Android) | in the app's Settings | Reads the four Android settings a page depends on, Do Not Disturb access and the alarm volume among them, and offers a Fix for each one that's off. Send yourself a test page afterwards. |
And two on HamAlert's side. Its Limits page caps you at 100 alerts an hour and won't let you switch that off, so a night of chasing can use it up. On the same page, turn on the limit for the same callsign, frequency and mode. Without it a station that gets respotted every two minutes pages you every two minutes.
POTA and SOTA: the spots that don't wait
A DXpedition is on for two weeks. A summit or park activator has a battery, a wire in a tree and maybe an hour before the weather turns. Here's how short the window is.
The spot comes with no warning. A self-spot goes out on one bar of cell signal; RBNHole posts when the skimmers hear the first CQ. By the time you notice a quiet banner, the pileup is over.
HamAlert has a condition for most of this already. Here are five triggers to start with, using HamAlert's own names for the conditions. Add the trigger, tick URL in its actions, done. The ones that mention "the bridge" need the optional last section; skip that line if you're on one channel.
Summits in my own associations · SOTA, a chaser's staple
Source = SOTAwatch, SOTA summit = the associations you can hear from
home, say W7W and W7O, plus your bands and modes. HamAlert
checks SOTAwatch every 30 seconds. QRT spots are filtered out, so they never ring you.
Title: SOTA W7ABC on W7W/LC-001 (14.062 CW). Summit name, height and points
come along as separate fields; the bridge Cell below can put them in
the body.
Summit-to-summit while I'm out · the one trigger that should page
Same conditions, no summit filter, trigger comment s2s. Turn it on the night
before, off when you're home. On the summit the phone is in the pack and the radio is in
your hand; a page is the only thing you'll notice, and on an iPhone the Action button
acknowledges it without taking the phone out. HamAlert sends the trigger comment as
triggerComment, so the bridge can send s2s to page
and the rest to loud.
An S2S is chaser points for both of you, on top of the activation.
The parks I'm still missing · POTA, by reference
Source = POTA, WWFF/POTA reference = the parks you still need.
References look like US-0001 or CA-5082. The old K-
prefix went away in March 2024. HamAlert polls pota.app once a minute and throws out
anything older than five, so the spot is fresh.
Title: POTA K1ABC in US-1518 (14.0620 CW). The park reference and name come
along as wwffRef and wwffName.
Sixteen thousand POTA spots a day will bury a pager. Filter by reference, state, or band and mode. Leave the wide-open trigger on HamAlert's push.
Park-to-park from the picnic table · POTA, while activating
Source = POTA, the band you're on, trigger comment p2p. The other
activator is on the air about as long as you are. Enable it when you set up, disable it
when you pack up.
Did my own spot go up? · the confirmation trigger
Full callsign = yours. When you self-spot on one bar, or count on the RBN to spot you, this tells you it went through. Keep it quiet, not a page. With the bridge, route it by trigger comment. Without, leave it on HamAlert's push.
Why spots come out of nowhere. Plenty of summits and parks have no cell coverage. RBNHole watches the RBN for a CQ from a call with a posted SOTA alert and spots it; POTA's RBN gateway does the same for a scheduled activation. The spot goes up the moment they start calling, and the pileup starts with it.
is the shack Pi still up?
Most shacks have a Pi doing something nobody watches. A Pi-Star or WPSD hotspot. An AllStarLink node. A Direwolf iGate, a Winlink gateway, rigctld for the remote rig, a weather station feeding CWOP. It runs for months. Then one day you key up and nothing happens. On the AllStarLink forum one owner only noticed because the LEDs were solid instead of blinking. Pi-Star users talk about hotspots that lock up about once a week. In every case someone had to walk over and look.
A Pi can't tell you it's dead. And from the outside, every way it dies looks the same. So turn it around: have the Pi check in with your phone every minute, and let the phone notice when it stops.
Mast calls that kind of channel a vital. The Pi fetches the channel's address
on a schedule, with nothing attached; that's the heartbeat. When the gap between heartbeats
runs past the period plus a grace, the phone rings: shack-pi stopped reporting.
The next heartbeat that gets through closes it and sends a quiet
shack-pi recovered. An internet blip at 4 a.m. that fixes itself leaves nothing
for you to clean up.
The heartbeat starts on the Pi and goes all the way out to the internet. One line checks the power, the SD card, the Wi-Fi, the router and the ISP at once. A watchdog that runs on the Pi and logs to the Pi tells you nothing when the Pi is the problem.
You'll need to type a few commands on the Pi. SSH in from your computer, or plug in a
keyboard and screen. On Pi-Star, the web dashboard has a terminal under
Configuration → Expert → SSH Access, and you type rpi-rw first
because Pi-Star keeps the card read-only until you do.
Make a vital, and pick how late is too late
New channel, pick Vital, call it shack-pi. Two numbers.
Heartbeat period is how often the Pi checks in. Grace is how long past
that Mast waits before it rings you. Under When it stops pick Page if a
dead hotspot should wake you up, or Loud if it can wait till morning.
When does the phone ring?
One minute is right for something you want to know about now. For a Pi on a cellular link, or a weather station that only uploads every ten minutes, hourly is fine, and that's where a new vital starts: every hour, ten minutes of grace. The grace covers the normal reasons a beat is late: slow DNS, a Wi-Fi reconnect, the router rebooting overnight. Five minutes of grace on a one-minute beat is five missed beats before anyone gets woken up.
Send the heartbeat
The heartbeat is one command, curl, which fetches a web address. It's already
on the Pi, on Windows and on a Mac. The machine's own scheduler runs it every minute. Pick
your machine:
The scheduler on Linux is called cron. Type crontab -e. The first time it asks
which editor to use; pick nano. Add this line at the bottom, then Ctrl-O,
Enter, Ctrl-X to save and leave:
* * * * * curl -fsS -m 20 https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4 >/dev/null 2>&1
The five stars mean "every minute". -fsS keeps curl quiet unless something
breaks, and -m 20 makes it give up after 20 seconds so a stuck request can't
pile up behind the next one. Nothing to install.
Windows has had curl.exe since 2018. Open PowerShell as Administrator and add
a task that repeats every minute:
$url = "https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4"
$a = New-ScheduledTaskAction -Execute "curl.exe" -Argument "-fsS -m 20 -o NUL $url"
$t = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName "Shack heartbeat" -Action $a -Trigger $t -User "SYSTEM" -RunLevel Highest
As SYSTEM it runs whether anyone is logged in or not. One minute is the
shortest repeat Task Scheduler allows. On Windows 10 and 11 the interval repeats forever on
its own; no duration needed.
launchd runs the job every 60 seconds and again on login. Save this as
~/Library/LaunchAgents/systems.tissue.mast.heartbeat.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>systems.tissue.mast.heartbeat</string>
<key>ProgramArguments</key><array>
<string>/usr/bin/curl</string><string>-fsS</string><string>-m</string><string>20</string>
<string>-o</string><string>/dev/null</string>
<string>https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4</string>
</array>
<key>StartInterval</key><integer>60</integer>
<key>RunAtLoad</key><true/>
</dict></plist>Then load it once:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/systems.tissue.mast.heartbeat.plistA sleeping Mac doesn't beat. A shack Mac wants Prevent automatic sleeping on in the Energy settings. If it does go to sleep, the vital pages you.
Watch it arrive
Within a minute the channel screen in Mast shows the last beat and how long ago, and the home-screen widget draws each vital as a dot, late ones marked. Now test it: pull the power on the Pi and wait six minutes. The phone rings. Plug it back in, and a minute or two after boot the page clears itself.
MAST · SHACK-PI04:17
shack-pi stopped reporting
No heartbeat for 360s; expected every 60s.
Acknowledge Resolve Mute channel 1h
MAST · SHACK-PI04:31
shack-pi recovered
Heartbeat received after 14m 12s.
beat only when the program is running
The Pi being up isn't the same as the node being up. There's an AllStarLink thread where
the Pi answers on the network, the web page loads, and the radio does nothing. So don't beat
just because the Pi is on. Beat because the program you care about is running. Put a check
in front of the curl, joined with &&, which means "and only
if that worked". If the check fails, no beat goes out, and the vital does the rest.
| What's on the Pi | Put this in front of the curl |
|---|---|
| Pi-Star, WPSD | systemctl is-active --quiet mmdvmhost && curl … |
| AllStarLink | systemctl is-active --quiet asterisk && curl … |
| Direwolf iGate | pgrep -x direwolf >/dev/null && curl … |
| rigctld, remote rig | rigctl -m 2 -r localhost:4532 f >/dev/null 2>&1 && curl … |
| Anything with a web page | curl -fsS -m 5 http://localhost/ >/dev/null && curl … |
Each check asks the Pi whether that program is running. The rig one in full, as it goes in the crontab:
* * * * * rigctl -m 2 -r localhost:4532 f >/dev/null 2>&1 && curl -fsS -m 20 https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4 >/dev/null 2>&1
Model 2 is Hamlib's network rig. This asks rigctld for the frequency. If
rigctld is dead or the rig is off, it fails and no beat goes out.
Let the Pi say what's wrong
A missed heartbeat says something's wrong. The Pi can also say what: send a message to
/fail on the end of the same address and Mast pages right away with your text,
no waiting for the grace. The one every Pi should have is the power check. Low voltage is
behind most of the lockups on the Pi-Star forum, and the Pi knows when it's happening.
Save this as /home/pi/power-check.sh, run
chmod +x /home/pi/power-check.sh once, and add the second line to your crontab.
#!/bin/sh
# /home/pi/power-check.sh run from cron every 5 minutes
url=https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4
t=$(vcgencmd get_throttled | cut -d= -f2)
if [ $(( t & 1 )) -eq 1 ]; then
# bit 0 = undervoltage right now. Tell Mast once, not every 5 minutes.
if [ ! -e /tmp/undervolt ]; then
curl -fsS -m 20 -X POST "$url/fail" \
-d "body=Undervoltage right now ($t). Check the supply and the cable." >/dev/null
touch /tmp/undervolt
fi
else
rm -f /tmp/undervolt
fi*/5 * * * * /home/pi/power-check.sh
A /fail with no title shows as shack-pi failed, with your text
under it. Same trick for anything the Pi can measure: disk almost full, CPU at 80 °C, SWR
from the amp, an RTL-SDR that dropped off lsusb.
Three things that stop most of these pages
-
Turn off Wi-Fi power save. It's on by default in Pi OS and it's the usual
reason a Pi drops off Wi-Fi after a while. On Bookworm:
sudo nmcli con modify "<your wifi>" wifi.powersave 2, then reboot.nmcli con showlists the names. - Give the Pi a DHCP reservation on the router. Then a router reboot can't hand it a new address.
-
Use the official power supply. A phone charger on a thin cable is the
other usual cause. If
vcgencmd get_throttledsays anything but0x0, that's it, and the script above turns it into a page.
Pi or internet? If anything else in the house can run the same one-line heartbeat, a NAS, another Pi, a Home Assistant box, give it its own vital. If both go quiet, it's the internet or the power. If only the Pi goes quiet, it's the Pi.
Remote site. A Pi at the repeater site or in a friend's barn can't be power-cycled from the couch. The vital tells you it's down. Getting it back is a smart plug, a DTMF reboot on an AllStar node, or a drive. You still find out at 04:17 and not next Tuesday.
what HamAlert puts on the address
You can stop here. One channel, one address, spots and a shack heartbeat on your phone. The two sections below are for anyone who wants different triggers to ring in different ways, and they involve a little JavaScript.
HamAlert adds every detail of the spot to the end of the address, each as a named field. Mast reads the ones it knows and ignores the rest. From HamAlert's server code, the useful ones:
| Field | Example | Mast does |
|---|---|---|
title | SOTA W7ABC on W7W/LC-001 (14.062 CW) | uses it as the alert title |
fullCallsign, callsign | W7ABC/P, W7ABC | ignores; a bridge can use them |
frequency, band, mode | 14.062, 20m, CW | ignores |
source | sotawatch, pota, rbn, cluster | ignores |
summitRef, summitName, summitPoints | W7W/LC-001, Mount Saint Helens, 10 | ignores |
wwffRef, wwffName | US-1518, the park's name | ignores (POTA references travel in the WWFF fields) |
spotter, snr, comment | K1RA-4-#, 18, the spot's comment | ignores |
triggerComment | s2s | ignores; the bridge routes on it |
The whole list is in hamutil.js in HamAlert's server. HamAlert doesn't retry. If Mast can't be reached in the second the spot lands, that spot is gone. It hasn't been a problem so far.
what tissue cloud is, and what a Cell is
The next section puts a small program between HamAlert and Mast, and that program runs on Tissue Cloud, our compute platform.
Tissue Cloud is hosting for small programs. You write a file, run one command, and a few seconds later it's answering at a web address. There's no box to keep patched, nothing to renew, nothing listening on your home line. That last one is why the bridge below can't just be another script on the shack Pi: HamAlert's servers have to be able to reach it, and a Pi behind your router isn't reachable without port forwarding, a dynamic DNS name and a certificate to keep on top of.
One of those programs is a Cell. A Cell is a folder with two files in it:
your code, and a short ribo.toml saying what it's called and what it needs. The
code is a fetch handler, which means a request comes in and you return a
response, written in plain JavaScript and running in its own V8 isolate. It imports nothing
of ours. The forty-odd lines in the next section are a whole Cell, start to finish. If you'd
sooner write Rust or C, it takes WebAssembly too.
Things attach to a Cell one line at a time in that manifest: a SQL database, a bucket for files, a secret, a schedule. The bridge below uses a secret, so the two Mast addresses aren't sitting in the code. A longer version of it could keep every spot that ever paged you in a SQL table, and serve you a page to look at them on.
Tissue Cloud's free plan
It's called Spore. Nothing to pay, no card, no trial running out, and a Tissue Mast account, if you made one, signs you in to it too. What's in it:
Requests aren't counted and bandwidth isn't metered, on any plan. CPU time is the meter, and
a spot bridge barely moves it: a few hundred spots a day works out well under one percent of
the month's allowance. Spore is a ceiling rather than a bill, so if you did somehow reach it
you'd be asked to upgrade, not charged. Custom domains start on the $5 plan and the bridge
doesn't need one; a Cell gets an address on tissue.dev the moment you deploy it.
two channels from one HamAlert URL
One address per HamAlert account is the limit you run into. Say you want the ATNO as a
page, S2S on a loud channel, and the summit name under the title.
The fix is a small program that sits between the two. On Tissue Cloud that's called a
Cell: a few lines of JavaScript that run on our servers and answer at a web address of their
own. HamAlert calls the Cell, the Cell decides which Mast channel gets the spot. About forty
lines, runs on the free plan. The two channels it sends to are the app's two free channels,
with the shack-pi vital alongside, so it still needs no Mast account.
The two Mast addresses live in the Cell's vault, the place a Cell keeps its secrets, not in HamAlert's settings and not in the code.
The manifest
Two files in a folder. The first tells Tissue Cloud what the Cell is called and which secrets it needs.
# ribo.toml
[cell]
name = "hamalert"
js = "./hamalert.js"
# The two Mast send addresses. Set both with `ribo vault set` BEFORE the first
# deploy. A vault value that isn't there yet gets stored as a placeholder
# string, and the Cell then throws on every spot instead of sending.
[[bindings]]
type = "vault"
binding = "DX_URL"
[[bindings]]
type = "vault"
binding = "S2S_URL"The Cell
The second is the program itself.
// hamalert.js
export default {
async fetch(request, env) {
const q = new URL(request.url).searchParams;
const title = q.get("title");
if (!title) return new Response("no title", { status: 400 });
// Route on the trigger comment. HamAlert sends it as triggerComment,
// joined with ", " if more than one trigger matched.
const comments = (q.get("triggerComment") || "").split(",").map((s) => s.trim());
const s2s = comments.includes("s2s");
// Body text. SOTA spots carry the summit, POTA spots the park;
// the reference for both lands in the WWFF fields on POTA's side.
const where = q.get("summitName")
? `${q.get("summitRef")} ${q.get("summitName")}, ${q.get("summitPoints")} pts`
: q.get("wwffName")
? `${q.get("wwffRef")} ${q.get("wwffName")}`
: "";
const heard = q.get("spotter") ? `spotted by ${q.get("spotter")}` : "";
const body = [where, q.get("comment"), heard].filter(Boolean).join(" · ");
const alert = new URLSearchParams({
title,
body,
priority: s2s ? "loud" : "page",
ack: "required",
retry: "60",
expire: "900",
});
// Re-spots of the same station on the same band fold into one alert.
// Only when HamAlert sent both, or every spot would share one key.
const key = [q.get("callsign"), q.get("band")].filter(Boolean).join("-");
if (key) alert.set("key", key);
const res = await fetch(s2s ? env.S2S_URL : env.DX_URL, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: alert,
});
return new Response(res.ok ? "ok" : `mast said ${res.status}`, { status: res.ok ? 200 : 502 });
},
};
The Cell waits for Mast's answer before it replies, so opening the Cell's own URL in a
browser tells you whether the send worked. HamAlert won't tell you: it fires the URL and
drops the reply on the floor, so over there the proof is still the phone ringing. The
key means a respot of the same activator on the same band updates the alert you
already have instead of ringing a new one.
Deploy it
-
Install
ribo, the Tissue Cloud command-line tool. One static binary, nothing to install alongside it. There's a Raspberry Pi build, so this can run on the shack Pi rather than your laptop if that's easier, along with builds for both kinds of Mac and for 64-bit Intel Linux; Windows runs the same line under WSL. They're all on the download page. The Pi build wants the 64-bit Raspberry Pi OS: ifuname -mdoesn't sayaarch64you're on the 32-bit image, the installer stops and names the architecture it found.curl -fsSL https://tissue.systems/install.sh | sh ribo loginopens a browser and signs the CLI in. The free plan does it: five Cells, no card.- Make a folder with the two files above and
cdinto it. -
Put the two Mast addresses in the vault, each from its own channel in the app:
ribo vault set hamalert DX_URL # prompts; paste the page channel's address ribo vault set hamalert S2S_URL # prompts; paste the s2s channel's address -
ribo deploy. The last lines give you the URL:deployed hamalert address 7f2a91c4h8k2m kind js url https://hamalert.quartz-4b.tissue.dev vault DX_URL, S2S_URL - In HamAlert, replace the Mast address with that
url. Method stays GET. Simulate.
From here, change whatever you like. Drop spots under a certain snr. Send park
spots to a third channel only when you're home. Flag a dxcc on your needed
list. It's plain JavaScript and a redeploy takes a few seconds.
how it compares with other pager apps
There are other pager apps, and one of them is probably on your phone already. The closest costs a few dollars and it's good. Five things are different here.
- The app is free. The one-time pager apps tend to charge per platform: once for the iPhone, again for Android, again for the desktop app, and their team plans run about $5 a person a month. Mast Pager costs nothing on iPhone or Android. Nothing on this page needs a paid plan or even an account.
- It notices silence. A pager app carries the messages you send it. The shack-Pi half of this page needs the opposite: nothing gets sent when the Pi dies, so something has to page you about the message that didn't arrive. Normally that's a second service you sign up for separately and point at the pager app. Here it's a kind of channel.
- No account needed. No email address, no password, no confirmation link. You install it and the phone mints its own keys. If you'd like your channels to outlive the phone, a free Tissue Mast account keeps them, and a new phone picks them up with a link code.
-
Pages sound through the ringer switch. On iPhone, Apple hands out the
critical-alert entitlement app by app and Mast holds one, which is what lets a
pagesound with the ringer off and the volume down, and a paired Apple Watch gets it by notification forwarding. On Android the app plays the tone itself on the alarm stream, so a phone on vibrate still rings. The other three priorities obey the switch, which is the point of having four of them. - One escalation partner, included. If you don't acknowledge inside the delay you set, the page moves to one other person's phone, once. Your spouse, the club secretary, whoever else has the app, on either kind of phone. You pair by swapping a code in the app and either of you can undo it. There's no seat to buy for it and no team plan it lives behind.
what it doesn't do
- Without an account, the channels live on the phone. Reinstall the app or get a new phone and they're gone, along with their addresses, so HamAlert and the crontab would need new ones. A free Tissue Mast account is what keeps them.
- Android can be talked out of ringing. There's no critical-alert entitlement on Android, so a page depends on settings the phone can change later: Do Not Disturb set to silence alarms, the alarm volume at zero, or Force stop in app info. The Waking you row reads them every time you open it.
- HamAlert doesn't retry. One call per spot. That's how HamAlert is built, not something Mast can change.
-
A vital tells you the Pi is quiet, not why.
/failcovers what the Pi can measure. The rest is walking over, or a smart plug. - Nothing here spots you. Self-spotting is still SOTAwatch, pota.app, SMS or the RBN.
- HamAlert's menu names may change. Destinations, URL notifications and Simulate are what they were called when this was written.
HamAlert is by Manuel Kasper, HB9DQM. It's free and the server is open source; this page uses the URL action he built. Mast Pager is free, with no account, on the App Store and on Google Play. The bridge Cell runs on Tissue Cloud, and the free plan covers it.
Mast Pager on its own is on the Mast Pager page, and Tissue Mast, the monitoring service with the free account and the paid plans, is on the Mast page and its pricing. Corrections and questions reach support@tissue.systems.