mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Make list of providers configurable, and add RBN support.
This commit is contained in:
@@ -13,9 +13,9 @@ Currently supports:
|
|||||||
* HEMA
|
* HEMA
|
||||||
* UKBOTA
|
* UKBOTA
|
||||||
* Parks n Peaks
|
* Parks n Peaks
|
||||||
|
* RBN
|
||||||
|
|
||||||
Future plans:
|
Future plans:
|
||||||
* RBN
|
|
||||||
* APRS?
|
* APRS?
|
||||||
* Packet?
|
* Packet?
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,37 @@
|
|||||||
# Rename this to "config.yml" before running the software.
|
# Rename this to "config.yml" before running the software.
|
||||||
|
|
||||||
# Your callsign. Used to log into DX clusters and included in User-Agent when querying HTTP servers. Useful so if your
|
# Your callsign. Used to log into DX clusters and included in User-Agent when querying HTTP servers. Useful so if your
|
||||||
# server goes crazy and causes other people problems, they know who to contact :)
|
# server goes crazy and causes other people problems, they know who to contact :) If you don't have one, you can leave
|
||||||
|
# this as "N0CALL" and it shouldn't do any harm, as we're not sending anything to the various networks, only receiving.
|
||||||
server-owner-callsign: "N0CALL"
|
server-owner-callsign: "N0CALL"
|
||||||
|
|
||||||
|
# Data providers to use. This is an example set, tailor it to your liking by commenting and uncommenting.
|
||||||
|
# RBN is supported but has such a high data rate, you probably don't want it enabled.
|
||||||
|
# APRS-IS support is not yet implemented.
|
||||||
|
# Feel free to write your own provider classes!
|
||||||
|
providers:
|
||||||
|
# Some providers don't require any config:
|
||||||
|
- type: "POTA"
|
||||||
|
- type: "SOTA"
|
||||||
|
- type: "WWFF"
|
||||||
|
- type: "WWBOTA"
|
||||||
|
- type: "GMA"
|
||||||
|
- type: "HEMA"
|
||||||
|
- type: "ParksNPeaks"
|
||||||
|
# Some, like DX Clusters, require extra config. You can add multiple DX clusters if you want!
|
||||||
|
-
|
||||||
|
type: "DXCluster"
|
||||||
|
host: "hrd.wa9pie.net"
|
||||||
|
port: 8000
|
||||||
|
# RBN uses two ports, 7000 for CW/RTTY and 7001 for FT8, so if you want both, you need to add two entries:
|
||||||
|
# -
|
||||||
|
# type: "RBN"
|
||||||
|
# port: 7000
|
||||||
|
# -
|
||||||
|
# type: "RBN"
|
||||||
|
# port: 7001
|
||||||
|
# - type: "APRS-IS"
|
||||||
|
|
||||||
# Port to open the local web server on
|
# Port to open the local web server on
|
||||||
web-server-port: 8080
|
web-server-port: 8080
|
||||||
|
|
||||||
|
|||||||
43
main.py
43
main.py
@@ -11,6 +11,7 @@ from providers.gma import GMA
|
|||||||
from providers.hema import HEMA
|
from providers.hema import HEMA
|
||||||
from providers.parksnpeaks import ParksNPeaks
|
from providers.parksnpeaks import ParksNPeaks
|
||||||
from providers.pota import POTA
|
from providers.pota import POTA
|
||||||
|
from providers.rbn import RBN
|
||||||
from providers.sota import SOTA
|
from providers.sota import SOTA
|
||||||
from providers.wwbota import WWBOTA
|
from providers.wwbota import WWBOTA
|
||||||
from providers.wwff import WWFF
|
from providers.wwff import WWFF
|
||||||
@@ -27,6 +28,31 @@ def shutdown(sig, frame):
|
|||||||
for p in providers: p.stop()
|
for p in providers: p.stop()
|
||||||
cleanup_timer.stop()
|
cleanup_timer.stop()
|
||||||
|
|
||||||
|
# Utility method to get a data provider based on its config entry.
|
||||||
|
# TODO we could probably find a way to do this more neatly by iterating through classes in "providers" and getting their
|
||||||
|
# names, if Python allows that sort of thing
|
||||||
|
def get_provider_from_config(config_providers_entry):
|
||||||
|
match config_providers_entry["type"]:
|
||||||
|
case "POTA":
|
||||||
|
return POTA()
|
||||||
|
case "SOTA":
|
||||||
|
return SOTA()
|
||||||
|
case "WWFF":
|
||||||
|
return WWFF()
|
||||||
|
case "GMA":
|
||||||
|
return GMA()
|
||||||
|
case "WWBOTA":
|
||||||
|
return WWBOTA()
|
||||||
|
case "HEMA":
|
||||||
|
return HEMA()
|
||||||
|
case "ParksNPeaks":
|
||||||
|
return ParksNPeaks()
|
||||||
|
case "DXCluster":
|
||||||
|
return DXCluster(config_providers_entry["host"], config_providers_entry["port"])
|
||||||
|
case "RBN":
|
||||||
|
return RBN(config_providers_entry["port"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
# Main function
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -43,21 +69,14 @@ if __name__ == '__main__':
|
|||||||
# Shut down gracefully on SIGINT
|
# Shut down gracefully on SIGINT
|
||||||
signal.signal(signal.SIGINT, shutdown)
|
signal.signal(signal.SIGINT, shutdown)
|
||||||
|
|
||||||
# Create providers
|
|
||||||
providers = [
|
|
||||||
POTA(),
|
|
||||||
SOTA(),
|
|
||||||
WWFF(),
|
|
||||||
WWBOTA(),
|
|
||||||
GMA(),
|
|
||||||
HEMA(),
|
|
||||||
ParksNPeaks(),
|
|
||||||
DXCluster("hrd.wa9pie.net", 8000),
|
|
||||||
# DXCluster("dxc.w3lpl.net", 22)
|
|
||||||
]
|
|
||||||
# Set up spot list & status data areas
|
# Set up spot list & status data areas
|
||||||
spot_list = []
|
spot_list = []
|
||||||
status_data = {}
|
status_data = {}
|
||||||
|
|
||||||
|
# Create data providers
|
||||||
|
providers = []
|
||||||
|
for entry in config["providers"]:
|
||||||
|
providers.append(get_provider_from_config(entry))
|
||||||
# Set up data providers
|
# Set up data providers
|
||||||
for p in providers: p.setup(spot_list=spot_list)
|
for p in providers: p.setup(spot_list=spot_list)
|
||||||
# Start data providers
|
# Start data providers
|
||||||
|
|||||||
96
providers/rbn.py
Normal file
96
providers/rbn.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from threading import Thread
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
import telnetlib3
|
||||||
|
|
||||||
|
from data.spot import Spot
|
||||||
|
from core.config import config
|
||||||
|
from providers.provider import Provider
|
||||||
|
|
||||||
|
|
||||||
|
# Provider for the Reverse Beacon Network. Connects to a single port, if you want both CW/RTTY (port 7000) and FT8
|
||||||
|
# (port 7001) you need to instantiate two copies of this. The port is provided as an argument to the constructor.
|
||||||
|
class RBN(Provider):
|
||||||
|
CALLSIGN_PATTERN = "([a-z|0-9|/]+)"
|
||||||
|
FREQUENCY_PATTERM = "([0-9|.]+)"
|
||||||
|
LINE_PATTERN = re.compile(
|
||||||
|
"^DX de " + CALLSIGN_PATTERN + "-.*:\\s+" + FREQUENCY_PATTERM + "\\s+" + CALLSIGN_PATTERN + "\\s+(.*)\\s+(\\d{4}Z)",
|
||||||
|
re.IGNORECASE)
|
||||||
|
|
||||||
|
# Constructor requires port number.
|
||||||
|
def __init__(self, port):
|
||||||
|
super().__init__()
|
||||||
|
self.port = port
|
||||||
|
self.telnet = None
|
||||||
|
self.thread = Thread(target=self.handle)
|
||||||
|
self.thread.daemon = True
|
||||||
|
self.run = True
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return "RBN port " + str(self.port)
|
||||||
|
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.run = False
|
||||||
|
self.telnet.close()
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
while self.run:
|
||||||
|
connected = False
|
||||||
|
while not connected and self.run:
|
||||||
|
try:
|
||||||
|
self.status = "Connecting"
|
||||||
|
logging.info("RBN port " + str(self.port) + " connecting...")
|
||||||
|
self.telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self.port)
|
||||||
|
telnet_output = self.telnet.read_until("Please enter your call: ".encode("latin-1"))
|
||||||
|
self.telnet.write((config["server-owner-callsign"] + "\n").encode("latin-1"))
|
||||||
|
connected = True
|
||||||
|
logging.info("RBN port " + str(self.port) + " connected.")
|
||||||
|
except Exception as e:
|
||||||
|
self.status = "Error"
|
||||||
|
logging.exception("Exception while connecting to RBN (port " + str(self.port) + ").")
|
||||||
|
sleep(5)
|
||||||
|
|
||||||
|
self.status = "Waiting for Data"
|
||||||
|
while connected and self.run:
|
||||||
|
try:
|
||||||
|
# Check new telnet info against regular expression
|
||||||
|
telnet_output = self.telnet.read_until("\n".encode("latin-1"))
|
||||||
|
match = self.LINE_PATTERN.match(telnet_output.decode("latin-1"))
|
||||||
|
if match:
|
||||||
|
spot_time = datetime.strptime(match.group(5), "%H%MZ")
|
||||||
|
spot_datetime = datetime.combine(datetime.today(), spot_time.time()).replace(tzinfo=pytz.UTC)
|
||||||
|
spot = Spot(source="RBN",
|
||||||
|
dx_call=match.group(3),
|
||||||
|
de_call=match.group(1),
|
||||||
|
freq=float(match.group(2)),
|
||||||
|
comment=match.group(4).strip(),
|
||||||
|
time=spot_datetime)
|
||||||
|
# Fill in any blanks
|
||||||
|
spot.infer_missing()
|
||||||
|
# Add to our list
|
||||||
|
self.submit(spot)
|
||||||
|
|
||||||
|
self.status = "OK"
|
||||||
|
self.last_update_time = datetime.now(timezone.utc)
|
||||||
|
logging.debug("Data received from RBN on port " + str(self.port) + ".")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
connected = False
|
||||||
|
if self.run:
|
||||||
|
self.status = "Error"
|
||||||
|
logging.exception("Exception in RBN provider (port " + str(self.port) + ")")
|
||||||
|
sleep(5)
|
||||||
|
else:
|
||||||
|
logging.info("RBN provider (port " + str(self.port) + ") shutting down...")
|
||||||
|
self.status = "Shutting down"
|
||||||
|
|
||||||
|
self.status = "Disconnected"
|
||||||
Reference in New Issue
Block a user