diff --git a/aprsd/client/aprsis.py b/aprsd/client/aprsis.py index e0c5f1d..1cd544d 100644 --- a/aprsd/client/aprsis.py +++ b/aprsd/client/aprsis.py @@ -23,13 +23,24 @@ class APRSISClient(base.APRSClient): max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0} self.max_delta = datetime.timedelta(**max_timeout) - def stats(self) -> dict: + def stats(self, serializable=False) -> dict: stats = {} if self.is_configured(): + if self._client: + keepalive = self._client.aprsd_keepalive + server_string = self._client.server_string + if serializable: + keepalive = keepalive.isoformat() + else: + keepalive = "None" + server_string = "None" stats = { - "server_string": self._client.server_string, - "sever_keepalive": self._client.aprsd_keepalive, + "connected": self.is_connected, "filter": self.filter, + "keepalive": keepalive, + "login_status": self.login_status, + "server_string": server_string, + "transport": self.transport(), } return stats @@ -99,22 +110,31 @@ class APRSISClient(base.APRSClient): self.connected = False backoff = 1 aprs_client = None + retries = 3 + retry_count = 0 while not self.connected: + retry_count += 1 + if retry_count >= retries: + break 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 + self.connected = self.login_status["success"] = True + self.login_status["message"] = aprs_client.server_string backoff = 1 except LoginError as e: LOG.error(f"Failed to login to APRS-IS Server '{e}'") - self.connected = False + self.connected = self.login_status["success"] = False + self.login_status["message"] = e.message + LOG.error(e.message) time.sleep(backoff) except Exception as e: LOG.error(f"Unable to connect to APRS-IS server. '{e}' ") - self.connected = False + self.connected = self.login_status["success"] = False + self.login_status["message"] = e.message time.sleep(backoff) # Don't allow the backoff to go to inifinity. if backoff > 5: @@ -135,5 +155,7 @@ class APRSISClient(base.APRSClient): except Exception as e: LOG.error(e) LOG.info(e.__cause__) + raise e else: LOG.warning("client is None, might be resetting.") + self.connected = False diff --git a/aprsd/client/base.py b/aprsd/client/base.py index 1e87eea..8470546 100644 --- a/aprsd/client/base.py +++ b/aprsd/client/base.py @@ -19,6 +19,10 @@ class APRSClient: _client = None connected = False + login_status = { + "success": False, + "message": None, + } filter = None lock = threading.Lock() @@ -38,6 +42,18 @@ class APRSClient: dict: Statistics about the connection and packet handling """ + @property + def is_connected(self): + return self.connected + + @property + def login_success(self): + return self.login_status.get("success", False) + + @property + def login_failure(self): + return self.login_status["message"] + def set_filter(self, filter): self.filter = filter if self._client: diff --git a/aprsd/client/drivers/aprsis.py b/aprsd/client/drivers/aprsis.py index 405de57..afcbd63 100644 --- a/aprsd/client/drivers/aprsis.py +++ b/aprsd/client/drivers/aprsis.py @@ -27,6 +27,9 @@ class Aprsdis(aprslib.IS): # date for last time we heard from the server aprsd_keepalive = datetime.datetime.now() + # Which server we are connected to? + server_string = "None" + # timeout in seconds select_timeout = 1 lock = threading.Lock() diff --git a/aprsd/client/fake.py b/aprsd/client/fake.py index c8e255b..b17262d 100644 --- a/aprsd/client/fake.py +++ b/aprsd/client/fake.py @@ -14,8 +14,11 @@ LOG = logging.getLogger("APRSD") class APRSDFakeClient(base.APRSClient, metaclass=trace.TraceWrapperMetaclass): - def stats(self) -> dict: - return {} + def stats(self, serializable=False) -> dict: + return { + "transport": "Fake", + "connected": True, + } @staticmethod def is_enabled(): diff --git a/aprsd/client/kiss.py b/aprsd/client/kiss.py index 8318fc1..852d524 100644 --- a/aprsd/client/kiss.py +++ b/aprsd/client/kiss.py @@ -17,12 +17,18 @@ class KISSClient(base.APRSClient): _client = None - def stats(self) -> dict: + def stats(self, serializable=False) -> dict: stats = {} if self.is_configured(): - return { + stats = { + "connected": self.is_connected, "transport": self.transport(), } + if self.transport() == client.TRANSPORT_TCPKISS: + stats["host"] = CONF.kiss_tcp.host + stats["port"] = CONF.kiss_tcp.port + elif self.transport() == client.TRANSPORT_SERIALKISS: + stats["device"] = CONF.kiss_serial.device return stats @staticmethod diff --git a/aprsd/client/stats.py b/aprsd/client/stats.py index 5b0d088..29692ad 100644 --- a/aprsd/client/stats.py +++ b/aprsd/client/stats.py @@ -17,22 +17,4 @@ class APRSClientStats: @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 + return client.client_factory.create().stats(serializable=serializable) diff --git a/aprsd/cmds/listen.py b/aprsd/cmds/listen.py index 47a6deb..f42f16a 100644 --- a/aprsd/cmds/listen.py +++ b/aprsd/cmds/listen.py @@ -256,6 +256,12 @@ def listen( LOG.info("Creating client connection") aprs_client = client_factory.create() LOG.info(aprs_client) + if not aprs_client.login_success: + # We failed to login, will just quit! + msg = f"Login Failure: {aprs_client.login_failure}" + LOG.error(msg) + print(msg) + sys.exit(-1) LOG.debug(f"Filter by '{filter}'") aprs_client.set_filter(filter) diff --git a/aprsd/cmds/server.py b/aprsd/cmds/server.py index 176d592..a355478 100644 --- a/aprsd/cmds/server.py +++ b/aprsd/cmds/server.py @@ -58,6 +58,14 @@ def server(ctx, flush): LOG.info("Creating client connection") aprs_client = client_factory.create() LOG.info(aprs_client) + if not aprs_client.login_success: + # We failed to login, will just quit! + msg = f"Login Failure: {aprs_client.login_failure}" + LOG.error(msg) + print(msg) + sys.exit(-1) + + # Check to make sure the login worked. # Create the initial PM singleton and Register plugins # We register plugins first here so we can register each diff --git a/aprsd/cmds/webchat.py b/aprsd/cmds/webchat.py index 1900b95..8ce38ba 100644 --- a/aprsd/cmds/webchat.py +++ b/aprsd/cmds/webchat.py @@ -24,7 +24,9 @@ from aprsd import utils as aprsd_utils from aprsd.client import client_factory, kiss from aprsd.main import cli from aprsd.threads import aprsd as aprsd_threads -from aprsd.threads import keep_alive, rx, tx +from aprsd.threads import keep_alive, rx +from aprsd.threads import stats as stats_thread +from aprsd.threads import tx from aprsd.utils import trace @@ -614,10 +616,24 @@ def webchat(ctx, flush, port): LOG.error("APRS client is not properly configured in config file.") sys.exit(-1) + # Creates the client object + LOG.info("Creating client connection") + aprs_client = client_factory.create() + LOG.info(aprs_client) + if not aprs_client.login_success: + # We failed to login, will just quit! + msg = f"Login Failure: {aprs_client.login_failure}" + LOG.error(msg) + print(msg) + sys.exit(-1) + keepalive = keep_alive.KeepAliveThread() LOG.info("Start KeepAliveThread") keepalive.start() + stats_store_thread = stats_thread.APRSDStatsStoreThread() + stats_store_thread.start() + socketio = init_flask(loglevel, quiet) rx_thread = rx.APRSDPluginRXThread( packet_queue=threads.packet_queue, diff --git a/aprsd/threads/keep_alive.py b/aprsd/threads/keep_alive.py index b212ac4..309ac8c 100644 --- a/aprsd/threads/keep_alive.py +++ b/aprsd/threads/keep_alive.py @@ -5,6 +5,7 @@ import tracemalloc from loguru import logger from oslo_config import cfg +import timeago from aprsd import packets, utils from aprsd.client import client_factory @@ -98,6 +99,9 @@ class KeepAliveThread(APRSDThread): # check the APRS connection cl = client_factory.create() + cl_stats = cl.stats() + keepalive = timeago.format(cl_stats.get("keepalive", None)) + LOGU.opt(colors=True).info(f"Client keepalive {keepalive}") # Reset the connection if it's dead and this isn't our # First time through the loop. # The first time through the loop can happen at startup where diff --git a/aprsd/threads/rx.py b/aprsd/threads/rx.py index b75f520..f641535 100644 --- a/aprsd/threads/rx.py +++ b/aprsd/threads/rx.py @@ -18,6 +18,8 @@ LOG = logging.getLogger("APRSD") class APRSDRXThread(APRSDThread): + _client = None + def __init__(self, packet_queue): super().__init__("RX_PKT") self.packet_queue = packet_queue @@ -33,6 +35,12 @@ class APRSDRXThread(APRSDThread): self._client = client_factory.create() time.sleep(1) return True + + if not self._client.is_connected: + self._client = client_factory.create() + time.sleep(1) + return True + # setup the consumer of messages and block until a messages try: # This will register a packet consumer with aprslib