1
0
mirror of https://github.com/craigerl/aprsd.git synced 2024-11-24 17:08:38 -05:00

Compare commits

...

2 Commits

Author SHA1 Message Date
18e6d9282e
Merge 0441bd5aa3 into fa2d2d965d 2024-05-19 21:02:37 +00:00
0441bd5aa3 Refactor client and drivers
this patch refactors the client, drivers and client factory
to use the same Protocol mechanism used by the stats collector
to construct the proper client to be used according to
the configuration
2024-05-19 17:02:26 -04:00
24 changed files with 576 additions and 513 deletions

View File

@ -1,461 +0,0 @@
import abc
import datetime
import logging
import threading
import time
import aprslib
from aprslib.exceptions import LoginError
from oslo_config import cfg
import wrapt
from aprsd import exception
from aprsd.clients import aprsis, fake, kiss
from aprsd.packets import core
from aprsd.utils import singleton, trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
TRANSPORT_APRSIS = "aprsis"
TRANSPORT_TCPKISS = "tcpkiss"
TRANSPORT_SERIALKISS = "serialkiss"
TRANSPORT_FAKE = "fake"
# Main must create this from the ClientFactory
# object such that it's populated with the
# Correct config
factory = None
@singleton
class APRSClientStats:
lock = threading.Lock()
@wrapt.synchronized(lock)
def stats(self, serializable=False):
client = factory.create()
stats = {
"transport": client.transport(),
"filter": client.filter,
"connected": client.connected,
}
if client.transport() == TRANSPORT_APRSIS:
stats["server_string"] = client.client.server_string
keepalive = client.client.aprsd_keepalive
if serializable:
keepalive = keepalive.isoformat()
stats["server_keepalive"] = keepalive
elif client.transport() == TRANSPORT_TCPKISS:
stats["host"] = CONF.kiss_tcp.host
stats["port"] = CONF.kiss_tcp.port
elif client.transport() == TRANSPORT_SERIALKISS:
stats["device"] = CONF.kiss_serial.device
return stats
class Client:
"""Singleton client class that constructs the aprslib connection."""
_instance = None
_client = None
connected = False
filter = None
lock = threading.Lock()
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.
cls._instance._create_client()
return cls._instance
@abc.abstractmethod
def stats(self) -> dict:
pass
def set_filter(self, filter):
self.filter = filter
if self._client:
self._client.set_filter(filter)
@property
def client(self):
if not self._client:
self._create_client()
return self._client
def _create_client(self):
self._client = self.setup_connection()
if self.filter:
LOG.info("Creating APRS client filter")
self._client.set_filter(self.filter)
def stop(self):
if self._client:
LOG.info("Stopping client connection.")
self._client.stop()
def send(self, packet: core.Packet):
"""Send a packet to the network."""
self.client.send(packet)
@wrapt.synchronized(lock)
def reset(self):
"""Call this to force a rebuild/reconnect."""
LOG.info("Resetting client connection.")
if self._client:
self._client.close()
del self._client
self._create_client()
else:
LOG.warning("Client not initialized, nothing to reset.")
# Recreate the client
LOG.info(f"Creating new client {self.client}")
@abc.abstractmethod
def setup_connection(self):
pass
@staticmethod
@abc.abstractmethod
def is_enabled():
pass
@staticmethod
@abc.abstractmethod
def transport():
pass
@abc.abstractmethod
def decode_packet(self, *args, **kwargs):
pass
@abc.abstractmethod
def consumer(self, callback, blocking=False, immortal=False, raw=False):
pass
@abc.abstractmethod
def is_alive(self):
pass
@abc.abstractmethod
def close(self):
pass
class APRSISClient(Client):
_client = None
def __init__(self):
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
self.max_delta = datetime.timedelta(**max_timeout)
def stats(self) -> dict:
stats = {}
if self.is_configured():
stats = {
"server_string": self._client.server_string,
"sever_keepalive": self._client.aprsd_keepalive,
"filter": self.filter,
}
return stats
@staticmethod
def is_enabled():
# Defaults to True if the enabled flag is non existent
try:
return CONF.aprs_network.enabled
except KeyError:
return False
@staticmethod
def is_configured():
if APRSISClient.is_enabled():
# Ensure that the config vars are correctly set
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.",
)
return True
return True
def _is_stale_connection(self):
delta = datetime.datetime.now() - self._client.aprsd_keepalive
if delta > self.max_delta:
LOG.error(f"Connection is stale, last heard {delta} ago.")
return True
def is_alive(self):
if self._client:
return self._client.is_alive() and not self._is_stale_connection()
else:
LOG.warning(f"APRS_CLIENT {self._client} alive? NO!!!")
return False
def close(self):
if self._client:
self._client.stop()
self._client.close()
@staticmethod
def transport():
return TRANSPORT_APRSIS
def decode_packet(self, *args, **kwargs):
"""APRS lib already decodes this."""
return core.factory(args[0])
def setup_connection(self):
user = CONF.aprs_network.login
password = CONF.aprs_network.password
host = CONF.aprs_network.host
port = CONF.aprs_network.port
self.connected = False
backoff = 1
aprs_client = None
while not self.connected:
try:
LOG.info(f"Creating aprslib client({host}:{port}) and logging in {user}.")
aprs_client = aprsis.Aprsdis(user, passwd=password, host=host, port=port)
# Force the log to be the same
aprs_client.logger = LOG
aprs_client.connect()
self.connected = True
backoff = 1
except LoginError as e:
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
self.connected = False
time.sleep(backoff)
except Exception as e:
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
self.connected = False
time.sleep(backoff)
# Don't allow the backoff to go to inifinity.
if backoff > 5:
backoff = 5
else:
backoff += 1
continue
self._client = aprs_client
return aprs_client
def consumer(self, callback, blocking=False, immortal=False, raw=False):
self._client.consumer(
callback, blocking=blocking,
immortal=immortal, raw=raw,
)
class KISSClient(Client):
_client = None
def stats(self) -> dict:
stats = {}
if self.is_configured():
return {
"transport": self.transport(),
}
return stats
@staticmethod
def is_enabled():
"""Return if tcp or serial KISS is enabled."""
if CONF.kiss_serial.enabled:
return True
if CONF.kiss_tcp.enabled:
return True
return False
@staticmethod
def is_configured():
# Ensure that the config vars are correctly set
if KISSClient.is_enabled():
transport = KISSClient.transport()
if transport == TRANSPORT_SERIALKISS:
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.",
)
elif transport == TRANSPORT_TCPKISS:
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.",
)
return True
return False
def is_alive(self):
if self._client:
return self._client.is_alive()
else:
return False
def close(self):
if self._client:
self._client.stop()
@staticmethod
def transport():
if CONF.kiss_serial.enabled:
return TRANSPORT_SERIALKISS
if CONF.kiss_tcp.enabled:
return TRANSPORT_TCPKISS
def decode_packet(self, *args, **kwargs):
"""We get a frame, which has to be decoded."""
LOG.debug(f"kwargs {kwargs}")
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}")
raw = aprslib.parse(str(frame))
packet = core.factory(raw)
if isinstance(packet, core.ThirdParty):
return packet.subpacket
else:
return packet
def setup_connection(self):
self._client = kiss.KISS3Client()
self.connected = True
return self._client
def consumer(self, callback, blocking=False, immortal=False, raw=False):
self._client.consumer(callback)
class APRSDFakeClient(Client, metaclass=trace.TraceWrapperMetaclass):
def stats(self) -> dict:
return {}
@staticmethod
def is_enabled():
if CONF.fake_client.enabled:
return True
return False
@staticmethod
def is_configured():
return APRSDFakeClient.is_enabled()
def is_alive(self):
return True
def close(self):
pass
def setup_connection(self):
self.connected = True
return fake.APRSDFakeClient()
@staticmethod
def transport():
return TRANSPORT_FAKE
def decode_packet(self, *args, **kwargs):
LOG.debug(f"kwargs {kwargs}")
pkt = kwargs["packet"]
LOG.debug(f"Got an APRS Fake Packet '{pkt}'")
return pkt
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):
self._builders = {}
def register(self, key, builder):
self._builders[key] = builder
def create(self, key=None):
if not key:
if APRSISClient.is_enabled():
key = TRANSPORT_APRSIS
elif KISSClient.is_enabled():
key = KISSClient.transport()
elif APRSDFakeClient.is_enabled():
key = TRANSPORT_FAKE
builder = self._builders.get(key)
if not builder:
raise ValueError(key)
return builder()
def is_client_enabled(self):
"""Make sure at least one client is enabled."""
enabled = False
for key in self._builders.keys():
try:
enabled |= self._builders[key].is_enabled()
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()
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
return enabled
@staticmethod
def setup():
"""Create and register all possible client objects."""
global factory
factory = ClientFactory()
factory.register(TRANSPORT_APRSIS, APRSISClient)
factory.register(TRANSPORT_TCPKISS, KISSClient)
factory.register(TRANSPORT_SERIALKISS, KISSClient)
factory.register(TRANSPORT_FAKE, APRSDFakeClient)

13
aprsd/client/__init__.py Normal file
View File

@ -0,0 +1,13 @@
from aprsd.client import aprsis, factory, fake, kiss
TRANSPORT_APRSIS = "aprsis"
TRANSPORT_TCPKISS = "tcpkiss"
TRANSPORT_SERIALKISS = "serialkiss"
TRANSPORT_FAKE = "fake"
client_factory = factory.ClientFactory()
client_factory.register(aprsis.APRSISClient)
client_factory.register(kiss.KISSClient)
client_factory.register(fake.APRSDFakeClient)

132
aprsd/client/aprsis.py Normal file
View File

@ -0,0 +1,132 @@
import datetime
import logging
import time
from aprslib.exceptions import LoginError
from oslo_config import cfg
from aprsd import client, exception
from aprsd.client import base
from aprsd.client.drivers import aprsis
from aprsd.packets import core
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class APRSISClient(base.APRSClient):
_client = None
def __init__(self):
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
self.max_delta = datetime.timedelta(**max_timeout)
def stats(self) -> dict:
stats = {}
if self.is_configured():
stats = {
"server_string": self._client.server_string,
"sever_keepalive": self._client.aprsd_keepalive,
"filter": self.filter,
}
return stats
@staticmethod
def is_enabled():
# Defaults to True if the enabled flag is non existent
try:
return CONF.aprs_network.enabled
except KeyError:
return False
@staticmethod
def is_configured():
if APRSISClient.is_enabled():
# Ensure that the config vars are correctly set
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.",
)
return True
return True
def _is_stale_connection(self):
delta = datetime.datetime.now() - self._client.aprsd_keepalive
if delta > self.max_delta:
LOG.error(f"Connection is stale, last heard {delta} ago.")
return True
def is_alive(self):
if self._client:
return self._client.is_alive() and not self._is_stale_connection()
else:
LOG.warning(f"APRS_CLIENT {self._client} alive? NO!!!")
return False
def close(self):
if self._client:
self._client.stop()
self._client.close()
@staticmethod
def transport():
return client.TRANSPORT_APRSIS
def decode_packet(self, *args, **kwargs):
"""APRS lib already decodes this."""
return core.factory(args[0])
def setup_connection(self):
user = CONF.aprs_network.login
password = CONF.aprs_network.password
host = CONF.aprs_network.host
port = CONF.aprs_network.port
self.connected = False
backoff = 1
aprs_client = None
while not self.connected:
try:
LOG.info(f"Creating aprslib client({host}:{port}) and logging in {user}.")
aprs_client = aprsis.Aprsdis(user, passwd=password, host=host, port=port)
# Force the log to be the same
aprs_client.logger = LOG
aprs_client.connect()
self.connected = True
backoff = 1
except LoginError as e:
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
self.connected = False
time.sleep(backoff)
except Exception as e:
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
self.connected = False
time.sleep(backoff)
# Don't allow the backoff to go to inifinity.
if backoff > 5:
backoff = 5
else:
backoff += 1
continue
self._client = aprs_client
return aprs_client
def consumer(self, callback, blocking=False, immortal=False, raw=False):
self._client.consumer(
callback, blocking=blocking,
immortal=immortal, raw=raw,
)

105
aprsd/client/base.py Normal file
View File

@ -0,0 +1,105 @@
import abc
import logging
import threading
from oslo_config import cfg
import wrapt
from aprsd.packets import core
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class APRSClient:
"""Singleton client class that constructs the aprslib connection."""
_instance = None
_client = None
connected = False
filter = None
lock = threading.Lock()
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.
cls._instance._create_client()
return cls._instance
@abc.abstractmethod
def stats(self) -> dict:
pass
def set_filter(self, filter):
self.filter = filter
if self._client:
self._client.set_filter(filter)
@property
def client(self):
if not self._client:
self._create_client()
return self._client
def _create_client(self):
self._client = self.setup_connection()
if self.filter:
LOG.info("Creating APRS client filter")
self._client.set_filter(self.filter)
def stop(self):
if self._client:
LOG.info("Stopping client connection.")
self._client.stop()
def send(self, packet: core.Packet):
"""Send a packet to the network."""
self.client.send(packet)
@wrapt.synchronized(lock)
def reset(self):
"""Call this to force a rebuild/reconnect."""
LOG.info("Resetting client connection.")
if self._client:
self._client.close()
del self._client
self._create_client()
else:
LOG.warning("Client not initialized, nothing to reset.")
# Recreate the client
LOG.info(f"Creating new client {self.client}")
@abc.abstractmethod
def setup_connection(self):
pass
@staticmethod
@abc.abstractmethod
def is_enabled():
pass
@staticmethod
@abc.abstractmethod
def transport():
pass
@abc.abstractmethod
def decode_packet(self, *args, **kwargs):
pass
@abc.abstractmethod
def consumer(self, callback, blocking=False, immortal=False, raw=False):
pass
@abc.abstractmethod
def is_alive(self):
pass
@abc.abstractmethod
def close(self):
pass

