Pagers used to be all the rage in the late 80s, 90s and early 2000s. You could carry around a little battery-powered trinket that could ring and give you a little text message. How neat! Then, mobile telephones became easier and easier to carry around, then smartphones became a thing, and pagers died.
Pagers worked and still work via radio signals. A lot of different radio standards for pagers have been developed over the decades, but, here, i will focus on the POCSAG family of standards and how they were (and are) used here, in France.
I will go over three different things in this post: a tiny bit of history on the Alphapage paging network in France, some technical information about POCSAG, and how i tweaked a GNURadio script to do multi-channel decoding to capture multiple channels at the same time.
Why am i writing an english-speaking article on a blog primarily oriented towards an english-speaking audience with so much french-specific data? Well, for one, i only live in one country, and i haven’t had as much experienced listening in on the data of other countries. If you want the full french experience, you can also read this in french when i get around to translating it (soon (hopefully)).
# A Brief History of Alphapage
A lot of companies used to sell pager services for French consumers (Tatoo, Tam-Tam and Kobby; the latter two of which used FLEX and relied on separate networks) and companies (Alphapage). These devices are not necessarily junk today! Tatoo, in particular, was the consumer-brand of the network i will focus on today. Some people have tried to and succeeded in broadcasting to a Tatoo pager they bought online or found stashed in a drawer. The cost, however, is prohibitive (more than 3.5€ per message). Unless you’re modding some hardware for a ham system like DAPNet, it’s unrealistic to try and build on top of a system that sends data to pagers.
The reason i focus specifically on Alphapage is because, contrary to the other networks at the time, Alphapage is still operational, albeit under a different name with a different company owning it. There are still messages being broadcast to POCSAG-capable pagers today.
Alphapage’s infrastructure used to belong to France Télécom, the name of our former national telecom operator. Their commercial network, Alphapage, was broken off as property of a company that was bought and renamed e*Message France when France Télécom was dismantled and sold to the private sector. That company officially received the right to operate FT’s pager frequencies in 2001, with ARCEP decision #2001-308 of March 23rd 2001. That decision conveniently lists the frequencies and some technical information. Around 466~MHz, five 25 kHz channels are listed:
- Channel 1 : 466.02500 MHz
- Channel 2 : 466.05000 MHz
- Channel 3 : 466.07500 MHz
- Channel 7 : 466.17500 MHz
- Channel 8 : 466.20625 MHz
e*Message is still authorized to use these frequencies in all of France. Another frequency, 87.390 MHz is listed, but not currently used. In fact, as of September 2025, only channels 2, 7 and 8 are still in use by e*Message. This is coherent with various ARCEP decisions i found that list the channels still in use as 2, 7 and 8. Those channels are usable, as of my writing this, until December 31st 2030.
By the mid-90s, the coverage of Alphapage was rather impressive. By 2007, e*Message itself advertised “about 500 antennas spread all over the country”.
In several places online, i read that France was cut in distinctive zones depending on your geographic location, and that Alphapage-compatible pagers were registered to a specific zone. However, i was never able to find a proper map of those zones, nor confirm that that was ever the case.
# The POCSAG Protocol
The standard used by these Alphapage-compatible pagers is the Radio-Paging Code No. 1 of the Post Office Code Standardization Advisory Group, or POCSAG. POCSAG was formed in 1976 by a group of international engineers looking to develop a wide-area paging code.
POCSAG (the protocol, not the standards group) supports different bitrates: 512, 1200 and 2400 bps. POCSAG transmissions begin with a preamble of 576 bits that alternate between 0 and 1. Most pagers put themselves to sleep to save power, and wake up to listen for a preamble at a regular interval (small enough that they will catch an interval when one occurs). The preamble signals for pagers to stay awake and power up decoding hardware. It also helps identify the data rate that will be used.
The rest of the communication comes in batches. POCSAG is capable of delivering messages simultaneously to multiple units by dividing messages into piece that are transmitted progressively batch per batch. Each batch is time-divided into 8 frames (preceded by a magic sequence of bytes called the Frame Synchronization Code). Then, each frame is divided into two codeword slots, making it 16 codewords per batches. Each codeword can contain either an address (first bit is 0), or a piece of message (first bit set to 1).
Where a message is in each batch will partially determine who is receiving it. In POCSAG, a pager’s address is 21 bits long: 18 bits are transmitted in the address codeword, and the last 3 are derived from the position of the codeword in the batch. That neat little trick saves space and time in transmission! When a batch transmits an address at a given codeword position, the same codeword position in incoming batches will contain the pieces of the incoming message. The pager will keep receiving and decoding its message until an address or idle codeword is transmitted in the codeword slot that it was listening for. Idle codewords are used when no message is to be transmitted in a given codeword slot.
+-----------+
| B | | B | +---+-------------------------------+
| A | | A | | | 8 | | F | R | A | M | E | S |
| T | . | T | | F + + + + + + + + +
+-- SYNC --+ C | . | C | | C |C|C|C|C|C|C|C|C|C|C|C|C|C|C|C|C|
| H | . | H =====>| S |0|0|0|0|0|0|0|0|0|1|1|1|1|1|1|1|
| | | | | |1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|
| 1 | | n | +---+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-----------+
POCSAG messages can be decoded in multiple modes:
- BCD (Binary-Coded Decimal) aka “Numeric”: Only digits, parens, dashes, ‘*’ and some special control characters are sent
- Alphanumeric with 7-bit ASCII, transmitted in little-endian.
Of note is that only 20 bits can be transmitted at a time in a codeword. Thus, when your characters are 7-bit, there will almost always be one last codeword filled with a bit, and then null/termination characters. Per the standard, the NULL character is the only one that is allowed to be truncated.
# Actually Listening to POCSAG
The technical stack used to decode and see pager messages is
not that complicated, and has been tried and tested before. multimon-ng is a CLI tool that provides decoding for a
lot of paging protocols, including POCSAG. You just need to feed it raw audio
at a bandwidth of 22.050K, and it’ll happily decode for you.
So i simply ran the same kind of command that people recommend for Alphapage: rtl_fm piped into multimon-ng. I looked at the three frequencies still in use
on SDR++ and figured out that, roughly every minute, a message is sent on all three frequencies.
Channel 7 received more frequent messages. So i picked that frequency:
rtl_fm -p -5 -F 0 -E dc -M fm -T -g 32 -f 466.175M -s 22050 | multimon-ng -t raw -c -a POCSAG1200 --timestamp -
And eventually, it decodes things! Most of it is junk i can’t understand, all of it is stuff i cannot share anyways1, but something is coming through.
Because a pager only listens to one of the specific carriers, and i had no way to simultaneously monitor messages sent to the three channels, i figured “well, my RTL-SDR dongle can cover the bandwidth needed to include all three channels, surely it isn’t too hard to decode all three channels at the same time”. That sounded like a fun technical challenge.
rtl_fm is a very nifty piece of CLI tooling if you want to just listen to one
frequency, or multiple frequencies with some scanning logic. Problem is, no
matter how i tuned the squelch, rtl_fm would get stuck on one channel and stop
scanning. Sometimes, i would see two carriers active, but rtl_fm can only tune
to one frequency at a time, and multimon-ng cannot know which frequency is
being listened and print it (another piece of information i want). Worse, when
a channel would become active, rtl_fm would switch to it after the
synchronization sequence started: multimon-ng couldn’t decode that audio.
So i poked around the internet, figuring that “someone more competent might
already have done that”, and i found multimon-ng issue #165.
In the examples/ folder of the multimon-ng repository, there is an example
Python script called multipager.py generated with GNURadio Companion that
splits received data into a bunch of channels and decodes them all individually
by piping the output audio to one multimon-ng process per channel. The output
is then parsed (yes, that’s awful) and the script adds information such as
the frequency where a message was decoded.
# So What is this GNU of the Radio Anyways
GNURadio is a flow-based framework to develop digital signal processing applications. It works on a logic of linking sources to sinks through a bunch of filters and transformations of the input. In the script above, we use the Open Source Mobile Communication (OSMoCom) library to have access to SDR peripherals.
GNURadio comes with a piece of software called GNURadio Companion (GRC) that provides
the graphical view of these processing flowcharts. As a bonus, once a flowchart
is validated in GRC, it can be exported as a Python script that uses the Python
gnuradio library.
And that’s where multimon-ng’s multipager is an issue: it was generated for
Python2, on an old version of GRC. Some bindings are not located in the same
namespaces anymore, some libraries have stopped existing, and the language as a
whole is very different.
At first, i simply updated the script. It’s not the first time i update a Python 2 script for Python 3. Even replacing some libraries, notably for async I/O, was done fairly quickly. And i could run the script rather fast.
But then i hit an issue: channelizing. With how the script was originally made,
the input bandwidth is divided into n equally sized channels. The Alphapage channels are not well
aligned for that: they do not all fit in n equally spaced channels because
of channel 8 (466.20625 MHz).
So i had to learn some digital signal processing.
What i want to do is have three copies of the baseband, cut down to the bandwidth of POCSAG, and shifted such that the center frequency is correct for a given channel. In DSP, frequency translation, also abbreviated Frequency XLating, is a process by which an input spectrum is shifted frequency-wise. In GNURadio, you can even use a Finite Impulse Response implementation of frequency translation to combine translation of the spectrum and an application of filters to isolate a piece of that band with the correct bandwidth. i don’t really understand any of the nitty-gritty underneath all of this, so i will let you read the Wikipedia page.
So i went on my merry way hacking away at the script: i need to center the
frequency in the middle of all channels (so 466.128125 MHz) in such a way that if
there is a DC offset, if will not bother us. Then, i need to connect three
XLat IR filters to the output of the OSMoCom SDR source to shift to all three
active pocsag channels. Then, i just hook myself to the existing bits of the script
that demodulate the spectrum into audio and pipes it into multimon-ng and parses
the output.
And voilà.
The actual script is available here. You can pass it the same kinds of
arguments that multipager.py from the original repository takes, except -f
and -c (those are never queried).
# Where To Go From Here?
Pagers are a cool little tool to play with. Some people send themselves messages, some others are trying to build entire networks for them. In the future, i might mess around with making a low-power local emitter for POCSAG, to automate notifications or alarms for meds and calendar events.
In general, i find learning about technology that provides you with more off-grids communication fascinating!
-
i’m pretty sure the precise piece of legislation is Penal Code R226-15 ¶2 for France. Be smart! don’t fedpost about what you hear on the radio if you weren’t the intended recipient :) ↩