Skip to content

Dj3ky/PagerMonitor

Repository files navigation

πŸ“Ÿ PagerMonitor

Real-time POCSAG & FLEX pager monitoring β€” from RF to your browser in seconds

License: MIT Node.js React SQLite Docker PWA

Plug in an RTL-SDR dongle, set your frequency, and get a live dashboard of every pager message on the air.

Quick Start Β· Features Β· Screenshots Β· Installation Β· Docker


What is this?

PagerMonitor turns a cheap RTL-SDR USB dongle (~€25) into a full pager monitoring station. It decodes POCSAG and FLEX transmissions in real time, stores them in a searchable database, and serves a polished web dashboard that works on any device β€” phone, tablet, desktop.

Built for emergency services monitoring, amateur radio enthusiasts, and anyone curious about what's being paged in their area.

RTL-SDR dongle β†’ rtl_fm β†’ multimon-ng β†’ Node.js β†’ Browser (WebSocket)

✨ Features

πŸ”΄ Live feed

  • Real-time WebSocket feed β€” messages appear instantly, no refresh
  • Per-user NEW badge β€” tracks what you've already seen, synced across all your devices
  • Highlight rules β€” regex or text patterns that colour-code matching messages
  • Keyword alerts β€” flash the browser and trigger notifications for urgent keywords
  • Click to expand β€” full details, raw data, timestamp on any message
  • Filter by capcode, alias, or group with one click
  • Pagination + load more β€” browse the full history, not just the last 200

🚫 Message filtering

Define exactly which messages the system processes. Filtered messages are completely dropped β€” not saved to the database, not shown in any view, and no notifications are sent.

Mode Behaviour
Accept all Default β€” every received message is processed normally
Ignore capcodes Blacklist β€” drop messages from specific capcodes, accept everything else
Only capcodes Whitelist β€” accept only the listed capcodes, drop everything else
Only groups Accept only messages whose alias belongs to one of the selected groups
Only aliased Accept only capcodes that have an alias defined; optionally restrict to specific ones

Configure under Admin β†’ Messages β†’ Feed Filter. Changes take effect immediately with no restart required.

πŸ—ΊοΈ Map view

  • Pins for messages with GPS coordinates extracted from message text
  • Three modes: individual pins Β· clustered Β· heatmap
  • Fly-to animation when clicking the map button on any message row
  • Re-geocode button on every message β€” retries address extraction and pins the result on the map
  • Delete location β€” remove a coordinate pin directly from the map popup
  • Reset Map button β€” clears all current pins from the map without reloading the page

🌍 Geocoding (address β†’ GPS)

Geocoder backend

Converts extracted addresses into GPS coordinates. Two backends are available:

Backend Cost Notes
Nominatim Free / no key Default β€” OpenStreetMap data; good global coverage
HERE Free 250k/month Better house-number accuracy, especially for NZ, AU, and countries with sparse OSM data

Configure under Admin β†’ Site β†’ AI Geocode β†’ Geocoder backend. For HERE, generate a free API key at developer.here.com (no credit card required) and paste it in the UI, or set HERE_API_KEY=… in your .env.

AI-assisted geocoding (optional)

When enabled, the raw pager message text is sent to an AI model to extract the street, house number, and settlement before falling back to the built-in regex pipeline β€” useful for unusual or abbreviated address formats that regex misses.

Provider Cost Notes
Groq Free tier (14 400 req/day) Llama 3.1 8B Instant β€” fastest option; no local hardware needed
OpenAI Paid (GPT-4o-mini) Most accurate; requires an API key
Ollama Free / local Runs on the same Raspberry Pi; no internet required. Llama 3.2 1B fits in 2 GB RAM

Configure under Admin β†’ Site β†’ AI Geocode. API keys are stored server-side and never sent to the browser. Disabling AI falls back silently to the regex pipeline with no data loss.

πŸ”§ Message normalizations

Define regex find-and-replace rules that are applied to every decoded message before it is stored or geocoded. Useful for stripping noise, fixing encoding artefacts, or standardising abbreviations across your feed. Includes a live preview so you can test a rule against sample text before saving.

Configure under Admin β†’ Messages β†’ Message Normalizations.

