2022-07-20 08:43:57 -04:00
|
|
|
import abc
|
2020-12-24 12:39:48 -05:00
|
|
|
import logging
|
|
|
|
import time
|
|
|
|
|
2021-01-08 15:47:30 -05:00
|
|
|
import aprslib
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2022-07-06 19:31:59 -04:00
|
|
|
from aprsd import client, messaging, packets, plugin, stats
|
|
|
|
from aprsd.threads import APRSDThread
|
2021-08-23 12:14:19 -04:00
|
|
|
|
|
|
|
|
2020-12-24 12:39:48 -05:00
|
|
|
LOG = logging.getLogger("APRSD")
|
|
|
|
|
2021-01-21 20:58:47 -05:00
|
|
|
|
2020-12-24 12:39:48 -05:00
|
|
|
class APRSDRXThread(APRSDThread):
|
|
|
|
def __init__(self, msg_queues, config):
|
2021-01-08 15:47:30 -05:00
|
|
|
super().__init__("RX_MSG")
|
2020-12-29 10:31:16 -05:00
|
|
|
self.msg_queues = msg_queues
|
|
|
|
self.config = config
|
2021-09-17 09:32:30 -04:00
|
|
|
self._client = client.factory.create()
|
2020-12-24 12:39:48 -05:00
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
self.thread_stop = True
|
2021-09-17 09:32:30 -04:00
|
|
|
client.factory.create().client.stop()
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2020-12-29 10:31:16 -05:00
|
|
|
def loop(self):
|
|
|
|
|
|
|
|
# setup the consumer of messages and block until a messages
|
|
|
|
try:
|
|
|
|
# This will register a packet consumer with aprslib
|
|
|
|
# When new packets come in the consumer will process
|
|
|
|
# the packet
|
|
|
|
|
|
|
|
# Do a partial here because the consumer signature doesn't allow
|
|
|
|
# For kwargs to be passed in to the consumer func we declare
|
|
|
|
# and the aprslib developer didn't want to allow a PR to add
|
|
|
|
# kwargs. :(
|
|
|
|
# https://github.com/rossengeorgiev/aprs-python/pull/56
|
2021-09-17 09:32:30 -04:00
|
|
|
self._client.client.consumer(
|
|
|
|
self.process_packet, raw=False, blocking=False,
|
|
|
|
)
|
2020-12-29 10:31:16 -05:00
|
|
|
|
2022-07-20 08:43:57 -04:00
|
|
|
except (
|
|
|
|
aprslib.exceptions.ConnectionDrop,
|
|
|
|
aprslib.exceptions.ConnectionError,
|
|
|
|
):
|
2020-12-29 10:31:16 -05:00
|
|
|
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
|
2021-09-17 09:32:30 -04:00
|
|
|
self._client.reset()
|
2020-12-29 10:31:16 -05:00
|
|
|
# Continue to loop
|
|
|
|
return True
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2022-07-20 08:43:57 -04:00
|
|
|
@abc.abstractmethod
|
|
|
|
def process_packet(self, *args, **kwargs):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class APRSDPluginRXThread(APRSDRXThread):
|
2022-12-02 16:26:48 -05:00
|
|
|
"""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.
|
|
|
|
"""
|
2021-09-17 09:32:30 -04:00
|
|
|
def process_packet(self, *args, **kwargs):
|
|
|
|
packet = self._client.decode_packet(*args, **kwargs)
|
2022-12-02 16:26:48 -05:00
|
|
|
thread = APRSDPluginProcessPacketThread(
|
|
|
|
config=self.config,
|
|
|
|
packet=packet,
|
|
|
|
)
|
2021-08-24 15:22:50 -04:00
|
|
|
thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
class APRSDProcessPacketThread(APRSDThread):
|
2022-12-02 16:26:48 -05:00
|
|
|
"""Base class for processing received packets.
|
2021-08-24 15:22:50 -04:00
|
|
|
|
2022-12-02 16:26:48 -05:00
|
|
|
This is the base class for processing packets coming from
|
|
|
|
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):
|
2021-08-24 15:22:50 -04:00
|
|
|
self.config = config
|
2022-12-02 16:26:48 -05:00
|
|
|
self.packet = packet
|
2021-08-24 15:22:50 -04:00
|
|
|
name = self.packet["raw"][:10]
|
2021-12-10 14:20:57 -05:00
|
|
|
super().__init__(f"RXPKT-{name}")
|
2021-08-24 15:22:50 -04:00
|
|
|
|
2020-12-24 12:39:48 -05:00
|
|
|
def process_ack_packet(self, packet):
|
|
|
|
ack_num = packet.get("msgNo")
|
2021-08-23 12:14:19 -04:00
|
|
|
LOG.info(f"Got ack for message {ack_num}")
|
2020-12-24 12:39:48 -05:00
|
|
|
messaging.log_message(
|
2022-01-26 14:29:37 -05:00
|
|
|
"RXACK",
|
2021-01-08 15:47:30 -05:00
|
|
|
packet["raw"],
|
|
|
|
None,
|
|
|
|
ack=ack_num,
|
|
|
|
fromcall=packet["from"],
|
2020-12-24 12:39:48 -05:00
|
|
|
)
|
2020-12-29 10:31:16 -05:00
|
|
|
tracker = messaging.MsgTrack()
|
|
|
|
tracker.remove(ack_num)
|
2021-01-21 20:58:47 -05:00
|
|
|
stats.APRSDStats().ack_rx_inc()
|
2020-12-24 12:39:48 -05:00
|
|
|
return
|
|
|
|
|
2021-08-24 15:22:50 -04:00
|
|
|
def loop(self):
|
2022-11-30 14:07:16 -05:00
|
|
|
"""Process a packet received from aprs-is server."""
|
2021-08-24 15:22:50 -04:00
|
|
|
packet = self.packet
|
2021-08-19 11:39:29 -04:00
|
|
|
packets.PacketList().add(packet)
|
2020-12-24 12:39:48 -05:00
|
|
|
|
|
|
|
fromcall = packet["from"]
|
2021-08-19 11:39:29 -04:00
|
|
|
tocall = packet.get("addresse", None)
|
|
|
|
msg = packet.get("message_text", None)
|
2020-12-29 10:31:16 -05:00
|
|
|
msg_id = packet.get("msgNo", "0")
|
2021-08-19 11:39:29 -04:00
|
|
|
msg_response = packet.get("response", None)
|
2021-08-24 13:31:33 -04:00
|
|
|
# LOG.debug(f"Got packet from '{fromcall}' - {packet}")
|
2021-08-19 11:39:29 -04:00
|
|
|
|
|
|
|
# We don't put ack packets destined for us through the
|
|
|
|
# plugins.
|
2022-11-30 14:07:16 -05:00
|
|
|
if (
|
2022-12-02 14:43:57 -05:00
|
|
|
tocall
|
|
|
|
and tocall.lower() == self.config["aprsd"]["callsign"].lower()
|
2022-11-30 14:07:16 -05:00
|
|
|
and msg_response == "ack"
|
|
|
|
):
|
2021-08-19 11:39:29 -04:00
|
|
|
self.process_ack_packet(packet)
|
|
|
|
else:
|
|
|
|
# It's not an ACK for us, so lets run it through
|
|
|
|
# the plugins.
|
|
|
|
messaging.log_message(
|
|
|
|
"Received Message",
|
|
|
|
packet["raw"],
|
|
|
|
msg,
|
|
|
|
fromcall=fromcall,
|
|
|
|
msg_num=msg_id,
|
|
|
|
)
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2021-08-19 11:39:29 -04:00
|
|
|
# Only ack messages that were sent directly to us
|
2022-12-02 16:26:48 -05:00
|
|
|
if (
|
|
|
|
tocall
|
|
|
|
and tocall.lower() == self.config["aprsd"]["callsign"].lower()
|
|
|
|
):
|
2021-08-24 13:31:33 -04:00
|
|
|
stats.APRSDStats().msgs_rx_inc()
|
2021-08-19 11:39:29 -04:00
|
|
|
# let any threads do their thing, then ack
|
|
|
|
# send an ack last
|
|
|
|
ack = messaging.AckMessage(
|
2022-07-28 16:24:25 -04:00
|
|
|
self.config["aprsd"]["callsign"],
|
2021-01-08 15:47:30 -05:00
|
|
|
fromcall,
|
2021-08-19 11:39:29 -04:00
|
|
|
msg_id=msg_id,
|
2020-12-24 12:39:48 -05:00
|
|
|
)
|
2021-08-24 15:22:50 -04:00
|
|
|
ack.send()
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2022-12-02 16:26:48 -05:00
|
|
|
self.process_non_ack_packet(packet)
|
|
|
|
else:
|
|
|
|
LOG.info("Packet was not for us.")
|
|
|
|
LOG.debug("Packet processing complete")
|
|
|
|
|
|
|
|
@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."""
|
2021-08-19 11:39:29 -04:00
|
|
|
|
2022-12-02 16:26:48 -05:00
|
|
|
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:
|
2021-08-19 11:39:29 -04:00
|
|
|
msg = messaging.TextMessage(
|
2022-07-28 16:24:25 -04:00
|
|
|
self.config["aprsd"]["callsign"],
|
2021-08-19 11:39:29 -04:00
|
|
|
fromcall,
|
2022-12-02 16:26:48 -05:00
|
|
|
subreply,
|
2021-08-19 11:39:29 -04:00
|
|
|
)
|
2021-08-24 15:22:50 -04:00
|
|
|
msg.send()
|
2022-12-02 16:26:48 -05:00
|
|
|
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}'")
|
2021-08-19 11:39:29 -04:00
|
|
|
|
2022-12-02 16:26:48 -05:00
|
|
|
msg = messaging.TextMessage(
|
|
|
|
self.config["aprsd"]["callsign"],
|
|
|
|
fromcall,
|
|
|
|
reply,
|
|
|
|
)
|
|
|
|
msg.send()
|
|
|
|
|
|
|
|
# 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()
|