2021-09-17 09:32:30 -04:00
|
|
|
import abc
|
2020-12-17 10:00:47 -05:00
|
|
|
import logging
|
|
|
|
import time
|
|
|
|
|
|
|
|
import aprslib
|
2021-09-17 09:32:30 -04:00
|
|
|
from aprslib.exceptions import LoginError
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2022-02-21 16:04:33 -05:00
|
|
|
from aprsd import config as aprsd_config
|
2022-07-07 10:47:34 -04:00
|
|
|
from aprsd import exception
|
2021-09-17 09:32:30 -04:00
|
|
|
from aprsd.clients import aprsis, kiss
|
2022-12-21 16:26:36 -05:00
|
|
|
from aprsd.packets import core, packet_list
|
2022-07-07 10:47:34 -04:00
|
|
|
from aprsd.utils import trace
|
2021-08-23 12:14:19 -04:00
|
|
|
|
|
|
|
|
2020-12-17 10:00:47 -05:00
|
|
|
LOG = logging.getLogger("APRSD")
|
2021-09-17 09:32:30 -04:00
|
|
|
TRANSPORT_APRSIS = "aprsis"
|
|
|
|
TRANSPORT_TCPKISS = "tcpkiss"
|
|
|
|
TRANSPORT_SERIALKISS = "serialkiss"
|
|
|
|
|
|
|
|
# Main must create this from the ClientFactory
|
|
|
|
# object such that it's populated with the
|
|
|
|
# Correct config
|
|
|
|
factory = None
|
2020-12-17 10:00:47 -05:00
|
|
|
|
|
|
|
|
2021-01-08 15:47:30 -05:00
|
|
|
class Client:
|
2020-12-17 10:00:47 -05:00
|
|
|
"""Singleton client class that constructs the aprslib connection."""
|
|
|
|
|
|
|
|
_instance = None
|
2021-09-17 09:32:30 -04:00
|
|
|
_client = None
|
2020-12-17 10:00:47 -05:00
|
|
|
config = None
|
|
|
|
|
2021-01-12 09:31:04 -05:00
|
|
|
connected = False
|
2021-03-30 10:43:31 -04:00
|
|
|
server_string = None
|
2022-12-14 19:21:25 -05:00
|
|
|
filter = None
|
2021-01-12 09:31:04 -05:00
|
|
|
|
2020-12-17 10:00:47 -05:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
"""This magic turns this into a singleton."""
|
|
|
|
if cls._instance is None:
|
2021-01-08 15:47:30 -05:00
|
|
|
cls._instance = super().__new__(cls)
|
2020-12-17 10:00:47 -05:00
|
|
|
# Put any initialization here.
|
|
|
|
return cls._instance
|
|
|
|
|
|
|
|
def __init__(self, config=None):
|
|
|
|
"""Initialize the object instance."""
|
|
|
|
if config:
|
|
|
|
self.config = config
|
|
|
|
|
2022-12-14 19:21:25 -05:00
|
|
|
def set_filter(self, filter):
|
|
|
|
self.filter = filter
|
|
|
|
if self._client:
|
|
|
|
self._client.set_filter(filter)
|
|
|
|
|
2020-12-17 10:00:47 -05:00
|
|
|
@property
|
|
|
|
def client(self):
|
2021-09-17 09:32:30 -04:00
|
|
|
if not self._client:
|
|
|
|
self._client = self.setup_connection()
|
2022-12-14 19:21:25 -05:00
|
|
|
if self.filter:
|
|
|
|
self._client.set_filter(self.filter)
|
2021-09-17 09:32:30 -04:00
|
|
|
return self._client
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2022-12-21 16:26:36 -05:00
|
|
|
def send(self, packet: core.Packet):
|
|
|
|
packet_list.PacketList().tx(packet)
|
|
|
|
self.client.send(packet)
|
|
|
|
|
2020-12-17 10:00:47 -05:00
|
|
|
def reset(self):
|
|
|
|
"""Call this to force a rebuild/reconnect."""
|
2022-07-20 08:43:57 -04:00
|
|
|
if self._client:
|
|
|
|
del self._client
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def setup_connection(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@abc.abstractmethod
|
|
|
|
def is_enabled(config):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@abc.abstractmethod
|
|
|
|
def transport(config):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def decode_packet(self, *args, **kwargs):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class APRSISClient(Client):
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
@staticmethod
|
|
|
|
def is_enabled(config):
|
|
|
|
# Defaults to True if the enabled flag is non existent
|
2022-02-21 16:04:33 -05:00
|
|
|
try:
|
|
|
|
return config["aprs"].get("enabled", True)
|
|
|
|
except KeyError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_configured(config):
|
|
|
|
if APRSISClient.is_enabled(config):
|
|
|
|
# Ensure that the config vars are correctly set
|
|
|
|
config.check_option("aprs.login")
|
|
|
|
config.check_option("aprs.password")
|
|
|
|
config.check_option("aprs.host")
|
|
|
|
return True
|
|
|
|
|
|
|
|
return True
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def transport(config):
|
|
|
|
return TRANSPORT_APRSIS
|
|
|
|
|
|
|
|
def decode_packet(self, *args, **kwargs):
|
|
|
|
"""APRS lib already decodes this."""
|
2022-12-15 17:23:54 -05:00
|
|
|
return core.Packet.factory(args[0])
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
@trace.trace
|
2020-12-17 10:00:47 -05:00
|
|
|
def setup_connection(self):
|
|
|
|
user = self.config["aprs"]["login"]
|
|
|
|
password = self.config["aprs"]["password"]
|
|
|
|
host = self.config["aprs"].get("host", "rotate.aprs.net")
|
|
|
|
port = self.config["aprs"].get("port", 14580)
|
|
|
|
connected = False
|
2020-12-20 12:14:51 -05:00
|
|
|
backoff = 1
|
2021-09-17 09:32:30 -04:00
|
|
|
aprs_client = None
|
2020-12-17 10:00:47 -05:00
|
|
|
while not connected:
|
|
|
|
try:
|
|
|
|
LOG.info("Creating aprslib client")
|
2021-09-17 09:32:30 -04:00
|
|
|
aprs_client = aprsis.Aprsdis(user, passwd=password, host=host, port=port)
|
2020-12-17 10:00:47 -05:00
|
|
|
# Force the logging to be the same
|
|
|
|
aprs_client.logger = LOG
|
|
|
|
aprs_client.connect()
|
|
|
|
connected = True
|
2020-12-20 12:14:51 -05:00
|
|
|
backoff = 1
|
2021-01-12 09:31:04 -05:00
|
|
|
except LoginError as e:
|
2021-08-23 12:14:19 -04:00
|
|
|
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
|
2021-01-12 09:31:04 -05:00
|
|
|
connected = False
|
|
|
|
raise e
|
2020-12-17 10:00:47 -05:00
|
|
|
except Exception as e:
|
2021-08-23 12:14:19 -04:00
|
|
|
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
|
2020-12-20 12:14:51 -05:00
|
|
|
time.sleep(backoff)
|
|
|
|
backoff = backoff * 2
|
2020-12-17 10:00:47 -05:00
|
|
|
continue
|
2021-08-23 12:14:19 -04:00
|
|
|
LOG.debug(f"Logging in to APRS-IS with user '{user}'")
|
2022-07-20 08:43:57 -04:00
|
|
|
self._client = aprs_client
|
2020-12-17 10:00:47 -05:00
|
|
|
return aprs_client
|
|
|
|
|
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
class KISSClient(Client):
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
@staticmethod
|
|
|
|
def is_enabled(config):
|
|
|
|
"""Return if tcp or serial KISS is enabled."""
|
|
|
|
if "kiss" not in config:
|
|
|
|
return False
|
|
|
|
|
2021-10-04 15:22:10 -04:00
|
|
|
if config.get("kiss.serial.enabled", default=False):
|
|
|
|
return True
|
2021-09-17 09:32:30 -04:00
|
|
|
|
2021-10-04 15:22:10 -04:00
|
|
|
if config.get("kiss.tcp.enabled", default=False):
|
|
|
|
return True
|
2021-09-17 09:32:30 -04:00
|
|
|
|
2021-12-11 07:46:43 -05:00
|
|
|
return False
|
|
|
|
|
2022-02-21 16:04:33 -05:00
|
|
|
@staticmethod
|
|
|
|
def is_configured(config):
|
|
|
|
# Ensure that the config vars are correctly set
|
|
|
|
if KISSClient.is_enabled(config):
|
|
|
|
config.check_option(
|
2022-07-28 16:24:25 -04:00
|
|
|
"aprsd.callsign",
|
|
|
|
default_fail=aprsd_config.DEFAULT_CONFIG_DICT["aprsd"]["callsign"],
|
2022-02-21 16:04:33 -05:00
|
|
|
)
|
|
|
|
transport = KISSClient.transport(config)
|
|
|
|
if transport == TRANSPORT_SERIALKISS:
|
|
|
|
config.check_option("kiss.serial")
|
|
|
|
config.check_option("kiss.serial.device")
|
|
|
|
elif transport == TRANSPORT_TCPKISS:
|
|
|
|
config.check_option("kiss.tcp")
|
|
|
|
config.check_option("kiss.tcp.host")
|
|
|
|
config.check_option("kiss.tcp.port")
|
|
|
|
|
|
|
|
return True
|
|
|
|
return True
|
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
@staticmethod
|
|
|
|
def transport(config):
|
2021-10-04 15:22:10 -04:00
|
|
|
if config.get("kiss.serial.enabled", default=False):
|
|
|
|
return TRANSPORT_SERIALKISS
|
2021-09-17 09:32:30 -04:00
|
|
|
|
2021-10-04 15:22:10 -04:00
|
|
|
if config.get("kiss.tcp.enabled", default=False):
|
|
|
|
return TRANSPORT_TCPKISS
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
def decode_packet(self, *args, **kwargs):
|
|
|
|
"""We get a frame, which has to be decoded."""
|
|
|
|
frame = kwargs["frame"]
|
|
|
|
LOG.debug(f"Got an APRS Frame '{frame}'")
|
|
|
|
# try and nuke the * from the fromcall sign.
|
|
|
|
frame.header._source._ch = False
|
|
|
|
payload = str(frame.payload.decode())
|
|
|
|
msg = f"{str(frame.header)}:{payload}"
|
|
|
|
# msg = frame.tnc2
|
|
|
|
LOG.debug(f"Decoding {msg}")
|
|
|
|
|
2022-12-15 17:23:54 -05:00
|
|
|
raw = aprslib.parse(msg)
|
|
|
|
return core.Packet.factory(raw)
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
@trace.trace
|
|
|
|
def setup_connection(self):
|
2022-11-22 13:32:19 -05:00
|
|
|
client = kiss.KISS3Client(self.config)
|
|
|
|
return client
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
|
|
|
|
class ClientFactory:
|
|
|
|
_instance = None
|
|
|
|
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
"""This magic turns this into a singleton."""
|
|
|
|
if cls._instance is None:
|
|
|
|
cls._instance = super().__new__(cls)
|
|
|
|
# Put any initialization here.
|
|
|
|
return cls._instance
|
|
|
|
|
|
|
|
def __init__(self, config):
|
|
|
|
self.config = config
|
|
|
|
self._builders = {}
|
|
|
|
|
|
|
|
def register(self, key, builder):
|
|
|
|
self._builders[key] = builder
|
|
|
|
|
|
|
|
def create(self, key=None):
|
|
|
|
if not key:
|
|
|
|
if APRSISClient.is_enabled(self.config):
|
|
|
|
key = TRANSPORT_APRSIS
|
|
|
|
elif KISSClient.is_enabled(self.config):
|
|
|
|
key = KISSClient.transport(self.config)
|
|
|
|
|
2022-11-22 13:32:19 -05:00
|
|
|
LOG.debug(f"GET client '{key}'")
|
2021-09-17 09:32:30 -04:00
|
|
|
builder = self._builders.get(key)
|
|
|
|
if not builder:
|
|
|
|
raise ValueError(key)
|
|
|
|
return builder(self.config)
|
|
|
|
|
|
|
|
def is_client_enabled(self):
|
|
|
|
"""Make sure at least one client is enabled."""
|
|
|
|
enabled = False
|
|
|
|
for key in self._builders.keys():
|
2022-02-21 16:04:33 -05:00
|
|
|
try:
|
|
|
|
enabled |= self._builders[key].is_enabled(self.config)
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return enabled
|
|
|
|
|
|
|
|
def is_client_configured(self):
|
|
|
|
enabled = False
|
|
|
|
for key in self._builders.keys():
|
|
|
|
try:
|
|
|
|
enabled |= self._builders[key].is_configured(self.config)
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
except exception.MissingConfigOptionException as ex:
|
|
|
|
LOG.error(ex.message)
|
|
|
|
return False
|
|
|
|
except exception.ConfigOptionBogusDefaultException as ex:
|
|
|
|
LOG.error(ex.message)
|
|
|
|
return False
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
return enabled
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def setup(config):
|
|
|
|
"""Create and register all possible client objects."""
|
|
|
|
global factory
|
|
|
|
|
|
|
|
factory = ClientFactory(config)
|
|
|
|
factory.register(TRANSPORT_APRSIS, APRSISClient)
|
|
|
|
factory.register(TRANSPORT_TCPKISS, KISSClient)
|
|
|
|
factory.register(TRANSPORT_SERIALKISS, KISSClient)
|