From Beeps to Toots: Reviving Pagers with Python and Mastodon

In a world that moves at the speed of a sent text, the evolution from pagers to smartphones reads like a thrilling chapter in the book of tech history. For those of us who’ve navigated this shift, pagers occupy a unique, and sometimes misunderstood, niche. Back in the day, carrying a pager, or beeper, could raise eyebrows, hinting at shady dealings. Yet, for someone like me, tucked away in a spot where cell service was a myth, pagers were a godsend. These little gadgets delivered messages across distances where cell phones dared not venture.

After exploring the depths of this post, dive deeper into the world of auditory wonders by visiting Finnley’s Audio Adventures blog and YouTube channel. Embark on an extraordinary journey through the most unconventional spoken word performances, advertisements, and promotional media. Finnley’s Audio Adventures is the treasure trove for anyone who relishes bizarre and hilariously unique audio entertainment. From the peculiarities of spoken word artistry to the whimsical world of vintage advertisements and beyond, Finnley’s Audio Adventures offers a vast ocean of extraordinary and offbeat audio content.

The image is a screenshot of a post from the social media platform Mastodon, also known as a "toot." The toot is from a user named C Train Mia with the handle @nkizz@tacobelllabs.net. The post is directed to another user named @FinnleyDolfin and reads: "@FinnleyDolfin who up POCing they SAG???" The post was made on February 22, 2024, at 18:51. It has received 0 reblogs, 0 replies, and 1 like. The background is dark with white text, and the like star is highlighted in yellow.
Used with permission
The image shows a close-up of a pager displaying a text message. The pager is black and has a few buttons on the front. The text on the screen is shown in a digital, monospaced font against a gray background. The message reads: "nkizz: Who up POCing they SAG????" and is displayed under a timestamp that says "05 23:41 08/16," indicating the time and date the message was received. Below the text, "Emergency Text Receiver" is written, suggesting the pager is set up to receive urgent communications. The pager appears to be lying on top of a keyboard, indicated by the visible keys in the background, illuminated by a blue light.

With the turn of the century, smartphones began their rise, pushing pagers towards the tech sidelines for everyday users. However, in certain circles—healthcare, emergency services, and niche industries—pagers remained indispensable for their reliability and straightforward use. The amateur radio crowd, myself included, also found a new playground for pagers, repurposing them for inventive uses.

Pagers operate on a variety of frequencies, typically within the VHF (Very High Frequency) and UHF (Ultra High Frequency) bands. These frequencies range from approximately 138 to 174 MHz in the VHF band and 400 to 470 MHz in the UHF band, ensuring a broad coverage area. The most common protocol used in paging systems is POCSAG (Post Office Code Standardization Advisory Group), developed in the 1970s. This digital coding standard allows for the efficient transmission of alphanumeric messages to pagers, optimizing the use of available bandwidth while ensuring message integrity

Driven by a mix of curiosity and nostalgia, I set out to link my pager—a relic from another era—with Mastodon, a modern node in the sprawling fediverse. The goal? To pull down Mastodon notifications straight to my pager, blending the old-school reliability of pagers with the vibrant exchange of today’s social media.

Digging deeper into pager protocols, I uncovered a workaround to send messages without the usual login. Initially, this hack was a gateway to getting weather updates directly on my pager. But why stop there? The thought of getting Mastodon alerts—be it mentions or direct messages—was too enticing.

This curiosity birthed a Python program that bridges my Mastodon life with the analog charm of my pager. Starting with the code for weather alerts, I tweaked it to funnel Mastodon interactions directly to my pager, keeping me looped into the digital chatter through a decidedly analog method.

This project isn’t just a technical marvel; it’s a homage to the enduring pager, proving that even in our smartphone-saturated world, there’s still room for the classics. It underscores the potential of marrying legacy tech with digital platforms, creating a unique cross-time dialogue.

At the heart of this project is the clever use of APIs from hampager.de and Mastodon, alongside SQLite3 for managing data and regular expressions to tidy up messages. This setup ensures no repeats and keeps messages clear and meaningful.

