.. © 2025 syslinx.org
SOTA cluster notifications on Mac OS and Linux
I wrote a small Python script that fetches data from the SOTA API and sends notifications once a new SOTA activator is spotted on the cluster.
Thanks to Ullrich (DF5WC) for helping me get this script running on Linux as well!
import requests
import time
import os
import platform
API_URL = "https://api2.sota.org.uk/api/spots/10/all"
CHECK_INTERVAL = 60 # seconds
# List the modes you want to be notified about (case-insensitive)
modes = ["SSB"] # Example: ["SSB", "CW"]
def fetch_spots():
"""Fetch the latest spots from the SOTA API."""
try:
resp = requests.get(API_URL)
resp.raise_for_status()
return resp.json()
except Exception as e:
print("Error fetching spots:", e)
return []
def notify(title, message):
"""Send a notification using the appropriate system command."""
current_os = platform.system()
if current_os == "Darwin":
# macOS notification
os.system(f'''osascript -e 'display notification "{message}" with title "{title}"' ''')
elif current_os == "Linux":
# Linux notification (requires notify-send)
os.system(f'''notify-send "{title}" "{message}"''')
else:
print(f"Notification: {title} - {message}")
def main():
seen = set()
desired_modes = [m.upper() for m in modes]
while True:
spots = fetch_spots()
for spot in spots:
mode = spot.get("mode", "").upper()
if mode in desired_modes:
# Create a unique spot ID to avoid duplicate notifications
spot_id = f"{spot.get('timeStamp')}_{spot.get('activatorCallsign')}_{spot.get('summitCode')}"
if spot_id not in seen:
callsign = spot.get("activatorCallsign", "Unknown")
freq = spot.get("frequency", "Unknown")
summit = spot.get("summitCode", "")
comment = spot.get("comments", "")
# Avoid showing 'None' or empty comments in the notification
if comment is None:
comment = ""
msg = f"{callsign} on {freq} kHz ({summit})"
if comment:
msg += f" {comment}"
notify(f"New {mode} station in SOTA Cluster", msg)
seen.add(spot_id)
# Limit memory usage by keeping only the last 1000 spot IDs
if len(seen) > 1000:
seen = set(list(seen)[-1000:])
time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
main()