def rx_packet(self, packet): global socketio # LOG.debug("Got packet back {}".format(packet)) resp = packet.get("response", None) if resp == "ack": ack_num = packet.get("msgNo") LOG.info(f"We got ack for our sent message {ack_num}") messaging.log_packet(packet) SentMessages().ack(self.msg.id) socketio.emit( "ack", SentMessages().get(self.msg.id), namespace="/sendmsg", ) stats.APRSDStats().ack_rx_inc() self.got_ack = True if self.request["wait_reply"] == "0" or self.got_reply: # We aren't waiting for a reply, so we can bail self.stop() self.thread_stop = self.aprs_client.thread_stop = True else: packets.PacketList().add(packet) stats.APRSDStats().msgs_rx_inc() message = packet.get("message_text", None) fromcall = packet["from"] msg_number = packet.get("msgNo", "0") messaging.log_message( "Received Message", packet["raw"], message, fromcall=fromcall, ack=msg_number, ) SentMessages().reply(self.msg.id, packet) SentMessages().set_status(self.msg.id, "Got Reply") socketio.emit( "reply", SentMessages().get(self.msg.id), namespace="/sendmsg", ) # Send the ack back? ack = messaging.AckMessage( self.request["from"], fromcall, msg_id=msg_number, ) ack.send_direct() SentMessages().set_status(self.msg.id, "Ack Sent") # Now we can exit, since we are done. self.got_reply = True if self.got_ack: self.stop() self.thread_stop = self.aprs_client.thread_stop = True
def signal_handler(sig, frame): threads.APRSDThreadList().stop_all() if "subprocess" not in str(frame): LOG.info( "Ctrl+C, Sending all threads exit! Can take up to 10 seconds {}". format(datetime.datetime.now(), ), ) time.sleep(5) LOG.info(stats.APRSDStats())
def loop(self): """Loop until a message is acked or it gets delayed. We only sleep for 5 seconds between each loop run, so that CTRL-C can exit the app in a short period. Each sleep means the app quitting is blocked until sleep is done. So we keep track of the last send attempt and only send if the last send attempt is old enough. """ cl = client.get_client() tracker = MsgTrack() # lets see if the message is still in the tracking queue msg = tracker.get(self.msg.id) if not msg: # The message has been removed from the tracking queue # So it got acked and we are done. LOG.info("Message Send Complete via Ack.") return False else: send_now = False if msg.last_send_attempt == msg.retry_count: # we reached the send limit, don't send again # TODO(hemna) - Need to put this in a delayed queue? LOG.info("Message Send Complete. Max attempts reached.") return False # Message is still outstanding and needs to be acked. if msg.last_send_time: # Message has a last send time tracking now = datetime.datetime.now() sleeptime = (msg.last_send_attempt + 1) * 31 delta = now - msg.last_send_time if delta > datetime.timedelta(seconds=sleeptime): # It's time to try to send it again send_now = True else: send_now = True if send_now: # no attempt time, so lets send it, and start # tracking the time. log_message( "Sending Message", str(msg).rstrip("\n"), msg.message, tocall=self.msg.tocall, retry_number=msg.last_send_attempt, msg_num=msg.id, ) cl.sendall(str(msg)) stats.APRSDStats().msgs_tx_inc() msg.last_send_time = datetime.datetime.now() msg.last_send_attempt += 1 time.sleep(5) # Make sure we get called again. return True
def _send_login(self): """ Sends login string to server """ login_str = "user {0} pass {1} vers github.com/craigerl/aprsd {3}{2}\r\n" login_str = login_str.format( self.callsign, self.passwd, (" filter " + self.filter) if self.filter != "" else "", aprsd.__version__, ) self.logger.info("Sending login information") try: self._sendall(login_str) self.sock.settimeout(5) test = self.sock.recv(len(login_str) + 100) if is_py3: test = test.decode("latin-1") test = test.rstrip() self.logger.debug("Server: %s", test) a, b, callsign, status, e = test.split(" ", 4) s = e.split(",") if len(s): server_string = s[0].replace("server ", "") else: server_string = e.replace("server ", "") self.logger.info(f"Connected to {server_string}") self.server_string = server_string stats.APRSDStats().set_aprsis_server(server_string) if callsign == "": raise LoginError("Server responded with empty callsign???") if callsign != self.callsign: raise LoginError(f"Server: {test}") if status != "verified," and self.passwd != "-1": raise LoginError("Password is incorrect") if self.passwd == "-1": self.logger.info("Login successful (receive only)") else: self.logger.info("Login successful") except LoginError as e: self.logger.error(str(e)) self.close() raise except Exception as e: self.close() self.logger.error(f"Failed to login '{e}'") raise LoginError("Failed to login")
def stats(self): stats_obj = stats.APRSDStats() track = messaging.MsgTrack() result = { "version": aprsd.__version__, "uptime": stats_obj.uptime, "size_tracker": len(track), "stats": stats_obj.stats(), } return json.dumps(result)
def process(self, packet): LOG.info("Version COMMAND") # fromcall = packet.get("from") # message = packet.get("message_text", None) # ack = packet.get("msgNo", "0") stats_obj = stats.APRSDStats() s = stats_obj.stats() return "APRSD ver:{} uptime:{}".format( aprsd.__version__, s["aprsd"]["uptime"], )
def send_direct(self): """Send a message without a separate thread.""" cl = client.get_client() log_message( "Sending Message Direct", str(self).rstrip("\n"), self.message, tocall=self.tocall, fromcall=self.fromcall, ) cl.sendall(str(self)) stats.APRSDStats().msgs_tx_inc()
def send_direct(self, aprsis_client=None): """Send a message without a separate thread.""" cl = client.factory.create().client log_message( "Sending Message Direct", str(self), self.message, tocall=self.tocall, fromcall=self.fromcall, ) cl.send(self) stats.APRSDStats().msgs_tx_inc()
def process_ack_packet(self, packet): ack_num = packet.get("msgNo") LOG.info(f"Got ack for message {ack_num}") messaging.log_message( "RXACK", packet["raw"], None, ack=ack_num, fromcall=packet["from"], ) tracker = messaging.MsgTrack() tracker.remove(ack_num) stats.APRSDStats().ack_rx_inc() return
def config_and_init(self, config=None): if not config: self.config = aprsd_config.Config(aprsd_config.DEFAULT_CONFIG_DICT) self.config["ham"]["callsign"] = self.fromcall self.config["aprs"]["login"] = fake.FAKE_TO_CALLSIGN self.config["services"]["aprs.fi"]["apiKey"] = "something" else: self.config = config # Inintialize the stats object with the config stats.APRSDStats(self.config) packets.WatchList(config=self.config) packets.SeenList(config=self.config) messaging.MsgTrack(config=self.config)
def signal_handler(sig, frame): global flask_enabled threads.APRSDThreadList().stop_all() if "subprocess" not in str(frame): LOG.info( "Ctrl+C, Sending all threads exit! Can take up to 10 seconds {}". format(datetime.datetime.now(), ), ) time.sleep(5) tracker = messaging.MsgTrack() tracker.save() LOG.info(stats.APRSDStats()) # signal.signal(signal.SIGTERM, sys.exit(0)) # sys.exit(0) if flask_enabled: signal.signal(signal.SIGTERM, sys.exit(0))
def send_thread(self): """Separate thread to send acks with retries.""" cl = client.get_client() for i in range(self.retry_count, 0, -1): log_message( "Sending ack", str(self).rstrip("\n"), None, ack=self.id, tocall=self.tocall, retry_number=i, ) cl.sendall(str(self)) stats.APRSDStats().ack_tx_inc() # aprs duplicate detection is 30 secs? # (21 only sends first, 28 skips middle) time.sleep(31)
def loop(self): """Separate thread to send acks with retries.""" send_now = False if self.ack.last_send_attempt == self.ack.retry_count: # we reached the send limit, don't send again # TODO(hemna) - Need to put this in a delayed queue? LOG.info("Ack Send Complete. Max attempts reached.") return False if self.ack.last_send_time: # Message has a last send time tracking now = datetime.datetime.now() # aprs duplicate detection is 30 secs? # (21 only sends first, 28 skips middle) sleeptime = 31 delta = now - self.ack.last_send_time if delta > datetime.timedelta(seconds=sleeptime): # It's time to try to send it again send_now = True else: LOG.debug(f"Still wating. {delta}") else: send_now = True if send_now: cl = client.factory.create().client log_message( "Sending ack", str(self.ack).rstrip("\n"), None, ack=self.ack.id, tocall=self.ack.tocall, retry_number=self.ack.last_send_attempt, ) cl.send(self.ack) stats.APRSDStats().ack_tx_inc() packets.PacketList().add(self.ack.dict()) self.ack.last_send_attempt += 1 self.ack.last_send_time = datetime.datetime.now() time.sleep(5) return True
def send_email(to_addr, content): global check_email_delay shortcuts = CONFIG["aprsd"]["email"]["shortcuts"] email_address = get_email_from_shortcut(to_addr) LOG.info("Sending Email_________________") if to_addr in shortcuts: LOG.info("To : " + to_addr) to_addr = email_address LOG.info(" (" + to_addr + ")") subject = CONFIG["ham"]["callsign"] # content = content + "\n\n(NOTE: reply with one line)" LOG.info("Subject : " + subject) LOG.info("Body : " + content) # check email more often since there's activity right now check_email_delay = 60 msg = MIMEText(content) msg["Subject"] = subject msg["From"] = CONFIG["aprsd"]["email"]["smtp"]["login"] msg["To"] = to_addr server = _smtp_connect() if server: try: server.sendmail( CONFIG["aprsd"]["email"]["smtp"]["login"], [to_addr], msg.as_string(), ) stats.APRSDStats().email_tx_inc() except Exception as e: msg = getattr(e, "message", repr(e)) LOG.error("Sendmail Error!!!! '{}'", msg) server.quit() return -1 server.quit() return 0
def _stats(self): stats_obj = stats.APRSDStats() track = messaging.MsgTrack() now = datetime.datetime.now() time_format = "%m-%d-%Y %H:%M:%S" stats_dict = stats_obj.stats() # Convert the watch_list entries to age wl = packets.WatchList() new_list = {} for call in wl.get_all(): # call_date = datetime.datetime.strptime( # str(wl.last_seen(call)), # "%Y-%m-%d %H:%M:%S.%f", # ) new_list[call] = { "last": wl.age(call), "packets": wl.get(call)["packets"].get(), } stats_dict["aprsd"]["watch_list"] = new_list packet_list = packets.PacketList() rx = packet_list.total_received() tx = packet_list.total_sent() stats_dict["packets"] = { "sent": tx, "received": rx, } result = { "time": now.strftime(time_format), "size_tracker": len(track), "stats": stats_dict, } return result
def send_email(config, to_addr, content): shortcuts = config["aprsd"]["email"]["shortcuts"] email_address = get_email_from_shortcut(config, to_addr) LOG.info("Sending Email_________________") if to_addr in shortcuts: LOG.info(f"To : {to_addr}") to_addr = email_address LOG.info(f" ({to_addr})") subject = config["ham"]["callsign"] # content = content + "\n\n(NOTE: reply with one line)" LOG.info(f"Subject : {subject}") LOG.info(f"Body : {content}") # check email more often since there's activity right now EmailInfo().delay = 60 msg = MIMEText(content) msg["Subject"] = subject msg["From"] = config["aprsd"]["email"]["smtp"]["login"] msg["To"] = to_addr server = _smtp_connect(config) if server: try: server.sendmail( config["aprsd"]["email"]["smtp"]["login"], [to_addr], msg.as_string(), ) stats.APRSDStats().email_tx_inc() except Exception: LOG.exception("Sendmail Error!!!!") server.quit() return -1 server.quit() return 0
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
def add(self, msg): with self.lock: key = int(msg.id) self.track[key] = msg stats.APRSDStats().msgs_tracked_inc() self.total_messages_tracked += 1
def listen( ctx, aprs_login, aprs_password, filter, ): """Listen to packets on the APRS-IS Network based on FILTER. FILTER is the APRS Filter to use.\n see http://www.aprs-is.net/javAPRSFilter.aspx\n r/lat/lon/dist - Range Filter Pass posits and objects within dist km from lat/lon.\n p/aa/bb/cc... - Prefix Filter Pass traffic with fromCall that start with aa or bb or cc.\n b/call1/call2... - Budlist Filter Pass all traffic from exact call: call1, call2, ... (* wild card allowed) \n o/obj1/obj2... - Object Filter Pass all objects with the exact name of obj1, obj2, ... (* wild card allowed)\n """ config = ctx.obj["config"] if not aprs_login: click.echo(ctx.get_help()) click.echo("") ctx.fail("Must set --aprs_login or APRS_LOGIN") ctx.exit() if not aprs_password: click.echo(ctx.get_help()) click.echo("") ctx.fail("Must set --aprs-password or APRS_PASSWORD") ctx.exit() config["aprs"]["login"] = aprs_login config["aprs"]["password"] = aprs_password LOG.info(f"APRSD Listen 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]}") stats.APRSDStats(config) # 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() @trace.trace def rx_packet(packet): resp = packet.get("response", None) if resp == "ack": ack_num = packet.get("msgNo") console.log(f"We saw an ACK {ack_num} Ignoring") messaging.log_packet(packet) else: message = packet.get("message_text", None) fromcall = packet["from"] msg_number = packet.get("msgNo", "0") messaging.log_message( "Received Message", packet["raw"], message, fromcall=fromcall, ack=msg_number, console=console, ) # 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) # Creates the client object LOG.info("Creating client connection") client.factory.create().client aprs_client = client.factory.create().client LOG.debug(f"Filter by '{filter}'") aprs_client.set_filter(filter) while True: try: # This will register a packet consumer with aprslib # When new packets come in the consumer will process # the packet with console.status("Listening for packets"): aprs_client.consumer(rx_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 aprs_client.reset() except aprslib.exceptions.UnknownFormat: LOG.error("Got a Bad packet")
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 run(self): global check_email_delay LOG.debug("Starting") check_email_delay = 60 past = datetime.datetime.now() while not self.thread_stop: time.sleep(5) stats.APRSDStats().email_thread_update() # always sleep for 5 seconds and see if we need to check email # This allows CTRL-C to stop the execution of this loop sooner # than check_email_delay time now = datetime.datetime.now() if now - past > datetime.timedelta(seconds=check_email_delay): # It's time to check email # slowly increase delay every iteration, max out at 300 seconds # any send/receive/resend activity will reset this to 60 seconds if check_email_delay < 300: check_email_delay += 1 LOG.debug("check_email_delay is " + str(check_email_delay) + " seconds") shortcuts = CONFIG["aprsd"]["email"]["shortcuts"] # swap key/value shortcuts_inverted = {v: k for k, v in shortcuts.items()} date = datetime.datetime.now() month = date.strftime("%B")[:3] # Nov, Mar, Apr day = date.day year = date.year today = "{}-{}-{}".format(day, month, year) server = None try: server = _imap_connect() except Exception as e: LOG.exception("IMAP failed to connect.", e) if not server: continue try: messages = server.search(["SINCE", today]) except Exception as e: LOG.exception( "IMAP failed to search for messages since today.", e) continue LOG.debug("{} messages received today".format(len(messages))) try: _msgs = server.fetch(messages, ["ENVELOPE"]) except Exception as e: LOG.exception("IMAP failed to fetch/flag messages: ", e) continue for msgid, data in _msgs.items(): envelope = data[b"ENVELOPE"] LOG.debug( 'ID:%d "%s" (%s)' % (msgid, envelope.subject.decode(), envelope.date), ) f = re.search( r"'([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)", str(envelope.from_[0]), ) if f is not None: from_addr = f.group(1) else: from_addr = "noaddr" # LOG.debug("Message flags/tags: " + str(server.get_flags(msgid)[msgid])) # if "APRS" not in server.get_flags(msgid)[msgid]: # in python3, imap tags are unicode. in py2 they're strings. so .decode them to handle both try: taglist = [ x.decode(errors="ignore") for x in server.get_flags(msgid)[msgid] ] except Exception as e: LOG.exception("Failed to get flags.", e) break if "APRS" not in taglist: # if msg not flagged as sent via aprs try: server.fetch([msgid], ["RFC822"]) except Exception as e: LOG.exception( "Failed single server fetch for RFC822", e) break (body, from_addr) = parse_email(msgid, data, server) # unset seen flag, will stay bold in email client try: server.remove_flags(msgid, [imapclient.SEEN]) except Exception as e: LOG.exception("Failed to remove flags SEEN", e) # Not much we can do here, so lets try and # send the aprs message anyway if from_addr in shortcuts_inverted: # reverse lookup of a shortcut from_addr = shortcuts_inverted[from_addr] reply = "-" + from_addr + " " + body.decode( errors="ignore") msg = messaging.TextMessage( self.config["aprs"]["login"], self.config["ham"]["callsign"], reply, ) self.msg_queues["tx"].put(msg) # flag message as sent via aprs try: server.add_flags(msgid, ["APRS"]) # unset seen flag, will stay bold in email client except Exception as e: LOG.exception("Couldn't add APRS flag to email", e) try: server.remove_flags(msgid, [imapclient.SEEN]) except Exception as e: LOG.exception( "Couldn't remove seen flag from email", e) # check email more often since we just received an email check_email_delay = 60 # reset clock LOG.debug("Done looping over Server.fetch, logging out.") past = datetime.datetime.now() try: server.logout() except Exception as e: LOG.exception("IMAP failed to logout: ", e) continue else: # We haven't hit the email delay yet. # LOG.debug("Delta({}) < {}".format(now - past, check_email_delay)) pass # Remove ourselves from the global threads list threads.APRSDThreadList().remove(self) LOG.info("Exiting")
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 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 consumer(self, callback, blocking=True, immortal=False, raw=False): """ When a position sentence is received, it will be passed to the callback function blocking: if true (default), runs forever, otherwise will return after one sentence You can still exit the loop, by raising StopIteration in the callback function immortal: When true, consumer will try to reconnect and stop propagation of Parse exceptions if false (default), consumer will return raw: when true, raw packet is passed to callback, otherwise the result from aprs.parse() """ if not self._connected: raise ConnectionError("not connected to a server") line = b"" while True and not self.thread_stop: try: for line in self._socket_readlines(blocking): if line[0:1] != b"#": if raw: callback(line) else: callback(self._parse(line)) else: self.logger.debug("Server: %s", line.decode("utf8")) stats.APRSDStats().set_aprsis_keepalive() except ParseError as exp: self.logger.log( 11, "%s\n Packet: %s", exp, exp.packet, ) except UnknownFormat as exp: self.logger.log( 9, "%s\n Packet: %s", exp, exp.packet, ) except LoginError as exp: self.logger.error("%s: %s", exp.__class__.__name__, exp) except (KeyboardInterrupt, SystemExit): raise except (ConnectionDrop, ConnectionError): self.close() if not immortal: raise else: self.connect(blocking=blocking) continue except GenericError: pass except StopIteration: break except Exception: self.logger.error("APRS Packet: %s", line) raise if not blocking: break
def loop(self): if self.cntr % 60 == 0: tracker = messaging.MsgTrack() stats_obj = stats.APRSDStats() pl = packets.PacketList() thread_list = APRSDThreadList() now = datetime.datetime.now() last_email = stats_obj.email_thread_time if last_email: email_thread_time = utils.strfdelta(now - last_email) else: email_thread_time = "N/A" last_msg_time = utils.strfdelta(now - stats_obj.aprsis_keepalive) current, peak = tracemalloc.get_traced_memory() stats_obj.set_memory(current) stats_obj.set_memory_peak(peak) try: login = self.config["aprs"]["login"] except KeyError: login = self.config["ham"]["callsign"] keepalive = ( "{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} " "Last:{} Email: {} - RAM Current:{} Peak:{} Threads:{}" ).format( login, utils.strfdelta(stats_obj.uptime), pl.total_recv, pl.total_tx, len(tracker), stats_obj.msgs_tx, stats_obj.msgs_rx, last_msg_time, email_thread_time, utils.human_size(current), utils.human_size(peak), len(thread_list), ) LOG.info(keepalive) # See if we should reset the aprs-is client # Due to losing a keepalive from them delta_dict = utils.parse_delta_str(last_msg_time) delta = datetime.timedelta(**delta_dict) if delta > self.max_delta: # We haven't gotten a keepalive from aprs-is in a while # reset the connection.a if not client.KISSClient.is_enabled(self.config): LOG.warning("Resetting connection to APRS-IS.") client.factory.create().reset() # Check version every hour delta = now - self.checker_time if delta > datetime.timedelta(hours=1): self.checker_time = now level, msg = utils._check_version() if level: LOG.warning(msg) self.cntr += 1 time.sleep(1) return True