If you’re itching to try this blend of old and new, the first step is gathering some Python libraries. These are the tools that will help translate Mastodon’s digital banter into something your pager can understand, installed with a simple command via PIP or another package manager of your choosing.

  • Mastodon.py for Mastodon API interaction,
  • sqlite3 for database operations,
  • re for handling regular expressions,
  • json for JSON parsing,
  • requests for making HTTP requests.

With your pager ready and your account set up on hampager.de, the next crucial step is to securely store your credentials in a config.json file. This file should adhere to the following structure:

{
  "toCall": "YourHamCallSign",
  "txGroup": "TheRegionToSendPages",
  "login": "YourHamPager.DEUsername",
  "passwd": "YourHamPager.DEPassword"
}

The txGroup parameter is particularly important for regional specificity. For instance, if you are located in Arizona, USA, you would specify us-az as the value. This setting ensures that your pages are transmitted within the correct regional context, enhancing the reliability and relevance of the messaging service.

The next phase involves establishing a connection to your Mastodon instance. This step is crucial for authorizing the program to access your Mastodon account and interact with it on your behalf. By entering the required information as prompted by the setup code, your credentials are saved, facilitating a seamless link between the program and Mastodon.

from mastodon import Mastodon
import os

# Prompt the user for their Mastodon instance URL and login credentials
instance_url = input("Enter your Mastodon instance URL: ")
email = input("Enter your Mastodon email: ")
password = input("Enter your Mastodon password: ")

# Check if the user credentials file exists and is valid
if os.path.isfile('APRSShare_user_credentials.txt'):
    mastodon = Mastodon(
        client_id='APRSShare_client_credentials.txt',
        access_token='APRSShare_user_credentials.txt',
        api_base_url=instance_url
    )
    try:
        mastodon.account_verify_credentials()
        print("User credentials are valid")
        access_token = mastodon.access_token
    except Exception:
        print("User credentials are invalid, setting up new Mastodon app...")
else:
    print("User credentials file not found, setting up new Mastodon app...")

# Set up your Mastodon app on your instance if necessary
if not os.path.isfile('APRSShare_client_credentials.txt'):
    mastodon = Mastodon.create_app(
        'APRSShare',
        api_base_url=instance_url,
        to_file='APRSShare_client_credentials.txt'
    )
# Log in with your account to obtain the access token if necessary
if not os.path.isfile('APRSShare_user_credentials.txt'):
    mastodon = Mastodon(
        client_id='APRSShare_client_credentials.txt',
        api_base_url=instance_url
    )
    mastodon.log_in(
        email,
        password,
        to_file='APRSShare_user_credentials.txt'
    )

    # Save the access token for future use
    access_token = mastodon.access_token
else:
    # Load the access token from the existing user credentials file
    mastodon = Mastodon(
        client_id='APRSShare_client_credentials.txt',
        access_token='APRSShare_user_credentials.txt',
        api_base_url=instance_url
    )
    access_token = mastodon.access_token

The credentials are saved as APRSShare because this format is also utilized by the software for the DolfinWX bot. This bot processes APRS data and distributes it across the Fediverse. However, detailing this process warrants a separate blog post.

After setting up the connection, you’ll dive into the heart of the program: the messaging feature. This part is all about pulling notifications from Mastodon, fine-tuning them with specific filtering and formatting guidelines, and sending them off to your pager through the hampager.de service. It’s the grand finale of the project, showcasing the ability to get updates from a modern social network on a piece of technology that’s often seen as a relic in the smartphone era.

Before jumping into the Mastodon notifications, it’s smart to make sure your pager setup is on point. A simple test with the hampager.de service confirms everything is working as it should. Here’s how to do that:

  1. Comment out the check_and_process_messages() function and uncomment sendPage("Test"). This action focuses the program’s execution on sending a predefined test message rather than processing incoming Mastodon notifications.
  2. The print(response.text) statement can be uncommented to aid in this testing phase. It provides feedback by displaying the response from the hampager.de API, allowing you to troubleshoot any issues that arise during the test.
  3. If you get a 401 error during this process, this typically indicates an issue with the authentication details provided in your config.json file. Verify the formatting of the file and your username and password for accuracy and attempt the test again once any corrections have been made.