πŸ“‹ Aliases & groups

  • Give capcodes friendly names: 1234567 β†’ Fire Station Alpha
  • Organise aliases into groups and subgroups with colour coding
  • Per-alias and per-group row colours and notification sounds
  • CSV import/export β€” manage hundreds of aliases in a spreadsheet

πŸ“ Message notes & annotations

  • Any user can add notes to any message
  • Notes can be shared (all users see it) or private (only you)
  • Note count badge on each message row
  • Admins can delete any note

πŸ”” Notifications

Service Features
Browser push OS-level notification on any subscribed device, even with the app closed
Discord Rich embeds β€” alias, group, Google Maps link
Telegram MarkdownV2 formatted, inline Maps link
Gotify Self-hosted push, any priority
Pushover Native Maps URL button in the app
Email (SMTP) HTML formatted, Maps button, any SMTP provider
Webhooks HTTP POST to any endpoint, HMAC-SHA256 signed

Per-user notification filters β€” email and push each have independent filters per user (by group, alias, capcode, or keyword). The global filter on the Services page applies only to Discord, Telegram, Gotify, Pushover, and MQTT. Set preferences from the profile panel.

πŸ“² PWA β€” installable app

Install PagerMonitor directly to your home screen on Android, iOS, or desktop. No app store needed.

  • Standalone window β€” no browser bar, feels like a native app
  • Background push notifications β€” alerted even when the phone is locked or the app is closed
  • Click the bell icon in the header to enable β€” automatically subscribes the device to push

πŸ‘₯ Multi-user access

Role Access
admin Full access β€” all settings, users, SDR control
editor Aliases, groups, highlights, keyword alerts
viewer Read-only feed, map, archive, search
  • Password reset via email (time-limited link)
  • Session tokens with 7-day expiry

πŸ“‘ Multi-SDR support

Run multiple RTL-SDR dongles in parallel β€” each on its own frequency, protocol, or gain setting. Status bar shows per-dongle health with individual indicators and hover tooltips.

πŸ—„οΈ Archive & history

  • Auto-archive old messages to a separate archive.db
  • Full-text search across both live and archived messages
  • CSV export from the archive panel
  • Backup & restore as a single .pmbackup file

βš™οΈ Admin panel

Group Tabs
SDR SDR Control (start/stop/restart) Β· Dead Air detection Β· Live log viewer Β· SDR Clients dashboard Β· Client Key
Messages Database tools (purge, export) Β· Archive config Β· Statistics dashboard Β· Dedup Β· Highlight rules Β· Keyword alerts Β· Feed Filter Β· Message Normalizations
Notifications Services (Discord / Telegram / Gotify / Pushover / MQTT) Β· Webhooks Β· Email (SMTP) Β· User notification preferences
Aliases & Groups Group manager Β· Alias manager (with CSV import/export)
System System stats Β· One-click update Β· Backup & Restore Β· Audit log
Site Site settings Β· AI Geocode (Nominatim / HERE Β· Groq / OpenAI / Ollama) Β· User management

πŸš€ Quick start

Raspberry Pi (single device, 5 minutes)

# Prerequisites (multimon-ng latest is built from source automatically by install.sh)
sudo apt update && sudo apt install -y rtl-sdr nodejs npm

# Install
git clone https://github.com/dj3ky/pagermonitor.git ~/pagermonitor
cd ~/pagermonitor && bash install.sh

# Configure frequency
nano ~/pagermonitor/backend/.env
# Set: RTL_FM_FREQ=173.250M

# Start
sudo systemctl start pagermonitor

Open http://<pi-ip>:3000 Β· Login: admin / see startup log for generated password Β· Change password immediately.

Docker (any machine)

git clone https://github.com/dj3ky/pagermonitor.git
cd pagermonitor
make setup        # creates .env from template
nano .env         # set your frequency
make start        # builds and starts

Distributed (RPi client β†’ server)

# On the server (no dongle needed)
make start-server
# Open admin panel β†’ SDR Client Key β†’ generate key

# On the Raspberry Pi
cp client/.env.example client/.env
nano client/.env   # set SERVER_URL, CLIENT_KEY, RTL_FM_FREQ
make start-client

πŸ› οΈ Hardware

