Tell me when a LoRaWAN device stops sending.
ChirpStack has no alerting. The Things Stack Community Edition has none either. So when a sensor goes flat, floods, or fails to rejoin, nothing tells you — and the first person to notice is usually your client, asking why the graph has been empty for three weeks.
This is one Python file, no dependencies outside the standard library, that notices and tells you.
$ python3 uplink_watchdog.py --ntfy my-secret-topic-name
uplink-watchdog 1.0.0 listening on http://0.0.0.0:8000/uplink
Point your network server's HTTP integration here. Ctrl-C to stop.
new device: IO103M
new device: LS101SM
ALERT: Device silent: LS101SMYour network server can already POST every uplink to a URL. Point it at this script. It records when each device was last heard from, learns how often that device normally reports, and alerts when one stops.
ChirpStack Applications ▸ your app ▸ Integrations ▸ HTTP
The Things Stack Integrations ▸ Webhooks ▸ Add webhook ▸ Custom
Anything else any JSON POST carrying a device EUI or id
There is nothing to configure per device. Devices appear the first time they send something.
There is no install. Download the file and run it with Python 3.8 or newer.
curl -O https://raw.githubusercontent.com/geosensor-tech/uplink-watchdog/main/uplink_watchdog.py
python3 uplink_watchdog.py --selftest # prove it works, offline
python3 uplink_watchdog.py --ntfy my-secret-topic-nameThe same file, with a longer write-up, is also at https://watch.geosensor.tech/tools/uplink-watchdog.
For phone push, install ntfy, subscribe to a topic name only
you know, and pass the same name to --ntfy. Anyone who guesses your topic can
read your alerts, so make it unguessable.
# Phone push
python3 uplink_watchdog.py --ntfy my-secret-topic-name
# Slack, Teams, or your own endpoint — receives {"title": ..., "text": ...}
python3 uplink_watchdog.py --webhook https://hooks.example.com/...
# Email
python3 uplink_watchdog.py --email-to ops@example.com \
--smtp-host smtp.example.com --smtp-port 587 \
--smtp-user alerts@example.com --smtp-pass "$SMTP_PASS"Use as many at once as you like. Credentials can come from the environment
(SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS) instead of the command line.
By default it learns. Each device gets its own window of three times its observed average interval, with a floor of 30 minutes. A people counter reporting every 15 minutes is called silent after 45; a fill sensor reporting daily is not called silent until three days have passed.
That is usually what you want, because it needs no per-device configuration and
tolerates one dropped uplink without crying wolf. If you would rather set one
flat window for everything, use --silence 60.
Each fault alerts once. You get a second message when the device recovers, so you know a site visit worked without opening anything.
| Option | Default | |
|---|---|---|
--port |
8000 |
port to listen on |
--bind |
0.0.0.0 |
interface to bind |
--path |
/uplink |
URL path that accepts uplinks |
--silence MIN |
learn per device | fixed silence window in minutes |
--interval SEC |
300 |
how often to check for silence |
--state FILE |
watchdog-state.json |
survives restarts; "" to disable |
--selftest |
run the built-in tests and exit |
GET /status returns current state as JSON, which is handy for a status page or
for piping into something else.
Put it behind a reverse proxy with TLS if it is reachable from the internet, and run it under systemd so it comes back after a reboot:
# /etc/systemd/system/uplink-watchdog.service
[Unit]
Description=uplink-watchdog
After=network-online.target
[Service]
ExecStart=/usr/bin/python3 /opt/uplink-watchdog/uplink_watchdog.py \
--ntfy my-secret-topic-name --state /var/lib/uplink-watchdog/state.json
Restart=always
DynamicUser=yes
StateDirectory=uplink-watchdog
[Install]
WantedBy=multi-user.target- Nothing watches the watchdog. If this process dies, or its host goes down, or the disk fills, you get silence — which looks exactly like everything being fine. This is the fundamental problem with self-hosted monitoring, and it is not solvable by making this script better.
- No authentication on the endpoint. Anyone who knows the URL can post uplinks into it. Put it behind a reverse proxy and a secret path, or a firewall.
- State is a JSON file, fine for hundreds of devices, not for hundreds of thousands.
- It only knows about devices that have sent at least one uplink since it started. A device that was already dead before you deployed it stays invisible.
We also run this as a hosted service, UplinkWatch. Same idea, same webhook, but it runs on our infrastructure instead of yours — so the watchdog problem above becomes ours. It adds a dashboard, gateway monitoring derived from uplink metadata, per-device overrides and recovery notices. Free for 5 devices, €9/month for 50.
To be straightforward about it: this script is not crippled bait for that. It is the tool we built for ourselves, it does what the README says, and if it is all you need then you should just use it.
Geosensor — a Portuguese company that deploys and leases LoRaWAN sensor networks. We wrote this because finding out from a client that a sensor died a fortnight ago is a bad afternoon.
Issues and pull requests welcome.
MIT — see LICENSE.
LoRaWAN® is a mark of the LoRa Alliance. This project is not affiliated with the LoRa Alliance, Semtech, ChirpStack or The Things Industries.