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
|
2022-12-24 13:53:06 -05:00
|
|
|
from oslo_config import cfg
|
2020-12-17 10:00:47 -05:00
|
|
|
|
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
|
|
|
|
|
|
|
|
2022-12-24 13:53:06 -05:00
|
|
|
CONF = cfg.CONF
|
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
|
|
|
|
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
|
|
|
|
|
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:
|
2023-07-24 17:02:17 -04:00
|
|
|
LOG.info("Creating APRS client")
|
2021-09-17 09:32:30 -04:00
|
|
|
self._client = self.setup_connection()
|
2022-12-14 19:21:25 -05:00
|
|
|
if self.filter:
|
2023-07-24 17:02:17 -04:00
|
|
|
LOG.info("Creating APRS client filter")
|
2022-12-14 19:21:25 -05:00
|
|
|
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
|
2023-07-20 14:44:46 -04:00
|
|
|
else:
|
|
|
|
LOG.warning("Client not initialized, nothing to reset.")
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def setup_connection(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@abc.abstractmethod
|
2022-12-24 13:53:06 -05:00
|
|
|
def is_enabled():
|
2021-09-17 09:32:30 -04:00
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@abc.abstractmethod
|
2022-12-24 13:53:06 -05:00
|
|
|
def transport():
|
2021-09-17 09:32:30 -04:00
|
|
|
pass
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def decode_packet(self, *args, **kwargs):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class APRSISClient(Client):
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2023-01-18 11:46:44 -05:00
|
|
|
_client = None
|
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
@staticmethod
|
2022-12-24 13:53:06 -05:00
|
|
|
def is_enabled():
|
2021-09-17 09:32:30 -04:00
|
|
|
# Defaults to True if the enabled flag is non existent
|
2022-02-21 16:04:33 -05:00
|
|
|
try:
|
2022-12-24 13:53:06 -05:00
|
|
|
return CONF.aprs_network.enabled
|
2022-02-21 16:04:33 -05:00
|
|
|
except KeyError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
@staticmethod
|
2022-12-24 13:53:06 -05:00
|
|
|
def is_configured():
|
|
|
|
if APRSISClient.is_enabled():
|
2022-02-21 16:04:33 -05:00
|
|
|
# Ensure that the config vars are correctly set
|
2022-12-24 13:53:06 -05:00
|
|
|
if not CONF.aprs_network.login:
|
|
|
|
LOG.error("Config aprs_network.login not set.")
|
|
|
|
raise exception.MissingConfigOptionException(
|
|
|
|
"aprs_network.login is not set.",
|
|
|
|
)
|
|
|
|
if not CONF.aprs_network.password:
|
|
|
|
LOG.error("Config aprs_network.password not set.")
|
|
|
|
raise exception.MissingConfigOptionException(
|
|
|
|
"aprs_network.password is not set.",
|
|
|
|
)
|
|
|
|
if not CONF.aprs_network.host:
|
|
|
|
LOG.error("Config aprs_network.host not set.")
|
|
|
|
raise exception.MissingConfigOptionException(
|
|
|
|
"aprs_network.host is not set.",
|
|
|
|
)
|
2022-02-21 16:04:33 -05:00
|
|
|
|
2022-12-24 13:53:06 -05:00
|
|
|
return True
|
2022-02-21 16:04:33 -05:00
|
|
|
return True
|
2021-09-17 09:32:30 -04:00
|
|
|
|
2023-01-18 11:46:44 -05:00
|
|
|
def is_alive(self):
|
|
|
|
if self._client:
|
|
|
|
return self._client.is_alive()
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
@staticmethod
|
2022-12-24 13:53:06 -05:00
|
|
|
def transport():
|
2021-09-17 09:32:30 -04:00
|
|
|
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):
|
2022-12-24 13:53:06 -05:00
|
|
|
user = CONF.aprs_network.login
|
|
|
|
password = CONF.aprs_network.password
|
|
|
|
host = CONF.aprs_network.host
|
|
|
|
port = CONF.aprs_network.port
|
2020-12-17 10:00:47 -05:00
|
|
|
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)
|
2023-07-16 16:28:15 -04:00
|
|
|
# Force the log to be the same
|
2020-12-17 10:00:47 -05:00
|
|
|
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
|
2023-07-19 11:27:34 -04:00
|
|
|
time.sleep(backoff)
|
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}' ")
|
2023-07-19 11:27:34 -04:00
|
|
|
connected = False
|
2020-12-20 12:14:51 -05:00
|
|
|
time.sleep(backoff)
|
2023-07-24 17:02:17 -04:00
|
|
|
# Don't allow the backoff to go to inifinity.
|
|
|
|
if backoff > 5:
|
|
|
|
backoff = 5
|
|
|
|
else:
|
|
|
|
backoff += 1
|
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
|
|
|
|
2023-01-18 11:46:44 -05:00
|
|
|
_client = None
|
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
@staticmethod
|
2022-12-24 13:53:06 -05:00
|
|
|
def is_enabled():
|
2021-09-17 09:32:30 -04:00
|
|
|
"""Return if tcp or serial KISS is enabled."""
|
2022-12-24 13:53:06 -05:00
|
|
|
if CONF.kiss_serial.enabled:
|
2021-10-04 15:22:10 -04:00
|
|
|
return True
|
2021-09-17 09:32:30 -04:00
|
|
|
|
2022-12-24 13:53:06 -05:00
|
|
|
if CONF.kiss_tcp.enabled:
|
2021-10-04 15:22:10 -04:00
|
|
|
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
|
2022-12-24 13:53:06 -05:00
|
|
|
def is_configured():
|
2022-02-21 16:04:33 -05:00
|
|
|
# Ensure that the config vars are correctly set
|
2022-12-24 13:53:06 -05:00
|
|
|
if KISSClient.is_enabled():
|
|
|
|
transport = KISSClient.transport()
|
2022-02-21 16:04:33 -05:00
|
|
|
if transport == TRANSPORT_SERIALKISS:
|
2022-12-24 13:53:06 -05:00
|
|
|
if not CONF.kiss_serial.device:
|
|
|
|
LOG.error("KISS serial enabled, but no device is set.")
|
|
|
|
raise exception.MissingConfigOptionException(
|
|
|
|
"kiss_serial.device is not set.",
|
|
|
|
)
|
2022-02-21 16:04:33 -05:00
|
|
|
elif transport == TRANSPORT_TCPKISS:
|
2022-12-24 13:53:06 -05:00
|
|
|
if not CONF.kiss_tcp.host:
|
|
|
|
LOG.error("KISS TCP enabled, but no host is set.")
|
|
|
|
raise exception.MissingConfigOptionException(
|
|
|
|
"kiss_tcp.host is not set.",
|
|
|
|
)
|
2022-02-21 16:04:33 -05:00
|
|
|
|
|
|
|
return True
|
2022-12-24 13:53:06 -05:00
|
|
|
return False
|
2022-02-21 16:04:33 -05:00
|
|
|
|
2023-01-18 11:46:44 -05:00
|
|
|
def is_alive(self):
|
|
|
|
if self._client:
|
|
|
|
return self._client.is_alive()
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
@staticmethod
|
2022-12-24 13:53:06 -05:00
|
|
|
def transport():
|
|
|
|
if CONF.kiss_serial.enabled:
|
2021-10-04 15:22:10 -04:00
|
|
|
return TRANSPORT_SERIALKISS
|
2021-09-17 09:32:30 -04:00
|
|
|
|
2022-12-24 13:53:06 -05:00
|
|
|
if CONF.kiss_tcp.enabled:
|
2021-10-04 15:22:10 -04:00
|
|
|
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."""
|
2023-01-02 14:20:13 -05:00
|
|
|
LOG.debug(f"kwargs {kwargs}")
|
2021-09-17 09:32:30 -04:00
|
|
|
frame = kwargs["frame"]
|
|
|
|
LOG.debug(f"Got an APRS Frame '{frame}'")
|
|
|
|
# try and nuke the * from the fromcall sign.
|
2023-01-02 14:20:13 -05:00
|
|
|
# frame.header._source._ch = False
|
|
|
|
# payload = str(frame.payload.decode())
|
|
|
|
# msg = f"{str(frame.header)}:{payload}"
|
2021-09-17 09:32:30 -04:00
|
|
|
# msg = frame.tnc2
|
2023-01-02 14:20:13 -05:00
|
|
|
# LOG.debug(f"Decoding {msg}")
|
2021-09-17 09:32:30 -04:00
|
|
|
|
2023-01-02 14:20:13 -05:00
|
|
|
raw = aprslib.parse(str(frame))
|
2022-12-15 17:23:54 -05:00
|
|
|
return core.Packet.factory(raw)
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
@trace.trace
|
|
|
|
def setup_connection(self):
|
2023-01-18 11:46:44 -05:00
|
|
|
self._client = kiss.KISS3Client()
|
|
|
|
return self._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
|
|
|
|
|
2022-12-24 13:53:06 -05:00
|
|
|
def __init__(self):
|
2021-09-17 09:32:30 -04:00
|
|
|
self._builders = {}
|
|
|
|
|
|
|
|
def register(self, key, builder):
|
|
|
|
self._builders[key] = builder
|
|
|
|
|
|
|
|
def create(self, key=None):
|
|
|
|
if not key:
|
2022-12-24 13:53:06 -05:00
|
|
|
if APRSISClient.is_enabled():
|
2021-09-17 09:32:30 -04:00
|
|
|
key = TRANSPORT_APRSIS
|
2022-12-24 13:53:06 -05:00
|
|
|
elif KISSClient.is_enabled():
|
|
|
|
key = KISSClient.transport()
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
builder = self._builders.get(key)
|
|
|
|
if not builder:
|
|
|
|
raise ValueError(key)
|
2022-12-24 13:53:06 -05:00
|
|
|
return builder()
|
2021-09-17 09:32:30 -04:00
|
|
|
|
|
|
|
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:
|
2022-12-24 13:53:06 -05:00
|
|
|
enabled |= self._builders[key].is_enabled()
|
2022-02-21 16:04:33 -05:00
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return enabled
|
|
|
|
|
|
|
|
def is_client_configured(self):
|
|
|
|
enabled = False
|
|
|
|
for key in self._builders.keys():
|
|
|
|
try:
|
2022-12-24 13:53:06 -05:00
|
|
|
enabled |= self._builders[key].is_configured()
|
2022-02-21 16:04:33 -05:00
|
|
|
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
|
2022-12-24 13:53:06 -05:00
|
|
|
def setup():
|
2021-09-17 09:32:30 -04:00
|
|
|
"""Create and register all possible client objects."""
|
|
|
|
global factory
|
|
|
|
|
2022-12-24 13:53:06 -05:00
|
|
|
factory = ClientFactory()
|
2021-09-17 09:32:30 -04:00
|
|
|
factory.register(TRANSPORT_APRSIS, APRSISClient)
|
|
|
|
factory.register(TRANSPORT_TCPKISS, KISSClient)
|
|
|
|
factory.register(TRANSPORT_SERIALKISS, KISSClient)
|