import re from datetime import datetime import pytz from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider class UKPacketNet(HTTPSpotProvider): """Spot provider for UK Packet Radio network API""" POLL_INTERVAL_SEC = 600 SPOTS_URL = "https://nodes.ukpacketradio.network/api/nodedata" def __init__(self, provider_config): super().__init__("UK Packet Net", provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC) def _http_response_to_spots(self, http_response): new_spots = [] # Iterate through source data nodes = http_response.json()["nodes"] for node in nodes.values(): # The node corresponse to the spotter here. It has an "mheard" section which indicates which nodes it has # recently heard, which will be our "DX". But "mheard" stations are not necessarily over RF, they could be # via the internet, so we also need to look up the "port" on which the station was heard, and check that it # is RF. if "mheard" in node: for heard in node["mheard"]: heard_port_id = heard["port"] if "ports" in node: for listed_port in node["ports"]: if listed_port["id"] == heard_port_id and listed_port["linkType"] == "RF": # This is another packet station heard over RF, so we are good to create a Spot object. # First build a "full" comment combining some of the extra info comment = listed_port.get("comment", "") comment = f"{comment} {listed_port['mode']}" if "mode" in listed_port else comment comment = ( f"{comment} {listed_port['modulation']}" if "modulation" in listed_port else comment ) comment = ( f"{comment} {listed_port['baud']!s} baud" if "baud" in listed_port and listed_port["baud"] > 0 else comment ) # Get frequency from the comment if it's not set properly in the data structure. This is # very hacky but a lot of node comments contain their frequency as the first or second # word of their comment, but not in the proper data structure field. freq = ( listed_port["freq"] if "freq" in listed_port and listed_port["freq"] > 0 else None ) if not freq and comment: possible_freq = comment.split(" ")[0].upper().replace("MHZ", "") if ( re.match(r"^[0-9.]+$", possible_freq) and possible_freq != "1200" and possible_freq != "9600" ): freq = float(possible_freq) * 1000000 if not freq and len(comment.split(" ")) > 1: possible_freq = comment.split(" ")[1].upper().replace("MHZ", "") if ( re.match(r"^[0-9.]+$", possible_freq) and possible_freq != "1200" and possible_freq != "9600" ): freq = float(possible_freq) * 1000000 # Check for a found frequency likely having been in kHz, sorry to all GHz packet folks if freq and freq > 1000000000: freq = freq / 1000 # Now build the spot object spot = Spot( source=self.name, dx_call=heard["callsign"].upper(), de_call=node["callsign"].upper(), freq=freq, mode="PKT", comment=comment, time=datetime.strptime(heard["lastHeard"], "%Y-%m-%d %H:%M:%S") .replace(tzinfo=pytz.UTC) .timestamp(), de_grid=node["location"].get("locator", None), de_latitude=node["location"]["coords"]["lat"], de_longitude=node["location"]["coords"]["lon"], ) # Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do # that for us. new_spots.append(spot) break # Now we have a list of every node that heard every other node via RF, as Spothole spots. What each spot doesn't # yet have is a DX lat/lon/grid, because the data doesn't provide the location of the "mheard" stations within # the structure. However, each "heard" station should also be represented in the list somewhere with its own # data, and we can use that to look these up. for spot in new_spots: if spot.dx_call in nodes: spot.dx_grid = ( nodes[spot.dx_call]["location"].get("locator", None) ) spot.dx_latitude = nodes[spot.dx_call]["location"]["coords"]["lat"] spot.dx_longitude = nodes[spot.dx_call]["location"]["coords"]["lon"] return new_spots