5 Commits

9 changed files with 53 additions and 29 deletions

View File

@@ -20,13 +20,18 @@ class SOTA(HTTPAlertProvider):
# Iterate through source data
for source_alert in http_response.json():
# Convert to our alert format
details = source_alert["summitDetails"].split(", ")
summit_name = details[0]
summit_points = None
if len(details) > 2:
summit_points = int(details[-1].split(" ")[0])
alert = Alert(source=self.name,
source_id=source_alert["id"],
dx_calls=[source_alert["activatingCallsign"].upper()],
dx_names=[source_alert["activatorName"].upper()],
freqs_modes=source_alert["frequency"],
comment=source_alert["comments"],
sig_refs=[SIGRef(id=source_alert["associationCode"] + "/" + source_alert["summitCode"], sig="SOTA", name=source_alert["summitDetails"])],
sig_refs=[SIGRef(id=source_alert["associationCode"] + "/" + source_alert["summitCode"], sig="SOTA", name=summit_name, activation_score=summit_points)],
start_time=datetime.strptime(source_alert["dateActivated"],
"%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC).timestamp(),
is_dxpedition=False)

View File

@@ -59,14 +59,32 @@ spot-providers:
enabled: true
host: "hrd.wa9pie.net"
port: 8000
login_prompt: "login: "
# Prompt the cluster node gives when asking for a callsign to log in. Varies between cluster node software.
login_prompt: "login:"
# Callsign Spothole will use to log into this cluster. Ensure the SSID (e.g. -99) is different to any personal
# connection you might make to this cluster node.
login_callsign: "N0CALL-99"
# Whether to allow RBN spots that come via this cluster. If you don't want RBN spots or you are making a separate
# connection to RBN directly, leave this as False. If you want RBN spots from this cluster, set this to True. (Make
# sure you aren't also separately connecting to RBN directly, otherwise you may get duplicate spots.) Note that not
# all clusters sent RBN spots anyway.
allow_rbn_spots: false
-
class: "DXCluster"
name: "W3LPL Cluster"
enabled: false
host: "w3lpl.net"
port: 7373
login_prompt: "Please enter your call: "
# Prompt the cluster node gives when asking for a callsign to log in. Varies between cluster node software.
login_prompt: "Please enter your call:"
# Callsign Spothole will use to log into this cluster. Ensure the SSID (e.g. -99) is different to any personal
# connection you might make to this cluster node.
login_callsign: "N0CALL-99"
# Whether to allow RBN spots that come via this cluster. If you don't want RBN spots or you are making a separate
# connection to RBN directly, leave this as False. If you want RBN spots from this cluster, set this to True. (Make
# sure you aren't also separately connecting to RBN directly, otherwise you may get duplicate spots.) Note that not
# all clusters sent RBN spots anyway.
allow_rbn_spots: false
-
class: "RBN"
name: "RBN CW/RTTY"

View File

@@ -46,6 +46,7 @@ def populate_sig_ref_info(sig_ref):
sig_ref.grid = data["locator"] if "locator" in data else None
sig_ref.latitude = data["latitude"] if "latitude" in data else None
sig_ref.longitude = data["longitude"] if "longitude" in data else None
sig_ref.activation_score = data["points"] if "points" in data else None
elif sig.upper() == "WWBOTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://api.wwbota.org/bunkers/" + ref_id,
headers=HTTP_HEADERS).json()

View File

@@ -53,8 +53,6 @@ class Alert:
sig: str = None
# SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO
sig_refs: list = None
# Activation score. SOTA only
activation_score: int = None
# Whether this alert is for a DXpedition, as opposed to e.g. an xOTA programme.
is_dxpedition: bool = False
# Where we got the alert from, e.g. "POTA", "SOTA"...

View File

@@ -18,3 +18,5 @@ class SIGRef:
longitude: float = None
# Maidenhead grid reference of the reference, if known.
grid: str = None
# Activation score. SOTA only
activation_score: int = None

View File

@@ -106,8 +106,6 @@ class Spot:
sig: str = None
# SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO
sig_refs: list = None
# Activation score. SOTA only
activation_score: int = None
# Timing info
@@ -224,8 +222,8 @@ class Spot:
if self.mode and not self.mode_type:
self.mode_type = lookup_helper.infer_mode_type_from_mode(self.mode)
# If we have a latitude at this point, it can only have been provided by the spot itself
if self.dx_latitude:
# If we have a latitude or grid at this point, it can only have been provided by the spot itself
if self.dx_latitude or self.dx_grid:
self.dx_location_source = "SPOT"
# Set the top-level "SIG" if it is missing but we have at least one SIG ref.

View File

