A complete Go implementation of the POCSAG pager protocol — encoder, decoder, and everything in between. Ported from pocsag-tool and extended significantly.
- Encode POCSAG messages as WAV audio files (512, 1200, or 2400 baud)
- Decode those WAV files back to text, with a bit-aware sync engine that handles real-world recordings
- BCH(31,21) error correction and parity validation on every codeword
- Full 21-bit RIC/capcode addressing, placed in the correct frame per ITU-R M.584-2
- Numeric (BCD) and 7-bit ASCII alphanumeric message types
- AES-256/AES-128 encryption with password-based key derivation
- Burst mode — pack multiple messages for different pagers into one WAV
- GPU-accelerated waterfall spectrogram (OpenGL 4.1) with PNG export
- JSON output for scripting and API integration
- Works out of the box with
multimon-ngandpocsag-decode
# Encoder
go install github.com/sqpp/pocsag-golang/v2/cmd/pocsag@latest
# Decoder
go install github.com/sqpp/pocsag-golang/v2/cmd/pocsag-decode@latest
# Burst encoder (multiple messages at once)
go install github.com/sqpp/pocsag-golang/v2/cmd/pocsag-burst@latestOr build from source:
git clone https://github.com/sqpp/pocsag-golang.git
cd pocsag-golang
make build
# Binaries land in: bin/pocsag, bin/pocsag-decode, bin/pocsag-burstGenerate a POCSAG message as a WAV file.
Required:
-a/--address— pager address (full 21-bit RIC/capcode, e.g.1234567)-m/--message— the message text
Optional:
-o/--output— output WAV file (default:output.wav)-f/--function—0= numeric,3= alphanumeric (default:3)-b/--baud— baud rate:512,1200, or2400(default:1200)-e/--encrypt— enable AES-256 encryption-k/--key— encryption password (required with-e)-j/--json— print result as JSON instead of human-readable text-w/--waterfall— save a waterfall spectrogram PNG of the signal
Examples:
# Basic message
pocsag -a 123456 -m "HELLO WORLD" -o message.wav
# Numeric at 512 baud
pocsag -a 999888 -m "0123456789" -f 0 -b 512 -o numeric.wav
# Fast 2400 baud
pocsag -a 123456 -m "FAST MSG" -b 2400 -o fast.wav
# With encryption
pocsag -a 123456 -m "SECRET" -e -k "mypassword" -o encrypted.wav
# With waterfall image
pocsag -a 123456 -m "HELLO WORLD" -o message.wav -w waterfall.png
# JSON output (great for scripts)
pocsag -a 123456 -m "TEST" -o test.wav --jsonNormal output:
✅ Generated message.wav
Address: 123456, Function: 3, Baud: 1200, Message: HELLO WORLD
Decode: pocsag-decode -i message.wav or multimon-ng -t wav -a POCSAG1200 message.wav
JSON output:
{
"success": true,
"output": "message.wav",
"address": 123456,
"function": 3,
"message": "HELLO WORLD",
"baud": 1200,
"encrypted": false,
"type": "alphanumeric",
"size": 38444
}Decode a POCSAG WAV back to text.
Options:
-i/--input— input WAV file (required)-b/--baud— baud rate to try (default:1200)-k/--key— decryption password (if the message is encrypted)-j/--json— JSON output-v/--version— show version info
pocsag-decode -i message.wav
pocsag-decode -i message.wav -b 2400
pocsag-decode -i encrypted.wav -k "mypassword"
pocsag-decode -i message.wav --jsonOutput:
POCSAG1200: Decoded messages:
Address: 123456 Function: 3 ALPHA Message: HELLO WORLD
Pack multiple messages for different pagers into a single WAV file.
Options:
-j/--json— path to a JSON file listing the messages (required)-o/--output— output WAV file (default:burst.wav)-b/--baud— baud rate (default:1200)
Input JSON format:
[
{"address": 123456, "message": "FIRST MESSAGE", "function": 3},
{"address": 789012, "message": "SECOND MESSAGE", "function": 3},
{"address": 345678, "message": "0123456789", "function": 0}
]pocsag-burst -j messages.json -o burst.wav
pocsag-burst -j messages.json -b 512 -o burst.wavPass -w output.png to the encoder and it generates a frequency×time spectrogram of the signal using an OpenGL 4.1 renderer. The image uses the PySDR colormap (dark blue → purple → red → yellow → white), which makes the FSK tones easy to spot even in a short transmission.
This runs headless — no window pops up, it just writes the PNG and exits.
pocsag -a 123456 -m "HELLO" -o msg.wav -w waterfall.pngThere are two waterfall implementations in the library:
waterfall.go— pure Go/CPU, no GPU required, generates PNGs programmaticallywaterfall_gl.go— OpenGL-accelerated, used by the CLI for better colour fidelity and real-time display
Messages can be encrypted with AES-256 before being packed into the POCSAG signal. The pipeline is:
Message → CRC32 checksum → AES-256 encrypt → Base64 encode → POCSAG packet
The encryption uses a password you supply, hashed with SHA-256 to produce the key. A random IV is generated per message to prevent replay attacks. CRC32 is checked on decode to catch any corruption or wrong-key attempts.
Encrypted messages appear as Base64 text in tools like multimon-ng that don't know about the encryption — they'll just show the raw ciphertext string.
# Encrypt
pocsag -a 123456 -m "CONFIDENTIAL" -e -k "strongpassword" -o enc.wav
# Decrypt
pocsag-decode -i enc.wav -k "strongpassword"Import as github.com/sqpp/pocsag-golang/v2.
Encode and write a WAV:
import pocsag "github.com/sqpp/pocsag-golang/v2"
packet := pocsag.CreatePOCSAGPacket(123456, "HELLO WORLD", pocsag.FuncAlphanumeric)
wavData := pocsag.ConvertToAudio(packet)
os.WriteFile("output.wav", wavData, 0644)Decode a WAV:
wavData, _ := os.ReadFile("message.wav")
messages, err := pocsag.DecodeFromAudio(wavData)
for _, msg := range messages {
fmt.Println(msg.String())
}Key functions:
| Function | Description |
|---|---|
CreatePOCSAGPacket(addr, msg, fn) |
Encode a message at 1200 baud |
CreatePOCSAGPacketWithBaudRate(addr, msg, fn, baud) |
Encode at a specific baud rate |
ConvertToAudio(data) |
Convert to WAV bytes (1200 baud) |
ConvertToAudioWithBaudRate(data, baud) |
Convert to WAV at specific baud |
DecodeFromAudio(wavData) |
Decode a WAV (assumes 1200 baud) |
DecodeFromAudioWithBaudRate(wavData, baud) |
Decode at specific baud |
DecodeFromBinary(data) |
Decode raw POCSAG bytes |
go test -v ./...The integration test suite covers every combination of baud rate (512, 1200, 2400) and message type (alpha, numeric, encrypted), including 500-character long messages, burst transmissions, and cross-validation against multimon-ng.
If you're on Windows and want the
multimon-ngcross-check to run, have it available in WSL.
POCSAG addresses are full 21-bit RIC/capcodes. The encoder places each message codeword in the correct frame within the burst (address % 8), which is required by ITU-R M.584-2.
Tools like multimon-ng typically display (address / 8) * 8, so an address of 1234567 will show up as 1234560 in their output — that's expected.
Encoder originally ported from pocsag-tool by hazardousfirmware.
BSD-2-Clause