Project 8: The Beeping Relic (POCSAG Pager Decoder)
Decode POCSAG pager signals to recover numeric or alphanumeric messages.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 3-4 weeks |
| Main Programming Language | Python (Alternatives: C, Rust) |
| Alternative Programming Languages | C++ |
| Coolness Level | High |
| Business Potential | Low (legacy systems) |
| Prerequisites | FM demod, bit timing, error correction basics |
| Key Topics | FSK demod, symbol rate detection, BCH decoding |
1. Learning Objectives
By completing this project, you will:
- Demodulate POCSAG FSK signals into a bitstream.
- Detect and lock onto the correct baud rate (512/1200/2400).
- Synchronize to the POCSAG sync word and frame structure.
- Implement BCH error correction for codewords.
- Decode numeric and ASCII message formats.
2. All Theory Needed (Per-Concept Breakdown)
2.1 FSK Demodulation and Symbol Timing Detection
Fundamentals
POCSAG uses FSK (frequency shift keying) with two tones representing 0 and 1. Common baud rates are 512, 1200, and 2400. After tuning and filtering, you can demodulate the signal with an FM discriminator to obtain a baseband waveform. You then recover timing and slice bits. Correct symbol timing is essential because the baud rate may not be known beforehand. Detecting the baud rate can be done by measuring zero-crossings or by evaluating the spectrum of the baseband.
Deep Dive into the Concept
FSK is effectively FM with discrete frequency shifts. The discriminator output will be a roughly square wave at the symbol rate (after low-pass filtering). However, channel noise and filtering create rounded edges. To recover bits, you must sample at the center of each symbol. If the baud rate is unknown, you can estimate it using spectral analysis: compute an FFT of the discriminator output and look for peaks around the symbol rate. Another approach is to measure average time between zero-crossings.
Once you estimate the baud rate, you can implement a simple clock recovery: compute samples-per-symbol and choose a phase that maximizes eye opening. A more robust method uses a digital PLL or a Gardner timing recovery loop. For POCSAG, a simple early-late gate often works if the signal is strong.
Another important consideration is frequency offset. If the SDR is not tuned correctly, the discriminator output may be biased, which shifts the threshold. Normalizing the discriminator output and subtracting its mean helps. You can also implement a simple DC-blocking filter to stabilize the slicing threshold.
How this fits on projects
- Implement symbol timing in §5.10.
- Similar FSK demod appears in P06 (AIS) and P03 (FM demod).
Definitions & key terms
- FSK: Frequency shift keying.
- Baud rate: Symbols per second.
- Discriminator: FM-to-voltage converter.
- Timing recovery: Aligning sampling to symbol centers.
Mental model diagram (ASCII)
RF -> LPF -> FM demod -> Timing recovery -> Bit slicer
How it works (step-by-step, with invariants and failure modes)
- Tune and filter around pager channel.
- FM demodulate to baseband.
- Estimate baud rate.
- Recover timing and slice bits.
Failure modes:
- Wrong baud rate -> garbage data.
- Poor timing -> high bit errors.
Minimal concrete example
samples_per_symbol = int(fs / baud)
bit_samples = baseband[phase::samples_per_symbol]
bits = (bit_samples > 0).astype(int)
Common misconceptions
- “Baud rate is always 1200.” POCSAG can be 512/1200/2400.
- “FSK is easy to slice.” Timing errors still matter.
Check-your-understanding questions
- Why must you detect the correct baud rate?
- What does the discriminator output represent?
- How can you estimate symbol rate from data?
Check-your-understanding answers
- Wrong rate misaligns bits and breaks framing.
- The instantaneous frequency deviation (tone).
- FFT peak or average zero-crossing interval.
Real-world applications
- Legacy paging systems and telemetry.
Where you’ll apply it
- This project: §3.4, §5.10.
- Also used in: P06 AIS.
References
- POCSAG spec summaries
- Lyons, “Understanding DSP” Ch. 12
Key insights
FSK demod is simple, but symbol timing and baud detection make it reliable.
Summary
You learned how to demodulate and time-align FSK signals for POCSAG.
Homework/Exercises to practice the concept
- Simulate FSK at 1200 bps and recover bits.
- Add frequency offset and correct with DC removal.
- Estimate baud rate from a mixed-rate capture.
Solutions to the homework/exercises
- Bitstream should match original pattern.
- DC removal restores proper slicing.
- FFT peak reveals symbol rate.
2.2 POCSAG Framing and BCH Error Correction
Fundamentals
POCSAG frames consist of a preamble (alternating 1s/0s), followed by a sync word, then batches of 8 codewords. Each codeword is 32 bits: 1 flag bit, 20 data/address bits, and 11 BCH parity bits. Error correction is done with BCH(31,21) plus an overall parity bit. To decode messages, you must detect the sync word, align to codewords, apply BCH correction, and then interpret address or message fields.
Deep Dive into the Concept
The preamble is at least 576 bits of alternating 1s and 0s (0xAAAAAAAA…), allowing receivers to lock onto timing. After the preamble, the sync word 0x7CD215D8 marks the start of a batch. The receiver then processes batches of 8 codewords. Each codeword can represent either an address or a message depending on the flag bit.
BCH error correction is essential. The BCH(31,21) code can correct up to 2 errors per codeword. Implementing BCH decoding requires computing a syndrome and, if non-zero, using an algorithm (e.g., Berlekamp-Massey) to correct errors. For a first implementation, you can use a precomputed table of syndromes for 1-2 bit errors or reuse a known BCH library. The important part is to verify that after correction, the parity and checksum match. If correction fails, you should discard the codeword.
Address codewords encode the receiver ID (RIC) and function bits. Message codewords encode 20 bits of payload, which are then grouped into 7-bit ASCII or 4-bit numeric digits depending on the message type. POCSAG uses bit interleaving within batches, so you must de-interleave codewords before decoding. The interleaving ensures that burst errors are spread across codewords and can be corrected by BCH.
How this fits on projects
- Implement BCH correction in §5.10.
- Error correction concepts also appear in P09 (Viterbi) and P10 (correlation gain).
Definitions & key terms
- BCH: Bose–Chaudhuri–Hocquenghem code.
- RIC: Receiver Identification Code (pager address).
- Sync word: 0x7CD215D8, marks batch start.
- Interleaving: Reordering bits to spread errors.
Mental model diagram (ASCII)
Bitstream -> Preamble -> Sync -> Deinterleave -> BCH -> Decode
How it works (step-by-step, with invariants and failure modes)
- Detect alternating preamble.
- Find sync word.
- Deinterleave 8 codewords.
- Apply BCH correction to each codeword.
- Decode address or message fields.
Failure modes:
- Wrong alignment -> sync not found.
- BCH decode errors -> garbled text.
Minimal concrete example
if codeword_flag == 0:
ric = (codeword >> 11) & 0x1FFFF
else:
msg_bits.extend(extract_payload(codeword))
Common misconceptions
- “CRC is enough.” POCSAG uses BCH for correction, not just detection.
- “Messages are ASCII.” They can be numeric or alphanumeric.
Check-your-understanding questions
- Why is interleaving used?
- What does BCH correct?
- How is an address codeword identified?
Check-your-understanding answers
- To spread burst errors across codewords.
- Up to two bit errors per codeword.
- The flag bit distinguishes address vs message.
Real-world applications
- Paging systems and legacy alerting.
Where you’ll apply it
- This project: §3.7, §5.10.
- Also used in: P09 GSM.
References
- POCSAG specification
- Rice, “Digital Communications” Ch. 9
Key insights
Correct framing and BCH decoding turn noisy bits into usable pager messages.
Summary
You learned how to align POCSAG frames and apply BCH correction to decode messages.
Homework/Exercises to practice the concept
- Implement BCH decoding for a known test vector.
- Detect sync word in a bitstream with noise.
- Decode a simple numeric message.
Solutions to the homework/exercises
- Syndrome becomes zero after correction.
- Sync word appears after preamble.
- Numeric message digits match expected.
3. Project Specification
3.1 What You Will Build
A POCSAG decoder that identifies baud rate, synchronizes frames, applies BCH correction, and outputs messages.
3.2 Functional Requirements
- Detect baud rate and recover timing.
- Detect preamble and sync word.
- Deinterleave codewords.
- Apply BCH correction.
- Decode address and message data.
3.3 Non-Functional Requirements
- Performance: Decode a 5-minute capture in under 1 minute.
- Reliability: Reject frames with uncorrectable errors.
- Usability: Output decoded text with timestamps.
3.4 Example Usage / Output
$ python pocsag.py --input pager.iq --fs 2.4e6
[POCSAG] RIC: 123456 | MSG: "CALL BACK"
3.5 Data Formats / Schemas / Protocols
- Output JSON lines:
{"ric":123456,"msg":"CALL BACK"}
3.6 Edge Cases
- Wrong baud rate detected.
- BCH fails due to noise.
- Partial frames at end of capture.
3.7 Real World Outcome
Decoded pager messages displayed with correct RIC identifiers.
3.7.1 How to Run (Copy/Paste)
python pocsag.py --input pocsag.iq --fs 2400000 --auto-baud
3.7.2 Golden Path Demo (Deterministic)
Known POCSAG test capture yields at least one valid message.
3.7.3 CLI Transcript (Exact)
$ python pocsag.py --input test_pocsag.iq --fs 2400000
[POCSAG] RIC=7654321 MSG="TEST"
3.7.4 Failure Demo
$ python pocsag.py --input noisy.iq --fs 2400000
[WARN] No valid frames decoded
[EXIT] code=3
4. Solution Architecture
4.1 High-Level Design
IQ -> FM demod -> Timing -> Preamble -> Sync -> BCH -> Decode
4.2 Key Components
| Component | Responsibility | Key Decisions | |———–|—————-|—————| | Timing | Baud detection | FFT vs zero-crossing | | Framing | Sync detection | Sliding window | | BCH | Error correction | Table vs algorithm | | Parser | Message decode | Numeric vs ASCII |
4.3 Data Structures (No Full Code)
class PocsagMessage:
ric: int
text: str
4.4 Algorithm Overview
Key Algorithm: POCSAG Decoder
- Demodulate and slice bits.
- Detect preamble and sync.
- Deinterleave codewords.
- BCH correct and parse.
Complexity Analysis:
- Time: O(N)
- Space: O(N) buffers
5. Implementation Guide
5.1 Development Environment Setup
pip install numpy scipy
5.2 Project Structure
pocsag/
├── src/
│ ├── main.py
│ ├── timing.py
│ ├── framing.py
│ ├── bch.py
│ └── parser.py
└── tests/
5.3 The Core Question You’re Answering
“How do you recover text from a low-bitrate FSK paging signal?”
5.4 Concepts You Must Understand First
- FSK demod and timing (§2.1)
- BCH and framing (§2.2)
5.5 Questions to Guide Your Design
- How will you detect baud rate reliably?
- How will you handle uncorrectable codewords?
- How will you decode numeric vs text messages?
5.6 Thinking Exercise
Write the POCSAG sync word in binary and locate it in a sample bitstream.
5.7 The Interview Questions They’ll Ask
- Why does POCSAG use BCH instead of CRC?
- What is the purpose of the preamble?
- How are numeric messages encoded?
5.8 Hints in Layers
- Use FM discriminator and slice bits.
- Detect preamble of alternating bits.
- Find sync word 0x7CD215D8.
- Apply BCH correction and parse messages.
5.9 Books That Will Help
| Topic | Book | Chapter | |——-|——|———| | FSK | Lyons | Ch. 12 | | Error correction | Rice | Ch. 9 |
5.10 Implementation Phases
Phase 1: Foundation (5-7 days)
Goals: Demod and slice bits. Checkpoint: Bitstream stable at chosen baud.
Phase 2: Core Functionality (7-10 days)
Goals: Sync and BCH decode. Checkpoint: Valid codewords decoded.
Phase 3: Polish & Edge Cases (5-7 days)
Goals: Full message decode. Checkpoint: Text output stable.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Baud detect | FFT vs zero-crossing | FFT | More robust | | BCH decode | Table vs algorithm | Table for 1-2 errors | Simpler |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |———|———|———-| | Unit | BCH correction | Known codewords | | Integration | Full decode | Test IQ capture | | Edge | Low SNR | Should reject frames |
6.2 Critical Test Cases
- Known POCSAG capture decodes messages.
- BCH correction fixes 1-bit errors.
- Wrong baud rate yields no valid sync.
6.3 Test Data
Public POCSAG sample captures.
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |———|———|———-| | Wrong baud | No sync | Re-estimate symbol rate | | Bad timing | Random decode | Adjust sampling phase | | BCH errors | Garbled text | Verify polynomial |
7.2 Debugging Strategies
- Plot eye diagram for baseband.
- Validate BCH with known vectors.
7.3 Performance Traps
- Excessively long correlation windows.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add RIC filtering.
8.2 Intermediate Extensions
- Support all message types.
8.3 Advanced Extensions
- Real-time live decode with alerts.
9. Real-World Connections
9.1 Industry Applications
- Legacy paging and alert systems.
9.2 Related Open Source Projects
- multimon-ng (POCSAG decoder).
9.3 Interview Relevance
- Demonstrates error correction and framing skills.
10. Resources
10.1 Essential Reading
- POCSAG specification documents
- Rice, “Digital Communications” Ch. 9
10.2 Video Resources
- SDR pager decode tutorials
10.3 Tools & Documentation
- multimon-ng reference
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain FSK and BCH coding.
- I can detect sync words in a bitstream.
11.2 Implementation
- I decode at least one valid message.
- Output contains correct RIC and text.
11.3 Growth
- I can handle multiple baud rates.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Decode at least one valid POCSAG message.
Full Completion:
- Robust decode across baud rates.
Excellence (Going Above & Beyond):
- Live alerting and message filtering.