Item Notes
RTL-SDR dongle RTL2832U chipset, ~€25. RTL-SDR Blog V3/V4 recommended
Antenna Dipole or discone for best reception
Raspberry Pi Pi 4 (2GB+) for single-device. Pi 3B+ or Zero 2W for client-only

No RTL-SDR dongle? You can still run PagerMonitor in server mode (DISABLE_SDR=true) and forward messages from a remote Pi client.


πŸ“ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Single device ────────────────────────┐
β”‚                                                           β”‚
β”‚   RTL-SDR dongle                                          β”‚
β”‚        β”‚                                                  β”‚
β”‚   rtl_fm (frequency tuner)                               β”‚
β”‚        β”‚  raw PCM audio                                   β”‚
β”‚   multimon-ng (POCSAG/FLEX decoder)                      β”‚
β”‚        β”‚  decoded text                                    β”‚
β”‚   Node.js server ──── SQLite database                    β”‚
β”‚        β”‚                                                  β”‚
β”‚   React frontend ◄──── WebSocket                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€ Distributed ────────────────────────────────────────────┐
β”‚                                                           β”‚
β”‚  Raspberry Pi          Server (VM / NAS / PC)             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚ rtl_fm       β”‚      β”‚ Node.js + SQLite          β”‚      β”‚
β”‚  β”‚ multimon-ng  │─────►│ React web UI              β”‚      β”‚
β”‚  β”‚ pm-client    β”‚ HTTP β”‚ Admin panel               β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ POST β”‚ Notifications             β”‚      β”‚
β”‚                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                               β–² browsers                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Multiple RPi clients can connect to the same server.


πŸ“¦ Tech stack

Layer Technology
SDR rtl_fm + multimon-ng
Backend Node.js 20, Express, better-sqlite3, ws
Database SQLite with FTS5 full-text search
Frontend React 18, Vite, Leaflet (maps)
Auth bcrypt, Bearer token sessions
Notifications node-fetch, nodemailer, web-push (VAPID)
AI geocoding Groq API Β· OpenAI API Β· Ollama (local) β€” all optional
Process systemd (native) or Docker Compose

No external services required by default. Everything runs locally. AI geocoding is optional β€” enable it for better address extraction without any impact on the core pipeline.


πŸ”„ Updating

Check CHANGELOG.md first to see what changed.

Native (systemd) β€” one command

cd ~/pagermonitor
bash update.sh

This pulls the latest code, upgrades system packages, upgrades multimon-ng if a newer version is available, rebuilds the frontend, and restarts the service automatically.

Or use the admin panel β€” Admin β†’ System β†’ Update shows the current vs latest commit and has an Update Now button that streams live progress and reloads the page when done.

The RPi client has no web UI β€” update it via SSH: cd ~/pagermonitor && bash update.sh

Docker

cd ~/pagermonitor
git pull
make update        # pulls, rebuilds, restarts in one command

Check your current version

Admin β†’ System β†’ Update shows the current and latest GitHub commit. Or:

curl -s http://localhost:3000/health | grep version

After major version bumps (e.g. 2.x β†’ 3.0) always read the CHANGELOG β€” there may be a manual migration step. Minor and patch versions are always safe to update without any extra steps.


⚑ API & monitoring

# Health check β€” use with Uptime Kuma, Zabbix, etc.
curl http://localhost:3000/health

# Response
{
  "ok": true,
  "status": "healthy",
  "uptime": { "human": "2d 4h 13m" },
  "database": { "messages": 4821, "today": 47 },
  "sdr": { "running": true }
}

Full REST API + WebSocket documented in INSTALL.md.


πŸ—‚οΈ Documentation

Document Contents
INSTALL.md Full installation guide, configuration reference, API docs, troubleshooting
DOCKER.md Docker setup, profiles, Makefile commands, environment variables
CHANGELOG.md Version history β€” what changed in each release
client/.env.example RPi client configuration with multi-dongle examples
.env.example Server configuration β€” all variables documented

πŸ“ License

MIT β€” free to use, modify, and distribute.


Built for the amateur radio and emergency services monitoring community.

If this project is useful to you, consider giving it a ⭐


Made with ❀️ in Slovenia · RTL-SDR + Node.js + React

⬆ Back to top

About

πŸ“Ÿ Real-time POCSAG & FLEX pager monitoring β€” from RF to your browser in seconds

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages