def index(self): stats = self._stats() LOG.debug( "watch list? {}".format(self.config["aprsd"]["watch_list"], ), ) wl = packets.WatchList() if wl.is_enabled(): watch_count = len(wl) watch_age = wl.max_delta() else: watch_count = 0 watch_age = 0 sl = packets.SeenList() seen_count = len(sl) pm = plugin.PluginManager() plugins = pm.get_plugins() plugin_count = len(plugins) if self.config["aprs"].get("enabled", True): transport = "aprs-is" aprs_connection = ( "APRS-IS Server: <a href='http://status.aprs2.net' >" "{}</a>".format(stats["stats"]["aprs-is"]["server"])) else: # We might be connected to a KISS socket? if client.KISSClient.kiss_enabled(self.config): transport = client.KISSClient.transport(self.config) if transport == client.TRANSPORT_TCPKISS: aprs_connection = ("TCPKISS://{}:{}".format( self.config["kiss"]["tcp"]["host"], self.config["kiss"]["tcp"]["port"], )) elif transport == client.TRANSPORT_SERIALKISS: aprs_connection = ("SerialKISS://{}@{} baud".format( self.config["kiss"]["serial"]["device"], self.config["kiss"]["serial"]["baudrate"], )) stats["transport"] = transport stats["aprs_connection"] = aprs_connection return flask.render_template( "index.html", initial_stats=stats, aprs_connection=aprs_connection, callsign=self.config["aprs"]["login"], version=aprsd.__version__, config_json=json.dumps(self.config.data), watch_count=watch_count, watch_age=watch_age, seen_count=seen_count, plugin_count=plugin_count, )
def server(loglevel, quiet, disable_validation, config_file): """Start the aprsd server process.""" signal.signal(signal.SIGINT, signal_handler) click.echo("Load config") config = utils.parse_config(config_file) # Force setting the config to the modules that need it # TODO(Walt): convert these modules to classes that can # Accept the config as a constructor param, instead of this # hacky global setting email.CONFIG = config messaging.CONFIG = config setup_logging(config, loglevel, quiet) LOG.info("APRSD Started version: {}".format(aprsd.__version__)) # TODO(walt): Make email processing/checking optional? # Maybe someone only wants this to process messages with plugins only. valid = email.validate_email_config(config, disable_validation) if not valid: LOG.error("Failed to validate email config options") sys.exit(-1) # start the email thread email.start_thread() # Create the initial PM singleton and Register plugins plugin_manager = plugin.PluginManager(config) plugin_manager.setup_plugins() cl = client.Client(config) # setup and run the main blocking loop while True: # Now use the helper which uses the singleton aprs_client = client.get_client() # 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 aprs_client.consumer(process_packet, raw=False) except aprslib.exceptions.ConnectionDrop: 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 cl.reset()
def process_message_packet(packet): LOG.info("Got a message packet") fromcall = packet["from"] message = packet.get("message_text", None) msg_number = packet.get("msgNo", None) if msg_number: ack = msg_number else: ack = "0" messaging.log_message("Received Message", packet["raw"], message, fromcall=fromcall, ack=ack) found_command = False # Get singleton of the PM pm = plugin.PluginManager() try: results = pm.run(fromcall=fromcall, message=message, ack=ack) for reply in results: found_command = 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("Sending '{}'".format(reply)) messaging.send_message(fromcall, reply) else: LOG.debug("Got NULL MESSAGE from plugin") if not found_command: plugins = pm.get_plugins() names = [x.command_name for x in plugins] names.sort() reply = "Usage: {}".format(", ".join(names)) messaging.send_message(fromcall, reply) except Exception as ex: LOG.exception("Plugin failed!!!", ex) reply = "A Plugin failed! try again?" messaging.send_message(fromcall, reply) # let any threads do their thing, then ack # send an ack last messaging.send_ack(fromcall, ack) LOG.debug("Packet processing complete")
def test_plugin( loglevel, config_file, plugin_path, fromcall, message, ): """APRSD Plugin test app.""" config = utils.parse_config(config_file) email.CONFIG = config setup_logging(config, loglevel, False) LOG.info("Test APRSD PLugin version: {}".format(aprsd.__version__)) if type(message) is tuple: message = " ".join(message) LOG.info("P'{}' F'{}' C'{}'".format(plugin_path, fromcall, message)) client.Client(config) pm = plugin.PluginManager(config) obj = pm._create_class(plugin_path, plugin.APRSDPluginBase, config=config) reply = obj.run(fromcall, message, 1) LOG.info("Result = '{}'".format(reply))
def plugins(self): pm = plugin.PluginManager() pm.reload_plugins() return "reloaded"
def loop(self): """Process a packet recieved from aprs-is server.""" packet = self.packet packets.PacketList().add(packet) fromcall = packet["from"] tocall = packet.get("addresse", None) msg = packet.get("message_text", None) msg_id = packet.get("msgNo", "0") msg_response = packet.get("response", None) # LOG.debug(f"Got packet from '{fromcall}' - {packet}") # We don't put ack packets destined for us through the # plugins. if tocall == self.config["aprs"]["login"] and msg_response == "ack": 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, ) # Only ack messages that were sent directly to us if tocall == self.config["aprs"]["login"]: stats.APRSDStats().msgs_rx_inc() # let any threads do their thing, then ack # send an ack last ack = messaging.AckMessage( self.config["aprs"]["login"], fromcall, msg_id=msg_id, ) ack.send() 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( self.config["aprs"]["login"], 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}'") msg = messaging.TextMessage( self.config["aprs"]["login"], 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["aprs"]["login"] and not replied: LOG.warning("Sending help!") msg = messaging.TextMessage( self.config["aprs"]["login"], 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["aprs"]["login"]: reply = "A Plugin failed! try again?" msg = messaging.TextMessage( self.config["aprs"]["login"], fromcall, reply, ) msg.send() LOG.debug("Packet processing complete")
def stats(self): now = datetime.datetime.now() if self._email_thread_last_time: last_update = str(now - self._email_thread_last_time) else: last_update = "never" if self._aprsis_keepalive: last_aprsis_keepalive = str(now - self._aprsis_keepalive) else: last_aprsis_keepalive = "never" pm = plugin.PluginManager() plugins = pm.get_plugins() plugin_stats = {} def full_name_with_qualname(obj): return "{}.{}".format( obj.__class__.__module__, obj.__class__.__qualname__, ) for p in plugins: plugin_stats[full_name_with_qualname(p)] = { "enabled": p.enabled, "rx": p.rx_count, "tx": p.tx_count, "version": p.version, } wl = packets.WatchList() sl = packets.SeenList() stats = { "aprsd": { "version": aprsd.__version__, "uptime": utils.strfdelta(self.uptime), "memory_current": self.memory, "memory_current_str": utils.human_size(self.memory), "memory_peak": self.memory_peak, "memory_peak_str": utils.human_size(self.memory_peak), "watch_list": wl.get_all(), "seen_list": sl.get_all(), }, "aprs-is": { "server": self.aprsis_server, "callsign": self.config["aprs"]["login"], "last_update": last_aprsis_keepalive, }, "messages": { "tracked": self.msgs_tracked, "sent": self.msgs_tx, "recieved": self.msgs_rx, "ack_sent": self.ack_tx, "ack_recieved": self.ack_rx, "mic-e recieved": self.msgs_mice_rx, }, "email": { "enabled": self.config["aprsd"]["email"]["enabled"], "sent": self._email_tx, "recieved": self._email_rx, "thread_last_update": last_update, }, "plugins": plugin_stats, } return stats
def server(ctx, flush): """Start the aprsd server gateway process.""" ctx.obj["config_file"] loglevel = ctx.obj["loglevel"] quiet = ctx.obj["quiet"] config = ctx.obj["config"] signal.signal(signal.SIGINT, aprsd_main.signal_handler) signal.signal(signal.SIGTERM, aprsd_main.signal_handler) if not quiet: click.echo("Load config") level, msg = utils._check_version() if level: LOG.warning(msg) else: LOG.info(msg) LOG.info(f"APRSD Started version: {aprsd.__version__}") flat_config = utils.flatten_dict(config) LOG.info("Using CONFIG values:") for x in flat_config: if "password" in x or "aprsd.web.users.admin" in x: LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX") else: LOG.info(f"{x} = {flat_config[x]}") if config["aprsd"].get("trace", False): trace.setup_tracing(["method", "api"]) stats.APRSDStats(config) # Initialize the client factory and create # The correct client object ready for use client.ClientFactory.setup(config) # Make sure we have 1 client transport enabled if not client.factory.is_client_enabled(): LOG.error("No Clients are enabled in config.") sys.exit(-1) if not client.factory.is_client_configured(): LOG.error("APRS client is not properly configured in config file.") sys.exit(-1) # Creates the client object LOG.info("Creating client connection") client.factory.create().client # Now load the msgTrack from disk if any packets.PacketList(config=config) if flush: LOG.debug("Deleting saved MsgTrack.") messaging.MsgTrack(config=config).flush() packets.WatchList(config=config) packets.SeenList(config=config) else: # Try and load saved MsgTrack list LOG.debug("Loading saved MsgTrack object.") messaging.MsgTrack(config=config).load() packets.WatchList(config=config).load() packets.SeenList(config=config).load() # Create the initial PM singleton and Register plugins LOG.info("Loading Plugin Manager and registering plugins") plugin_manager = plugin.PluginManager(config) plugin_manager.setup_plugins() rx_thread = threads.APRSDRXThread( msg_queues=threads.msg_queues, config=config, ) rx_thread.start() messaging.MsgTrack().restart() keepalive = threads.KeepAliveThread(config=config) keepalive.start() web_enabled = config.get("aprsd.web.enabled", default=False) if web_enabled: aprsd_main.flask_enabled = True (socketio, app) = flask.init_flask(config, loglevel, quiet) socketio.run( app, host=config["aprsd"]["web"]["host"], port=config["aprsd"]["web"]["port"], ) # If there are items in the msgTracker, then save them LOG.info("APRSD Exiting.") return 0
def test_plugin( ctx, aprs_login, plugin_path, load_all, number, message, ): """Test an individual APRSD plugin given a python path.""" config = ctx.obj["config"] if not aprs_login: if not config.exists("aprs.login"): click.echo("Must set --aprs_login or APRS_LOGIN") ctx.exit(-1) return else: fromcall = config.get("aprs.login") else: fromcall = aprs_login if not plugin_path: click.echo(ctx.get_help()) click.echo("") ctx.fail("Failed to provide -p option to test a plugin") return if type(message) is tuple: message = " ".join(message) if config["aprsd"].get("trace", False): trace.setup_tracing(["method", "api"]) client.Client(config) stats.APRSDStats(config) messaging.MsgTrack(config=config) packets.WatchList(config=config) packets.SeenList(config=config) pm = plugin.PluginManager(config) if load_all: pm.setup_plugins() else: pm._init() obj = pm._create_class(plugin_path, plugin.APRSDPluginBase, config=config) if not obj: click.echo(ctx.get_help()) click.echo("") ctx.fail(f"Failed to create object from plugin path '{plugin_path}'") ctx.exit() # Register the plugin they wanted tested. LOG.info( "Testing plugin {} Version {}".format( obj.__class__, obj.version, ), ) pm._pluggy_pm.register(obj) login = config["aprs"]["login"] packet = { "from": fromcall, "addresse": login, "message_text": message, "format": "message", "msgNo": 1, } LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'") for x in range(number): reply = pm.run(packet) # Plugin might have threads, so lets stop them so we can exit. # obj.stop_threads() LOG.info(f"Result{x} = '{reply}'") pm.stop()
def server( loglevel, quiet, disable_validation, config_file, flush, ): """Start the aprsd server process.""" global flask_enabled signal.signal(signal.SIGINT, signal_handler) if not quiet: click.echo("Load config") config = utils.parse_config(config_file) # Force setting the config to the modules that need it # TODO(Walt): convert these modules to classes that can # Accept the config as a constructor param, instead of this # hacky global setting email.CONFIG = config setup_logging(config, loglevel, quiet) if config["aprsd"].get("trace", False): trace.setup_tracing(["method", "api"]) LOG.info("APRSD Started version: {}".format(aprsd.__version__)) stats.APRSDStats(config) email_enabled = config["aprsd"]["email"].get("enabled", False) if email_enabled: # TODO(walt): Make email processing/checking optional? # Maybe someone only wants this to process messages with plugins only. valid = email.validate_email_config(config, disable_validation) if not valid: LOG.error("Failed to validate email config options") sys.exit(-1) else: LOG.info("Email services not enabled.") # Create the initial PM singleton and Register plugins plugin_manager = plugin.PluginManager(config) plugin_manager.setup_plugins() try: cl = client.Client(config) cl.client except LoginError: sys.exit(-1) # Now load the msgTrack from disk if any if flush: LOG.debug("Deleting saved MsgTrack.") messaging.MsgTrack().flush() else: # Try and load saved MsgTrack list LOG.debug("Loading saved MsgTrack object.") messaging.MsgTrack().load() rx_msg_queue = queue.Queue(maxsize=20) tx_msg_queue = queue.Queue(maxsize=20) msg_queues = {"rx": rx_msg_queue, "tx": tx_msg_queue} rx_thread = threads.APRSDRXThread(msg_queues=msg_queues, config=config) tx_thread = threads.APRSDTXThread(msg_queues=msg_queues, config=config) if email_enabled: email_thread = email.APRSDEmailThread(msg_queues=msg_queues, config=config) email_thread.start() rx_thread.start() tx_thread.start() messaging.MsgTrack().restart() keepalive = threads.KeepAliveThread() keepalive.start() try: web_enabled = utils.check_config_option(config, ["aprsd", "web", "enabled"]) except Exception: web_enabled = False if web_enabled: flask_enabled = True app = flask.init_flask(config) app.run( host=config["aprsd"]["web"]["host"], port=config["aprsd"]["web"]["port"], ) # If there are items in the msgTracker, then save them LOG.info("APRSD Exiting.") return 0