@@ -12,22 +12,27 @@ from data.spot import Spot
from spotproviders.spot_provider import SpotProvider
# Spot provider for a DX Cluster. Hostname port and login_prompt provided as parameters.
# Spot provider for a DX Cluster. Hostname, port, login_prompt, login_callsign and allow_rbn_spots are provided in config.
# See config-example.yml for examples.
class DXCluster(SpotProvider):
# Note the callsign pattern deliberately excludes calls ending in "-#", which are from RBN and can be enabled by
# default on some clusters. If you want RBN spots, there is a separate provider for that.
CALLSIGN_PATTERN = "([a-z|0-9|/]+)"
FREQUENCY_PATTERN = "([0-9|.]+)"
LINE_PATTERN = re.compile(
LINE_PATTERN_EXCLUDE_RBN = re.compile(
"^DX de " + CALLSIGN_PATTERN + ":\\s+" + FREQUENCY_PATTERN + "\\s+" + CALLSIGN_PATTERN + "\\s+(.*)\\s+(\\d{4}Z)",
re.IGNORECASE)
LINE_PATTERN_ALLOW_RBN = re.compile(
"^DX de " + CALLSIGN_PATTERN + "-?#?:\\s+" + FREQUENCY_PATTERN + "\\s+" + CALLSIGN_PATTERN + "\\s+(.*)\\s+(\\d{4}Z)",
re.IGNORECASE)
# Constructor requires hostname and port
def __init__(self, provider_config):
super().__init__(provider_config)
self.hostname = provider_config["host"]
self.port = provider_config["port"]
self.login_prompt = provider_config["login_prompt"]
self.login_prompt = provider_config["login_prompt"] if "login_prompt" in provider_config else "login:"
self.login_callsign = provider_config["login_callsign"] if "login_callsign" in provider_config else SERVER_OWNER_CALLSIGN
self.allow_rbn_spots = provider_config["allow_rbn_spots"] if "allow_rbn_spots" in provider_config else False
self.spot_line_pattern = self.LINE_PATTERN_ALLOW_RBN if self.allow_rbn_spots else self.LINE_PATTERN_EXCLUDE_RBN
self.telnet = None
self.thread = Thread(target=self.handle)
self.thread.daemon = True
@@ -50,7 +55,7 @@ class DXCluster(SpotProvider):
logging.info("DX Cluster " + self.hostname + " connecting...")
self.telnet = telnetlib3.Telnet(self.hostname, self.port)
self.telnet.read_until(self.login_prompt.encode("latin-1"))
self.telnet.write((SERVER_OWNER_CALLSIGN + "\n").encode("latin-1"))
self.telnet.write((self.login_callsign + "\n").encode("latin-1"))
connected = True
logging.info("DX Cluster " + self.hostname + " connected.")
except Exception as e:
@@ -63,7 +68,7 @@ class DXCluster(SpotProvider):
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"))
match = self.spot_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)

View File

@@ -45,9 +45,8 @@ class SOTA(HTTPSpotProvider):
mode=source_spot["mode"].upper(),
comment=source_spot["comments"],
sig="SOTA",
sig_refs=[SIGRef(id=source_spot["summitCode"], sig="SOTA", name=source_spot["summitName"])],
time=datetime.fromisoformat(source_spot["timeStamp"]).timestamp(),
activation_score=source_spot["points"])
sig_refs=[SIGRef(id=source_spot["summitCode"], sig="SOTA", name=source_spot["summitName"], activation_score=source_spot["points"])],
time=datetime.fromisoformat(source_spot["timeStamp"]).timestamp())
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
# that for us.

View File

@@ -14,7 +14,9 @@ info:
### 1.1
Added Server-Sent Event API endpoint. Removed band colour and icon information from spots.
* Added Server-Sent Event API endpoints for spots and alerts.
* Removed band colour and icon information from spots.
* Moved activation_score from top-level in Spot and Alert to be part of the SIGRef
contact:
email: ian@ianrenton.com
license:
@@ -940,6 +942,10 @@ components:
type: number
description: Longitude of the reference, in degrees, if known.
example: -1.2345
activation_score:
type: integer
description: Activation score. SOTA only
example: 0
Spot:
type: object
@@ -1086,10 +1092,6 @@ components:
items:
$ref: '#/components/schemas/SIGRef'
description: SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO
activation_score:
type: integer
description: Activation score. SOTA only
example: 0
qrt:
type: boolean
description: QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.
@@ -1194,10 +1196,6 @@ components:
items:
$ref: '#/components/schemas/SIGRef'
description: SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO
activation_score:
type: integer
description: Activation score. SOTA only
example: 0
source:
type: string
description: Where we got the alert from.