import abc import logging import time import aprslib from aprsd import client, messaging, packets, plugin, stats from aprsd.threads import APRSDThread LOG = logging.getLogger("APRSD") class APRSDRXThread(APRSDThread): def __init__(self, msg_queues, config): super().__init__("RX_MSG") self.msg_queues = msg_queues self.config = config self._client = client.factory.create() 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 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 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() # Continue to loop return True @abc.abstractmethod def process_packet(self, *args, **kwargs): pass 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): packet = self._client.decode_packet(*args, **kwargs) # LOG.debug(raw) #packet = packets.Packet.factory(raw.copy()) packet.log(header="RX Packet") thread = APRSDPluginProcessPacketThread( config=self.config, packet=packet, ) thread.start() class APRSDProcessPacketThread(APRSDThread): """Base class for processing received packets. 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): self.config = config self.packet = packet name = self.packet.raw[:10] super().__init__(f"RXPKT-{name}") self._loop_cnt = 1 def process_ack_packet(self, packet): ack_num = packet.msgNo LOG.info(f"Got ack for message {ack_num}") packet.log("RXACK") pkt_tracker = packets.PacketTrack() pkt_tracker.remove(ack_num) stats.APRSDStats().ack_rx_inc() return def loop(self): """Process a packet received from aprs-is server.""" LOG.debug(f"RXPKT-LOOP {self._loop_cnt}") packet = self.packet packets.PacketList().add(packet) our_call = self.config["aprsd"]["callsign"].lower() from_call = packet.from_call if packet.addresse: to_call = packet.addresse else: to_call = packet.to_call msg_id = packet.msgNo # We don't put ack packets destined for us through the # plugins. wl = packets.WatchList() wl.update_seen(packet) if ( isinstance(packet, packets.AckPacket) and packet.addresse.lower() == our_call ): self.process_ack_packet(packet) else: # Only ack messages that were sent directly to us if isinstance(packet, packets.MessagePacket): if to_call and to_call.lower() == our_call: # It's a MessagePacket and it's for us! stats.APRSDStats().msgs_rx_inc() # let any threads do their thing, then ack # send an ack last ack_pkt = packets.AckPacket( from_call=self.config["aprsd"]["callsign"], to_call=from_call, msgNo=msg_id, ) LOG.warning(f"Send AckPacket {ack_pkt}") ack_pkt.send() LOG.warning("Send ACK called Continue on") #ack = messaging.AckMessage( # self.config["aprsd"]["callsign"], # from_call, # msg_id=msg_id, #) #ack.send() self.process_our_message_packet(packet) else: # Packet wasn't meant for us! self.process_other_packet(packet, for_us=False) else: self.process_other_packet( packet, for_us=(to_call.lower() == our_call), ) LOG.debug("Packet processing complete") return False @abc.abstractmethod def process_our_message_packet(self, *args, **kwargs): """Process a MessagePacket destined for us!""" def process_other_packet(self, packet, for_us=False): """Process an APRS Packet that isn't a message or ack""" if not for_us: LOG.info("Got a packet not meant for us.") else: LOG.info("Got a non AckPacket/MessagePacket") LOG.info(packet) class APRSDPluginProcessPacketThread(APRSDProcessPacketThread): """Process the packet through the plugin manager. This is the main aprsd server plugin processing thread.""" def process_our_message_packet(self, packet): """Send the packet through the plugins.""" from_call = packet.from_call if packet.addresse: to_call = packet.addresse else: to_call = None # msg = packet.get("message_text", None) # packet.get("msgNo", "0") # packet.get("response", None) pm = plugin.PluginManager() try: results = pm.run(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_pkt = packets.MessagePacket( from_call=self.config["aprsd"]["callsign"], to_call=from_call, message_text=subreply, ) msg_pkt.send() #msg = messaging.TextMessage( # self.config["aprsd"]["callsign"], # from_call, # subreply, #) #msg.send() elif isinstance(reply, messaging.Message): # We have a message based object. LOG.debug(f"Sending '{reply}'") # Convert this to the new packet msg_pkt = packets.MessagePacket( from_call=reply.fromcall, to_call=reply.tocall, message_text=reply._raw_message, ) #reply.send() msg_pkt.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}'") msg_pkt = packets.MessagePacket( from_call=self.config["aprsd"]["callsign"], to_call=from_call, message_text=reply, ) LOG.warning("Calling msg_pkg.send()") msg_pkt.send() LOG.warning("Calling msg_pkg.send() --- DONE") #msg = messaging.TextMessage( # self.config["aprsd"]["callsign"], # from_call, # reply, #) #msg.send() # If the message was for us and we didn't have a # response, then we send a usage statement. if to_call == self.config["aprsd"]["callsign"] and not replied: LOG.warning("Sending help!") msg_pkt = packets.MessagePacket( from_call=self.config["aprsd"]["callsign"], to_call=from_call, message_text="Unknown command! Send 'help' message for help", ) msg_pkt.send() #msg = messaging.TextMessage( # self.config["aprsd"]["callsign"], # from_call, # "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 to_call == self.config["aprsd"]["callsign"]: reply = "A Plugin failed! try again?" msg_pkt = packets.MessagePacket( from_call=self.config["aprsd"]["callsign"], to_call=from_call, message_text=reply, ) msg_pkt.send() #msg = messaging.TextMessage( # self.config["aprsd"]["callsign"], # from_call, # reply, #) #msg.send() LOG.debug("Completed process_our_message_packet")