88
aprsd/client/factory.py Normal file
View File

@ -0,0 +1,88 @@
import logging
from typing import Callable, Protocol, runtime_checkable
from aprsd import exception
from aprsd.packets import core
LOG = logging.getLogger("APRSD")
@runtime_checkable
class Client(Protocol):
def __init__(self):
pass
def connect(self) -> bool:
pass
def disconnect(self) -> bool:
pass
def decode_packet(self, *args, **kwargs) -> type[core.Packet]:
pass
def is_enabled(self) -> bool:
pass
def is_configured(self) -> bool:
pass
def transport(self) -> str:
pass
def send(self, message: str) -> bool:
pass
def setup_connection(self) -> None:
pass
class ClientFactory:
_instance = None
clients = []
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):
self.clients: list[Callable] = []
def register(self, aprsd_client: Callable):
if isinstance(aprsd_client, Client):
raise ValueError("Client must be a subclass of Client protocol")
self.clients.append(aprsd_client)
def create(self, key=None):
for client in self.clients:
if client.is_enabled():
return client()
raise Exception("No client is configured!!")
def is_client_enabled(self):
"""Make sure at least one client is enabled."""
enabled = False
for client in self.clients:
if client.is_enabled():
enabled = True
return enabled
def is_client_configured(self):
enabled = False
for client in self.clients:
try:
if client.is_configured():
enabled = True
except exception.MissingConfigOptionException as ex:
LOG.error(ex.message)
return False
except exception.ConfigOptionBogusDefaultException as ex:
LOG.error(ex.message)
return False
return enabled

48
aprsd/client/fake.py Normal file
View File

@ -0,0 +1,48 @@
import logging
from oslo_config import cfg
from aprsd import client
from aprsd.client import base
from aprsd.client.drivers import fake as fake_driver
from aprsd.utils import trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class APRSDFakeClient(base.APRSClient, metaclass=trace.TraceWrapperMetaclass):
def stats(self) -> dict:
return {}
@staticmethod
def is_enabled():
if CONF.fake_client.enabled:
return True
return False
@staticmethod
def is_configured():
return APRSDFakeClient.is_enabled()
def is_alive(self):
return True
def close(self):
pass
def setup_connection(self):
self.connected = True
return fake_driver.APRSDFakeClient()
@staticmethod
def transport():
return client.TRANSPORT_FAKE
def decode_packet(self, *args, **kwargs):
LOG.debug(f"kwargs {kwargs}")
pkt = kwargs["packet"]
LOG.debug(f"Got an APRS Fake Packet '{pkt}'")
return pkt

103
aprsd/client/kiss.py Normal file
View File

@ -0,0 +1,103 @@
import logging
import aprslib
from oslo_config import cfg
from aprsd import client, exception
from aprsd.client import base
from aprsd.client.drivers import kiss
from aprsd.packets import core
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class KISSClient(base.APRSClient):
_client = None
def stats(self) -> dict:
stats = {}
if self.is_configured():
return {
"transport": self.transport(),
}
return stats
@staticmethod
def is_enabled():
"""Return if tcp or serial KISS is enabled."""
if CONF.kiss_serial.enabled:
return True
if CONF.kiss_tcp.enabled:
return True
return False
@staticmethod
def is_configured():
# Ensure that the config vars are correctly set
if KISSClient.is_enabled():
transport = KISSClient.transport()
if transport == client.TRANSPORT_SERIALKISS:
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.",
)
elif transport == client.TRANSPORT_TCPKISS:
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.",
)
return True
return False
def is_alive(self):
if self._client:
return self._client.is_alive()
else:
return False
def close(self):
if self._client:
self._client.stop()
@staticmethod
def transport():
if CONF.kiss_serial.enabled:
return client.TRANSPORT_SERIALKISS
if CONF.kiss_tcp.enabled:
return client.TRANSPORT_TCPKISS
def decode_packet(self, *args, **kwargs):
"""We get a frame, which has to be decoded."""
LOG.debug(f"kwargs {kwargs}")
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}")
raw = aprslib.parse(str(frame))
packet = core.factory(raw)
if isinstance(packet, core.ThirdParty):
return packet.subpacket
else:
return packet
def setup_connection(self):
self._client = kiss.KISS3Client()
self.connected = True
return self._client
def consumer(self, callback, blocking=False, immortal=False, raw=False):
self._client.consumer(callback)

38
aprsd/client/stats.py Normal file
View File

