Wednesday, May 21, 2025

Is your Reverse Beacon (RBN) Spotting Station Up?

  

-----

The Reverse Beacon Network (RBN) is a system of volunteer-operated CW (Morse Code) skimmers that listen to ham radio bands and automatically report any CW CQ signals they hear.    These reports include callsign, signal strength, location, speed (WPM), etc. and are then relayed to a central server.  So who cares?  Well, if you are a ham radio CW operator this allows you to see where your signals are reaching, identify other operators, monitor band conditions, and a whole lot more.   See the RBN site for details.

-----

If you are running or monitoring a RBN skimmer/reporting station knowing if the system is reporting information (spots) to the central service is information you want.   Most of these stations are "just running happily in the background" and the operator running the station may not know it is down.  So...

-----

We created a Python script to send an email if a skimmer you own or monitor is down.   The code below will send an email alert if no spots have been reported in 60 minutes for a particular spotter's callsign.  It allows control of the bands, WPM, distance, and other filtering options through the RBN URL that the program interrogates.  

-----

# Python3 script to monitor a RBN CW Spotting Station
# and report with email if that station is down.
# Programs assume FireFox browser installed somewhere  
# Program assumes Gmail acct for sending email.

# WhiskeyTangoHotel.Com
# MAY 2025

import time
from selenium import webdriver
# Assumes Firefox is installed somewhere
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
import smtplib
from email.mime.text import MIMEText

# --- CONFIGURATION and SETUP ---
CHECK_INTERVAL_MINUTES = 60    # How often to check
# Below is the RBN URL used.   You can change bands, etc.  Make certain the age is less than the CHECK_INTERVAL_MINUTES variable
# The call sign in the WEBPAGE_URL and the SEARCH_TERM variable must be the same!!!
WEBPAGE_URL = "https://www.reversebeacon.net/main.php?rows=100&max_age=45,minutes&bands=160,80,60,40,30,20,17,15,12,10,6&modes=cw&spotter_call=K5TR&hide=distance_km,mode,time"
SEARCH_TERM = "K5TR"   # What RBN Spotting station call sign do you want to monitor for uptime?

# Your email settings
EMAIL_FROM = "yourgmailname@gmail.com"
EMAIL_TO = ["rx_address_1@domain.com" , "rx_address_2@domail.net"]  # etc, etc for more email addresses
EMAIL_SUBJECT = "RBN Spot Alert by WhiskeyTangoHotel.Com"
EMAIL_BODY_TEMPLATE = "ALERT: Only {count} occurrences found on the Reverse Beacon Network."

# Gmail credentials
GMAIL_USERNAME = "yourgmailname@gmail.com"
GMAIL_APP_PASSWORD = "abcd efgh ijkl mnop"  # NEVER EVER let your GMAIL_APP_PASSWORD into the wild

def check_count():
    options = Options()
    options.add_argument("--headless")
    service = Service()
    driver = webdriver.Firefox(service=service, options=options)
    driver.get(WEBPAGE_URL)
    time.sleep(5)  # Don't shorten this
    page_source = driver.page_source
    driver.quit()
    return page_source.count(SEARCH_TERM)

def send_email(count):  # Gmail sending stuff
    msg = MIMEText(EMAIL_BODY_TEMPLATE.format(count=count))
    msg["Subject"] = EMAIL_SUBJECT
    msg["From"] = EMAIL_FROM
    msg["To"] = ", ".join(EMAIL_TO)  # Displayed in the email header

    with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:  
        smtp.login(GMAIL_USERNAME, GMAIL_APP_PASSWORD)
        smtp.sendmail(EMAIL_FROM, EMAIL_TO, msg.as_string())

def main_loop():
    while True:  # Loop until the Dallas Cowboys win a Super Bowl
        count = check_count()
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
        print(f"[{timestamp}] Found {count} occurrence(s) of K5TR.")
        
        if count <= 1:   # Set to <=1 unless testing.  If this condition is met then send email.
            send_email(count)
            print("Email sent.")

        for minute in range(CHECK_INTERVAL_MINUTES, 0, -1):  # Print status message.
            time_now = time.strftime('%Y-%m-%d %H:%M:%S')
            print(f"[{time_now}] Next {SEARCH_TERM} RBN Spotter check in {minute} minutes...")
            #print(f"  Next check in {minute} minutes...")
            time.sleep(60)
        print()  # Move to next line after countdown

if __name__ == "__main__":
    main_loop()


-----