import logging from datetime import datetime from threading import Event, Thread import aprslib import pytz from core.config import SERVER_OWNER_CALLSIGN from data.spot import Spot from providers.spot.spot_provider import SpotProvider logger = logging.getLogger(__name__) class APRSIS(SpotProvider): """Spot provider for the APRS-IS.""" def __init__(self, provider_config): super().__init__("APRS-IS", provider_config) self._thread = None self._aprsis = None self._stop_event = Event() def start(self): self._thread = Thread(target=self._run, name="APRSISSpotProvider", daemon=True) self._thread.start() def _run(self): while not self._stop_event.is_set(): try: self._aprsis = aprslib.IS(SERVER_OWNER_CALLSIGN) self.status = "Connecting" logger.info("APRS-IS connecting...") self._aprsis.connect() logger.info("APRS-IS connected.") self._aprsis.consumer(self._handle, immortal=True) except Exception: if not self._stop_event.is_set(): self.status = "Error" logger.exception("Exception in APRS-IS provider") if not self._stop_event.is_set(): self._stop_event.wait(timeout=5) def stop(self): self.status = "Shutting down" self._stop_event.set() if self._aprsis: self._aprsis.close() if self._thread: self._thread.join(timeout=5) if self._thread.is_alive(): logger.warning("APRS-IS worker thread did not exit on time and will be killed.") def _handle(self, data): try: # Split SSID in "from" call and store separately from_parts = str(data["from"]).split("-") dx_call = from_parts[0].upper() dx_ssid = from_parts[1].upper() if len(from_parts) > 1 else None via_parts = str(data["via"]).split("-") de_call = via_parts[0].upper() de_ssid = via_parts[1].upper() if len(via_parts) > 1 else None spot = Spot( source="APRS-IS", dx_call=dx_call, dx_ssid=dx_ssid, de_call=de_call, de_ssid=de_ssid, comment=str(data["comment"]) if "comment" in data else None, dx_latitude=float(data["latitude"]) if data.get("latitude") is not None else None, dx_longitude=float(data["longitude"]) if data.get("longitude") is not None else None, time=datetime.now(pytz.UTC).timestamp(), ) # APRS-IS spots are live so we can assume spot time is "now" # Add to our list self._submit(spot) self.status = "OK" self.last_update_time = datetime.now(pytz.UTC) logger.debug("Data received from APRS-IS.") except Exception: logger.exception("Exception handling APRS-IS packet")