@ -0,0 +1,38 @@
import threading
from oslo_config import cfg
import wrapt
from aprsd import client
from aprsd.utils import singleton
CONF = cfg.CONF
@singleton
class APRSClientStats:
lock = threading.Lock()
@wrapt.synchronized(lock)
def stats(self, serializable=False):
cl = client.client_factory.create()
stats = {
"transport": cl.transport(),
"filter": cl.filter,
"connected": cl.connected,
}
if cl.transport() == client.TRANSPORT_APRSIS:
stats["server_string"] = cl.client.server_string
keepalive = cl.client.aprsd_keepalive
if serializable:
keepalive = keepalive.isoformat()
stats["server_keepalive"] = keepalive
elif cl.transport() == client.TRANSPORT_TCPKISS:
stats["host"] = CONF.kiss_tcp.host
stats["port"] = CONF.kiss_tcp.port
elif cl.transport() == client.TRANSPORT_SERIALKISS:
stats["device"] = CONF.kiss_serial.device
return stats

View File

@ -8,8 +8,9 @@ import logging
import click import click
from oslo_config import cfg from oslo_config import cfg
from aprsd import cli_helper, conf, packets, plugin
# local imports here # local imports here
from aprsd import cli_helper, client, conf, packets, plugin from aprsd.client import base
from aprsd.main import cli from aprsd.main import cli
from aprsd.utils import trace from aprsd.utils import trace
@ -96,7 +97,7 @@ def test_plugin(
if CONF.trace_enabled: if CONF.trace_enabled:
trace.setup_tracing(["method", "api"]) trace.setup_tracing(["method", "api"])
client.Client() base.APRSClient()
pm = plugin.PluginManager() pm = plugin.PluginManager()
if load_all: if load_all:

View File

@ -15,7 +15,8 @@ from rich.console import Console
# local imports here # local imports here
import aprsd import aprsd
from aprsd import cli_helper, client, packets, plugin, threads from aprsd import cli_helper, packets, plugin, threads
from aprsd.client import client_factory
from aprsd.main import cli from aprsd.main import cli
from aprsd.packets import collector as packet_collector from aprsd.packets import collector as packet_collector
from aprsd.packets import log as packet_log from aprsd.packets import log as packet_log
@ -179,15 +180,14 @@ def listen(
# Initialize the client factory and create # Initialize the client factory and create
# The correct client object ready for use # The correct client object ready for use
client.ClientFactory.setup()
# Make sure we have 1 client transport enabled # Make sure we have 1 client transport enabled
if not client.factory.is_client_enabled(): if not client_factory.is_client_enabled():
LOG.error("No Clients are enabled in config.") LOG.error("No Clients are enabled in config.")
sys.exit(-1) sys.exit(-1)
# Creates the client object # Creates the client object
LOG.info("Creating client connection") LOG.info("Creating client connection")
aprs_client = client.factory.create() aprs_client = client_factory.create()
LOG.info(aprs_client) LOG.info(aprs_client)
LOG.debug(f"Filter by '{filter}'") LOG.debug(f"Filter by '{filter}'")

View File

@ -8,8 +8,9 @@ import click
from oslo_config import cfg from oslo_config import cfg
import aprsd import aprsd
from aprsd import cli_helper, client, packets from aprsd import cli_helper, packets
from aprsd import conf # noqa : F401 from aprsd import conf # noqa : F401
from aprsd.client import client_factory
from aprsd.main import cli from aprsd.main import cli
from aprsd.packets import collector from aprsd.packets import collector
from aprsd.threads import tx from aprsd.threads import tx
@ -102,7 +103,7 @@ def send_message(
def rx_packet(packet): def rx_packet(packet):
global got_ack, got_response global got_ack, got_response
cl = client.factory.create() cl = client_factory.create()
packet = cl.decode_packet(packet) packet = cl.decode_packet(packet)
collector.PacketCollector().rx(packet) collector.PacketCollector().rx(packet)
packet.log("RX") packet.log("RX")
@ -130,8 +131,7 @@ def send_message(
sys.exit(0) sys.exit(0)
try: try:
client.ClientFactory.setup() client_factory.create().client
client.factory.create().client
except LoginError: except LoginError:
sys.exit(-1) sys.exit(-1)
@ -163,7 +163,7 @@ def send_message(
# This will register a packet consumer with aprslib # This will register a packet consumer with aprslib
# When new packets come in the consumer will process # When new packets come in the consumer will process
# the packet # the packet
aprs_client = client.factory.create().client aprs_client = client_factory.create().client
aprs_client.consumer(rx_packet, raw=False) aprs_client.consumer(rx_packet, raw=False)
except aprslib.exceptions.ConnectionDrop: except aprslib.exceptions.ConnectionDrop:
LOG.error("Connection dropped, reconnecting") LOG.error("Connection dropped, reconnecting")

View File

@ -6,9 +6,10 @@ import click
from oslo_config import cfg from oslo_config import cfg
import aprsd import aprsd
from aprsd import cli_helper, client from aprsd import cli_helper
from aprsd import main as aprsd_main from aprsd import main as aprsd_main
from aprsd import packets, plugin, threads, utils from aprsd import packets, plugin, threads, utils
from aprsd.client import client_factory
from aprsd.main import cli from aprsd.main import cli
from aprsd.packets import collector as packet_collector from aprsd.packets import collector as packet_collector
from aprsd.packets import seen_list from aprsd.packets import seen_list
@ -49,14 +50,13 @@ def server(ctx, flush):
# Initialize the client factory and create # Initialize the client factory and create
# The correct client object ready for use # The correct client object ready for use
client.ClientFactory.setup() if not client_factory.is_client_enabled():
if not client.factory.is_client_enabled():
LOG.error("No Clients are enabled in config.") LOG.error("No Clients are enabled in config.")
sys.exit(-1) sys.exit(-1)
# Creates the client object # Creates the client object
LOG.info("Creating client connection") LOG.info("Creating client connection")
aprs_client = client.factory.create() aprs_client = client_factory.create()
LOG.info(aprs_client) LOG.info(aprs_client)
# Create the initial PM singleton and Register plugins # Create the initial PM singleton and Register plugins
@ -79,18 +79,14 @@ def server(ctx, flush):
LOG.info(p) LOG.info(p)
# Make sure we have 1 client transport enabled # Make sure we have 1 client transport enabled
if not client.factory.is_client_enabled(): if not client_factory.is_client_enabled():
LOG.error("No Clients are enabled in config.") LOG.error("No Clients are enabled in config.")
sys.exit(-1) sys.exit(-1)
if not client.factory.is_client_configured(): if not client_factory.is_client_configured():
LOG.error("APRS client is not properly configured in config file.") LOG.error("APRS client is not properly configured in config file.")
sys.exit(-1) sys.exit(-1)
# Creates the client object
# LOG.info("Creating client connection")
# client.factory.create().client
# Now load the msgTrack from disk if any # Now load the msgTrack from disk if any
packets.PacketList() packets.PacketList()
if flush: if flush:

View File

@ -21,6 +21,7 @@ import aprsd
from aprsd import ( from aprsd import (
cli_helper, client, packets, plugin_utils, stats, threads, utils, cli_helper, client, packets, plugin_utils, stats, threads, utils,
) )
from aprsd.client import client_factory, kiss
from aprsd.main import cli from aprsd.main import cli
from aprsd.threads import aprsd as aprsd_threads from aprsd.threads import aprsd as aprsd_threads
from aprsd.threads import keep_alive, rx, tx from aprsd.threads import keep_alive, rx, tx
@ -380,8 +381,8 @@ def _get_transport(stats):
"APRS-IS Server: <a href='http://status.aprs2.net' >" "APRS-IS Server: <a href='http://status.aprs2.net' >"
"{}</a>".format(stats["APRSClientStats"]["server_string"]) "{}</a>".format(stats["APRSClientStats"]["server_string"])
) )
elif client.KISSClient.is_enabled(): elif kiss.KISSClient.is_enabled():
transport = client.KISSClient.transport() transport = kiss.KISSClient.transport()
if transport == client.TRANSPORT_TCPKISS: if transport == client.TRANSPORT_TCPKISS:
aprs_connection = ( aprs_connection = (
"TCPKISS://{}:{}".format( "TCPKISS://{}:{}".format(
@ -637,13 +638,12 @@ def webchat(ctx, flush, port):
# Initialize the client factory and create # Initialize the client factory and create
# The correct client object ready for use # The correct client object ready for use
client.ClientFactory.setup()
# Make sure we have 1 client transport enabled # Make sure we have 1 client transport enabled
if not client.factory.is_client_enabled(): if not client_factory.is_client_enabled():
LOG.error("No Clients are enabled in config.") LOG.error("No Clients are enabled in config.")
sys.exit(-1) sys.exit(-1)
if not client.factory.is_client_configured(): if not client_factory.is_client_configured():
LOG.error("APRS client is not properly configured in config file.") LOG.error("APRS client is not properly configured in config file.")
sys.exit(-1) sys.exit(-1)

View File

@ -148,7 +148,7 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
watch_list = CONF.watch_list.callsigns watch_list = CONF.watch_list.callsigns
# make sure the timeout is set or this doesn't work # make sure the timeout is set or this doesn't work
if watch_list: if watch_list:
aprs_client = client.factory.create().client aprs_client = client.client_factory.create().client
filter_str = "b/{}".format("/".join(watch_list)) filter_str = "b/{}".format("/".join(watch_list))
aprs_client.set_filter(filter_str) aprs_client.set_filter(filter_str)
else: else:

View File

@ -1,5 +1,5 @@
from aprsd import client as aprs_client
from aprsd import plugin from aprsd import plugin
from aprsd.client import stats as client_stats
from aprsd.packets import packet_list, seen_list, tracker, watch_list from aprsd.packets import packet_list, seen_list, tracker, watch_list
from aprsd.plugins import email from aprsd.plugins import email
from aprsd.stats import app, collector from aprsd.stats import app, collector
@ -16,5 +16,5 @@ stats_collector.register_producer(tracker.PacketTrack)
stats_collector.register_producer(plugin.PluginManager) stats_collector.register_producer(plugin.PluginManager)
stats_collector.register_producer(aprsd.APRSDThreadList) stats_collector.register_producer(aprsd.APRSDThreadList)
stats_collector.register_producer(email.EmailStats) stats_collector.register_producer(email.EmailStats)
stats_collector.register_producer(aprs_client.APRSClientStats) stats_collector.register_producer(client_stats.APRSClientStats)
stats_collector.register_producer(seen_list.SeenList) stats_collector.register_producer(seen_list.SeenList)

View File

@ -5,7 +5,8 @@ import tracemalloc
from oslo_config import cfg from oslo_config import cfg
from aprsd import client, packets, utils from aprsd import packets, utils
from aprsd.client import client_factory
from aprsd.log import log as aprsd_log from aprsd.log import log as aprsd_log
from aprsd.stats import collector from aprsd.stats import collector
from aprsd.threads import APRSDThread, APRSDThreadList from aprsd.threads import APRSDThread, APRSDThreadList
@ -89,7 +90,7 @@ class KeepAliveThread(APRSDThread):
LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}") LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}")
# check the APRS connection # check the APRS connection
cl = client.factory.create() cl = client_factory.create()
# Reset the connection if it's dead and this isn't our # Reset the connection if it's dead and this isn't our
# First time through the loop. # First time through the loop.
# The first time through the loop can happen at startup where # The first time through the loop can happen at startup where
@ -97,7 +98,7 @@ class KeepAliveThread(APRSDThread):
# to make it's connection the first time. # to make it's connection the first time.
if not cl.is_alive() and self.cntr > 0: if not cl.is_alive() and self.cntr > 0:
LOG.error(f"{cl.__class__.__name__} is not alive!!! Resetting") LOG.error(f"{cl.__class__.__name__} is not alive!!! Resetting")
client.factory.create().reset() client_factory.create().reset()
# else: # else:
# # See if we should reset the aprs-is client # # See if we should reset the aprs-is client
# # Due to losing a keepalive from them # # Due to losing a keepalive from them

View File

@ -19,7 +19,6 @@ def send_log_entries(force=False):
if CONF.admin.web_enabled: if CONF.admin.web_enabled:
if force or LogEntries().is_purge_ready(): if force or LogEntries().is_purge_ready():
entries = LogEntries().get_all_and_purge() entries = LogEntries().get_all_and_purge()
print(f"Sending log entries {len(entries)}")
if entries: if entries:
try: try:
requests.post( requests.post(
@ -27,9 +26,8 @@ def send_log_entries(force=False):
json=entries, json=entries,
auth=(CONF.admin.user, CONF.admin.password), auth=(CONF.admin.user, CONF.admin.password),
) )
except Exception as ex: except Exception:
LOG.warning(f"Failed to send log entries {len(entries)}") LOG.warning(f"Failed to send log entries. len={len(entries)}")
LOG.warning(ex)
class LogEntries: class LogEntries:

View File

@ -6,7 +6,8 @@ import time
import aprslib import aprslib
from oslo_config import cfg from oslo_config import cfg
from aprsd import client, packets, plugin from aprsd import packets, plugin
from aprsd.client import client_factory
from aprsd.packets import collector from aprsd.packets import collector
from aprsd.packets import log as packet_log from aprsd.packets import log as packet_log
from aprsd.threads import APRSDThread, tx from aprsd.threads import APRSDThread, tx
@ -20,7 +21,7 @@ class APRSDRXThread(APRSDThread):
def __init__(self, packet_queue): def __init__(self, packet_queue):
super().__init__("RX_PKT") super().__init__("RX_PKT")
self.packet_queue = packet_queue self.packet_queue = packet_queue
self._client = client.factory.create() self._client = client_factory.create()
def stop(self): def stop(self):
self.thread_stop = True self.thread_stop = True
@ -29,7 +30,7 @@ class APRSDRXThread(APRSDThread):
def loop(self): def loop(self):
if not self._client: if not self._client:
self._client = client.factory.create() self._client = client_factory.create()
time.sleep(1) time.sleep(1)
return True return True
# setup the consumer of messages and block until a messages # setup the consumer of messages and block until a messages

View File

@ -9,9 +9,9 @@ from rush.limiters import periodic
from rush.stores import dictionary from rush.stores import dictionary
import wrapt import wrapt
from aprsd import client
from aprsd import conf # noqa from aprsd import conf # noqa
from aprsd import threads as aprsd_threads from aprsd import threads as aprsd_threads
from aprsd.client import client_factory
from aprsd.packets import collector, core from aprsd.packets import collector, core
from aprsd.packets import log as packet_log from aprsd.packets import log as packet_log
from aprsd.packets import tracker from aprsd.packets import tracker
@ -80,7 +80,7 @@ def _send_direct(packet, aprs_client=None):
if aprs_client: if aprs_client:
cl = aprs_client cl = aprs_client
else: else:
cl = client.factory.create() cl = client_factory.create()
packet.update_timestamp() packet.update_timestamp()
packet_log.log(packet, tx=True) packet_log.log(packet, tx=True)
@ -247,7 +247,7 @@ class BeaconSendThread(aprsd_threads.APRSDThread):
send(pkt, direct=True) send(pkt, direct=True)
except Exception as e: except Exception as e:
LOG.error(f"Failed to send beacon: {e}") LOG.error(f"Failed to send beacon: {e}")
client.factory.create().reset() client_factory.create().reset()
time.sleep(5) time.sleep(5)
self._loop_cnt += 1 self._loop_cnt += 1

View File

@ -62,9 +62,9 @@ class TestAPRSDWatchListPluginBase(TestWatchListPlugin):
expected = packets.NULL_MESSAGE expected = packets.NULL_MESSAGE
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@mock.patch("aprsd.client.ClientFactory", autospec=True) @mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
def test_watchlist_not_in_watchlist(self, mock_factory): def test_watchlist_not_in_watchlist(self, mock_factory):
client.factory = mock_factory client.client_factory = mock_factory
self.config_and_init() self.config_and_init()
plugin = fake.FakeWatchListPlugin() plugin = fake.FakeWatchListPlugin()
@ -92,9 +92,9 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
expected = packets.NULL_MESSAGE expected = packets.NULL_MESSAGE
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@mock.patch("aprsd.client.ClientFactory", autospec=True) @mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
def test_callsign_not_in_watchlist(self, mock_factory): def test_callsign_not_in_watchlist(self, mock_factory):
client.factory = mock_factory client.client_factory = mock_factory
self.config_and_init(watchlist_enabled=False) self.config_and_init(watchlist_enabled=False)
plugin = notify_plugin.NotifySeenPlugin() plugin = notify_plugin.NotifySeenPlugin()
@ -106,10 +106,10 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
expected = packets.NULL_MESSAGE expected = packets.NULL_MESSAGE
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@mock.patch("aprsd.client.ClientFactory", autospec=True) @mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
@mock.patch("aprsd.packets.WatchList.is_old") @mock.patch("aprsd.packets.WatchList.is_old")
def test_callsign_in_watchlist_not_old(self, mock_is_old, mock_factory): def test_callsign_in_watchlist_not_old(self, mock_is_old, mock_factory):
client.factory = mock_factory client.client_factory = mock_factory
mock_is_old.return_value = False mock_is_old.return_value = False
self.config_and_init( self.config_and_init(
watchlist_enabled=True, watchlist_enabled=True,
@ -126,10 +126,10 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
expected = packets.NULL_MESSAGE expected = packets.NULL_MESSAGE
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@mock.patch("aprsd.client.ClientFactory", autospec=True) @mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
@mock.patch("aprsd.packets.WatchList.is_old") @mock.patch("aprsd.packets.WatchList.is_old")
def test_callsign_in_watchlist_old_same_alert_callsign(self, mock_is_old, mock_factory): def test_callsign_in_watchlist_old_same_alert_callsign(self, mock_is_old, mock_factory):
client.factory = mock_factory client.client_factory = mock_factory
mock_is_old.return_value = True mock_is_old.return_value = True
self.config_and_init( self.config_and_init(
watchlist_enabled=True, watchlist_enabled=True,
@ -147,10 +147,10 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
expected = packets.NULL_MESSAGE expected = packets.NULL_MESSAGE
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@mock.patch("aprsd.client.ClientFactory", autospec=True) @mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
@mock.patch("aprsd.packets.WatchList.is_old") @mock.patch("aprsd.packets.WatchList.is_old")
def test_callsign_in_watchlist_old_send_alert(self, mock_is_old, mock_factory): def test_callsign_in_watchlist_old_send_alert(self, mock_is_old, mock_factory):
client.factory = mock_factory client.client_factory = mock_factory
mock_is_old.return_value = True mock_is_old.return_value = True
notify_callsign = fake.FAKE_TO_CALLSIGN notify_callsign = fake.FAKE_TO_CALLSIGN
fromcall = "WB4BOR" fromcall = "WB4BOR"