This systematic approach to testing not only ensures that the fundamental pager communication channel is operational but also facilitates a smoother transition to integrating Mastodon notifications. Once the basic functionality has been confirmed, you can proceed with confidence to the more complex aspects of the program, knowing that the underlying pager service connection is sound.

import json
import os
import re
import requests
import sqlite3
from mastodon import Mastodon

# Control variables
get_mentions = False
get_direct = True

# Database setup
db_path = 'processed_messages.db'
conn = sqlite3.connect(db_path)
c = conn.cursor()
if not os.path.exists(db_path):
    c.execute('CREATE TABLE processed_messages (id INTEGER PRIMARY KEY)')
    conn.commit()

# Mastodon setup
mastodon = Mastodon(
    access_token='APRSShare_user_credentials.txt',
    api_base_url='https://dolphin.rodeo'
)

# Configuration for sending pages
with open('config.json', 'r') as f:
    config = json.load(f)

toCall = config['toCall']
txGroup = config['txGroup']
login = config['login']
passwd = config['passwd']

def remove_username_from_content(content, username="FinnleyDolfin"):
    pattern = r'@?' + re.escape(username) + r'(@dolphin\.rodeo)?\b'
    return re.sub(pattern, '', content, flags=re.IGNORECASE)

def message_already_processed(message_id):
    c.execute('SELECT id FROM processed_messages WHERE id=?', (message_id,))
    return c.fetchone() is not None

def mark_message_as_processed(message_id):
    c.execute('INSERT INTO processed_messages (id) VALUES (?)', (message_id,))
    conn.commit()

def split_message(message):
    return [message[i:i+80] for i in range(0, len(message), 80)]

def sendPage(messageText):
    headers = {'Content-Type': 'application/json; charset=utf-8'}
    data = {
        "text": messageText,
        "callSignNames": [toCall],
        "transmitterGroupNames": [txGroup],
        "emergency": False
    }
    response = requests.post('http://www.hampager.de:8080/calls', headers=headers, json=data, auth=(login, passwd))
    print(response.text)

def check_and_process_messages():
    messages = (mastodon.notifications(types=['mention']))

    for message in messages:
        if not message_already_processed(message['id']):
            content = re.sub('<[^<]+?>', '', message['status']['content'])  # Remove HTML tags
            content = remove_username_from_content(content)  # Remove specific username mentions

            # Determine if the message should be processed
            should_process = False
            if get_mentions and message['type'] == 'mention' and message['status']['visibility'] == 'public':
                should_process = True
            elif get_direct and message['type'] == 'mention' and message['status']['visibility'] == 'direct':
                should_process = True

            # Process and send the message if it meets the criteria
            if should_process:
                fromUsername = message['status']['account']['username']
                content = f"{fromUsername}: {content}"  # Prepend the username to content
                if content.strip():  # Check if the message is not empty or just whitespace
                    if len(content) > 80:
                        for part in split_message(content):
                            sendPage(part)
                    else:
                        sendPage(content)
                    mark_message_as_processed(message['id'])

if __name__ == "__main__":
    check_and_process_messages()

Last updated Feb 28, 2024
Added functionality to turn on and off public and direct mentions
Bug fixes

This unique application of Python programming not only showcases the flexibility and power of the language but also revives an appreciation for the pager. By connecting it with the dynamic conversations of the Mastodon fediverse, it redefines the boundaries of digital communication, proving that even in an age dominated by instant messaging and social media, there’s still a place for the simplicity and reliability of the pager.

I didn’t specifically state that using a pager on the ham bands and registering with hampager.de requires an amateur radio license, as it’s generally understood that such a license is necessary. If you have any questions or need further information, feel free to contact me on Mastodon at @FinnleyDolfin@dolphin.rodeo.

Leave a Reply

Your email address will not be published. Required fields are marked *