1
0
mirror of https://github.com/craigerl/aprsd.git synced 2025-05-29 04:32:29 -04:00

Refactored threads a bit

This patch refactors the rx threads a bit to reuse some code
responsible for processing acks when packets are received.

This also eliminates a custom thread in the webchat command for
processing received packets now that there is common code in the base
classes.
This commit is contained in:
Hemna 2022-12-02 16:26:48 -05:00
parent 480094b0d4
commit 51b80cd4ea
2 changed files with 145 additions and 221 deletions

View File

@ -2,13 +2,11 @@ import datetime
import json import json
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import queue
import signal import signal
import sys import sys
import threading import threading
import time import time
import aprslib
from aprslib import util as aprslib_util from aprslib import util as aprslib_util
import click import click
from device_detector import DeviceDetector from device_detector import DeviceDetector
@ -27,7 +25,6 @@ from aprsd import config as aprsd_config
from aprsd import messaging, packets, stats, threads, utils from aprsd import messaging, packets, stats, threads, utils
from aprsd.aprsd import cli from aprsd.aprsd import cli
from aprsd.logging import rich as aprsd_logging from aprsd.logging import rich as aprsd_logging
from aprsd.threads import aprsd as aprsd_thread
from aprsd.threads import rx from aprsd.threads import rx
from aprsd.utils import objectstore, trace from aprsd.utils import objectstore, trace
@ -35,14 +32,6 @@ from aprsd.utils import objectstore, trace
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
auth = HTTPBasicAuth() auth = HTTPBasicAuth()
users = None users = None
rx_msg_queue = queue.Queue(maxsize=20)
tx_msg_queue = queue.Queue(maxsize=20)
control_queue = queue.Queue(maxsize=20)
msg_queues = {
"rx": rx_msg_queue,
"control": control_queue,
"tx": tx_msg_queue,
}
def signal_handler(sig, frame): def signal_handler(sig, frame):
@ -143,62 +132,19 @@ def verify_password(username, password):
class WebChatRXThread(rx.APRSDRXThread): class WebChatRXThread(rx.APRSDRXThread):
"""Class that connects to aprsis/kiss and waits for messages.""" """Class that connects to APRISIS/kiss and waits for messages.
After the packet is received from APRSIS/KISS, the packet is
sent to processing in the WebChatProcessPacketThread.
"""
def __init__(self, config, socketio):
super().__init__(None, config)
self.socketio = socketio
self.connected = False
def connected(self, connected=True): def connected(self, connected=True):
self.connected = connected self.connected = connected
def stop(self):
self.thread_stop = True
client.factory.create().client.stop()
def loop(self):
# setup the consumer of messages and block until a messages
msg = None
try:
msg = self.msg_queues["tx"].get_nowait()
except queue.Empty:
pass
try:
if msg:
LOG.debug("GOT msg from TX queue!!")
msg.send()
except (
aprslib.exceptions.ConnectionDrop,
aprslib.exceptions.ConnectionError,
):
LOG.error("Connection dropped, reconnecting")
# Put it back on the queue to send.
self.msg_queues["tx"].put(msg)
# Force the deletion of the client object connected to aprs
# This will cause a reconnect, next time client.get_client()
# is called
self._client.reset()
time.sleep(2)
try:
# When new packets come in the consumer will process
# the packet
# This call blocks until thread stop() is called.
self._client.client.consumer(
self.process_packet, raw=False, blocking=False,
)
except (
aprslib.exceptions.ConnectionDrop,
aprslib.exceptions.ConnectionError,
):
LOG.error("Connection dropped, reconnecting")
time.sleep(5)
# Force the deletion of the client object connected to aprs
# This will cause a reconnect, next time client.get_client()
# is called
self._client.reset()
return True
return True
def process_packet(self, *args, **kwargs): def process_packet(self, *args, **kwargs):
# packet = self._client.decode_packet(*args, **kwargs) # packet = self._client.decode_packet(*args, **kwargs)
if "packet" in kwargs: if "packet" in kwargs:
@ -207,101 +153,55 @@ class WebChatRXThread(rx.APRSDRXThread):
packet = self._client.decode_packet(*args, **kwargs) packet = self._client.decode_packet(*args, **kwargs)
LOG.debug(f"GOT Packet {packet}") LOG.debug(f"GOT Packet {packet}")
self.msg_queues["rx"].put(packet) thread = WebChatProcessPacketThread(
config=self.config,
packet=packet,
socketio=self.socketio,
)
thread.start()
class WebChatTXThread(aprsd_thread.APRSDThread): class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
"""Class that """ """Class that handles packets being sent to us."""
def __init__(self, msg_queues, config, socketio): def __init__(self, config, packet, socketio):
super().__init__("_TXThread_")
self.msg_queues = msg_queues
self.config = config
self.socketio = socketio self.socketio = socketio
self.connected = False self.connected = False
super().__init__(config, packet)
def loop(self):
try:
msg = self.msg_queues["control"].get_nowait()
self.connected = msg["connected"]
except queue.Empty:
pass
try:
packet = self.msg_queues["rx"].get_nowait()
if packet:
# we got a packet and we need to send it to the
# web socket
self.process_packet(packet)
except queue.Empty:
pass
except Exception as ex:
LOG.exception(ex)
time.sleep(1)
return True
def process_ack_packet(self, packet): def process_ack_packet(self, packet):
super().process_ack_packet(packet)
ack_num = packet.get("msgNo") ack_num = packet.get("msgNo")
LOG.info(f"We got ack for our sent message {ack_num}")
messaging.log_packet(packet)
SentMessages().ack(int(ack_num)) SentMessages().ack(int(ack_num))
tracker = messaging.MsgTrack()
tracker.remove(ack_num)
self.socketio.emit( self.socketio.emit(
"ack", SentMessages().get(int(ack_num)), "ack", SentMessages().get(int(ack_num)),
namespace="/sendmsg", namespace="/sendmsg",
) )
stats.APRSDStats().ack_rx_inc()
self.got_ack = True self.got_ack = True
def process_packet(self, packet): def process_non_ack_packet(self, packet):
LOG.info(f"process PACKET {packet}") LOG.info(f"process non ack PACKET {packet}")
tocall = packet.get("addresse", None) packet.get("addresse", None)
fromcall = packet["from"] fromcall = packet["from"]
msg = packet.get("message_text", None)
msg_id = packet.get("msgNo", "0")
msg_response = packet.get("response", None)
if ( packets.PacketList().add(packet)
tocall.lower() == self.config["aprsd"]["callsign"].lower() stats.APRSDStats().msgs_rx_inc()
and msg_response == "ack" message = packet.get("message_text", None)
): msg = {
self.process_ack_packet(packet) "id": 0,
elif tocall.lower() == self.config["aprsd"]["callsign"].lower(): "ts": time.time(),
messaging.log_message( "ack": False,
"Received Message", "from": fromcall,
packet["raw"], "to": packet["to"],
msg, "raw": packet["raw"],
fromcall=fromcall, "message": message,
msg_num=msg_id, "status": None,
) "last_update": None,
# let any threads do their thing, then ack "reply": None,
# send an ack last }
ack = messaging.AckMessage( self.socketio.emit(
self.config["aprsd"]["callsign"], "new", msg,
fromcall, namespace="/sendmsg",
msg_id=msg_id, )
)
ack.send()
packets.PacketList().add(packet)
stats.APRSDStats().msgs_rx_inc()
message = packet.get("message_text", None)
msg = {
"id": 0,
"ts": time.time(),
"ack": False,
"from": fromcall,
"to": packet["to"],
"raw": packet["raw"],
"message": message,
"status": None,
"last_update": None,
"reply": None,
}
self.socketio.emit(
"new", msg,
namespace="/sendmsg",
)
class WebChatFlask(flask_classful.FlaskView): class WebChatFlask(flask_classful.FlaskView):
@ -418,9 +318,8 @@ class SendMessageNamespace(Namespace):
msg = None msg = None
request = None request = None
def __init__(self, namespace=None, config=None, msg_queues=None): def __init__(self, namespace=None, config=None):
self._config = config self._config = config
self._msg_queues = msg_queues
super().__init__(namespace) super().__init__(namespace)
def on_connect(self): def on_connect(self):
@ -430,13 +329,9 @@ class SendMessageNamespace(Namespace):
"connected", {"data": "/sendmsg Connected"}, "connected", {"data": "/sendmsg Connected"},
namespace="/sendmsg", namespace="/sendmsg",
) )
msg = {"connected": True}
self._msg_queues["control"].put(msg)
def on_disconnect(self): def on_disconnect(self):
LOG.debug("WS Disconnected") LOG.debug("WS Disconnected")
msg = {"connected": False}
self._msg_queues["control"].put(msg)
def on_send(self, data): def on_send(self, data):
global socketio global socketio
@ -458,7 +353,6 @@ class SendMessageNamespace(Namespace):
namespace="/sendmsg", namespace="/sendmsg",
) )
msg.send() msg.send()
# self._msg_queues["tx"].put(msg)
def on_gps(self, data): def on_gps(self, data):
LOG.debug(f"WS on_GPS: {data}") LOG.debug(f"WS on_GPS: {data}")
@ -567,7 +461,6 @@ def init_flask(config, loglevel, quiet):
socketio.on_namespace( socketio.on_namespace(
SendMessageNamespace( SendMessageNamespace(
"/sendmsg", config=config, "/sendmsg", config=config,
msg_queues=msg_queues,
), ),
) )
return socketio, flask_app return socketio, flask_app
@ -644,18 +537,11 @@ def webchat(ctx, flush, port):
(socketio, app) = init_flask(config, loglevel, quiet) (socketio, app) = init_flask(config, loglevel, quiet)
rx_thread = WebChatRXThread( rx_thread = WebChatRXThread(
msg_queues=msg_queues,
config=config,
)
LOG.info("Start RX Thread")
rx_thread.start()
tx_thread = WebChatTXThread(
msg_queues=msg_queues,
config=config, config=config,
socketio=socketio, socketio=socketio,
) )
LOG.info("Start TX Thread") LOG.info("Start RX Thread")
tx_thread.start() rx_thread.start()
keepalive = threads.KeepAliveThread(config=config) keepalive = threads.KeepAliveThread(config=config)
LOG.info("Start KeepAliveThread") LOG.info("Start KeepAliveThread")

View File

@ -58,17 +58,32 @@ class APRSDRXThread(APRSDThread):
class APRSDPluginRXThread(APRSDRXThread): class APRSDPluginRXThread(APRSDRXThread):
"""Process received packets.
This is the main APRSD Server command thread that
receives packets from APRIS and then sends them for
processing in the PluginProcessPacketThread.
"""
def process_packet(self, *args, **kwargs): def process_packet(self, *args, **kwargs):
packet = self._client.decode_packet(*args, **kwargs) packet = self._client.decode_packet(*args, **kwargs)
thread = APRSDProcessPacketThread(packet=packet, config=self.config) thread = APRSDPluginProcessPacketThread(
config=self.config,
packet=packet,
)
thread.start() thread.start()
class APRSDProcessPacketThread(APRSDThread): class APRSDProcessPacketThread(APRSDThread):
"""Base class for processing received packets.
def __init__(self, packet, config): This is the base class for processing packets coming from
self.packet = packet the consumer. This base class handles sending ack packets and
will ack a message before sending the packet to the subclass
for processing."""
def __init__(self, config, packet):
self.config = config self.config = config
self.packet = packet
name = self.packet["raw"][:10] name = self.packet["raw"][:10]
super().__init__(f"RXPKT-{name}") super().__init__(f"RXPKT-{name}")
@ -119,7 +134,10 @@ class APRSDProcessPacketThread(APRSDThread):
) )
# Only ack messages that were sent directly to us # Only ack messages that were sent directly to us
if (tocall.lower() == self.config["aprsd"]["callsign"].lower()): if (
tocall
and tocall.lower() == self.config["aprsd"]["callsign"].lower()
):
stats.APRSDStats().msgs_rx_inc() stats.APRSDStats().msgs_rx_inc()
# let any threads do their thing, then ack # let any threads do their thing, then ack
# send an ack last # send an ack last
@ -130,69 +148,89 @@ class APRSDProcessPacketThread(APRSDThread):
) )
ack.send() ack.send()
pm = plugin.PluginManager() self.process_non_ack_packet(packet)
try: else:
results = pm.run(packet) LOG.info("Packet was not for us.")
wl = packets.WatchList() LOG.debug("Packet processing complete")
wl.update_seen(packet)
replied = False
for reply in results:
if isinstance(reply, list):
# one of the plugins wants to send multiple messages
replied = True
for subreply in reply:
LOG.debug(f"Sending '{subreply}'")
if isinstance(subreply, messaging.Message):
subreply.send()
else:
msg = messaging.TextMessage(
self.config["aprsd"]["callsign"],
fromcall,
subreply,
)
msg.send()
elif isinstance(reply, messaging.Message):
# We have a message based object.
LOG.debug(f"Sending '{reply}'")
reply.send()
replied = True
else:
replied = True
# A plugin can return a null message flag which signals
# us that they processed the message correctly, but have
# nothing to reply with, so we avoid replying with a
# usage string
if reply is not messaging.NULL_MESSAGE:
LOG.debug(f"Sending '{reply}'")
@abc.abstractmethod
def process_non_ack_packet(self, *args, **kwargs):
"""Ack packets already dealt with here."""
class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
"""Process the packet through the plugin manager.
This is the main aprsd server plugin processing thread."""
def process_non_ack_packet(self, packet):
"""Send the packet through the plugins."""
fromcall = packet["from"]
tocall = packet.get("addresse", None)
msg = packet.get("message_text", None)
packet.get("msgNo", "0")
packet.get("response", None)
pm = plugin.PluginManager()
try:
results = pm.run(packet)
wl = packets.WatchList()
wl.update_seen(packet)
replied = False
for reply in results:
if isinstance(reply, list):
# one of the plugins wants to send multiple messages
replied = True
for subreply in reply:
LOG.debug(f"Sending '{subreply}'")
if isinstance(subreply, messaging.Message):
subreply.send()
else:
msg = messaging.TextMessage( msg = messaging.TextMessage(
self.config["aprsd"]["callsign"], self.config["aprsd"]["callsign"],
fromcall, fromcall,
reply, subreply,
) )
msg.send() msg.send()
elif isinstance(reply, messaging.Message):
# We have a message based object.
LOG.debug(f"Sending '{reply}'")
reply.send()
replied = True
else:
replied = True
# A plugin can return a null message flag which signals
# us that they processed the message correctly, but have
# nothing to reply with, so we avoid replying with a
# usage string
if reply is not messaging.NULL_MESSAGE:
LOG.debug(f"Sending '{reply}'")
# If the message was for us and we didn't have a msg = messaging.TextMessage(
# response, then we send a usage statement. self.config["aprsd"]["callsign"],
if tocall == self.config["aprsd"]["callsign"] and not replied: fromcall,
LOG.warning("Sending help!") reply,
msg = messaging.TextMessage( )
self.config["aprsd"]["callsign"], msg.send()
fromcall,
"Unknown command! Send 'help' message for help",
)
msg.send()
except Exception as ex:
LOG.error("Plugin failed!!!")
LOG.exception(ex)
# Do we need to send a reply?
if tocall == self.config["aprsd"]["callsign"]:
reply = "A Plugin failed! try again?"
msg = messaging.TextMessage(
self.config["aprsd"]["callsign"],
fromcall,
reply,
)
msg.send()
LOG.debug("Packet processing complete") # If the message was for us and we didn't have a
# response, then we send a usage statement.
if tocall == self.config["aprsd"]["callsign"] and not replied:
LOG.warning("Sending help!")
msg = messaging.TextMessage(
self.config["aprsd"]["callsign"],
fromcall,
"Unknown command! Send 'help' message for help",
)
msg.send()
except Exception as ex:
LOG.error("Plugin failed!!!")
LOG.exception(ex)
# Do we need to send a reply?
if tocall == self.config["aprsd"]["callsign"]:
reply = "A Plugin failed! try again?"
msg = messaging.TextMessage(
self.config["aprsd"]["callsign"],
fromcall,
reply,
)
msg.send()