import logging import re from datetime import datetime from threading import Thread from time import sleep import pytz import telnetlib3 from core.config import SERVER_OWNER_CALLSIGN from data.spot import Spot from spotproviders.spot_provider import SpotProvider class DXCluster(SpotProvider): """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.""" _CALLSIGN_PATTERN = "([a-z|0-9|/]+)" _FREQUENCY_PATTERN = "([0-9|.]+)" _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) def __init__(self, provider_config): """Constructor requires hostname and port""" super().__init__(provider_config) self._hostname = provider_config["host"] self._port = provider_config["port"] 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 self._running = True def start(self): self._thread.start() def stop(self): self._running = False self._telnet.close() self._thread.join() def _handle(self): while self._running: connected = False while not connected and self._running: try: self.status = "Connecting" 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((self._login_callsign + "\n").encode("latin-1")) connected = True logging.info("DX Cluster " + self._hostname + " connected.") except Exception: self.status = "Error" logging.exception("Exception while connecting to DX Cluster Provider (" + self._hostname + ").") sleep(5) self.status = "Waiting for Data" while connected and self._running: try: # Check new telnet info against regular expression telnet_output = self._telnet.read_until("\n".encode("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) spot = Spot(source=self.name, dx_call=match.group(3), de_call=match.group(1), freq=float(match.group(2)) * 1000, comment=match.group(4).strip(), time=spot_datetime.timestamp()) # Add to our list self._submit(spot) self.status = "OK" self.last_update_time = datetime.now(pytz.UTC) logging.debug("Data received from DX Cluster " + self._hostname + ".") except Exception: connected = False if self._running: self.status = "Error" logging.exception("Exception in DX Cluster Provider (" + self._hostname + ")") sleep(5) else: logging.info("DX Cluster " + self._hostname + " shutting down...") self.status = "Shutting down" self.status = "Disconnected"