1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-08-18 17:44:02 -04:00

Compare commits

..

10 Commits

Author SHA1 Message Date
hemna 7530bcf55c Updated Changelog 2021-12-11 07:59:50 -05:00
hemna ab37a5e7a7 Merge pull request #79 from craigerl/fix_kiss_is_enabled
Fixed an KISS config disabled issue
2021-12-11 07:56:47 -05:00
hemna 3b9970c0e7 Fixed an KISS config disabled issue
This patch fixes a small bug when both KISS interfaces are disabled.
2021-12-11 07:46:43 -05:00
hemna e57a2e2ffc Fixed a bug with multiple notify plugins enabled
This patch fixes an issue with the processing of packets
and updateing the watchlist.  Previously after the
notify plugin processed the packet it would update the watchlist.
This doesn't work when there are more than 1 notify plugins
enabled, only the first notify plugin seeing the packet will
recognize that the callsign is old.
2021-12-10 14:20:57 -05:00
hemna 6a1cea63e4 Merge pull request #77 from craigerl/logs
Unify the logging to file and stdout
2021-12-10 11:12:48 -05:00
hemna 592b328956 Unify the logging to file and stdout
This patch updates the logging facility to ensure that
logging to a file works even when --quiet mode is selected.
Also update the listen and list-plugins command to show
a console.status line while waiting for results to come in.
2021-12-10 10:49:09 -05:00
hemna 450bacfe99 Merge pull request #76 from craigerl/list-plugins
Added new feature to list-plugins command
2021-12-09 09:44:49 -05:00
hemna cd62db95c1 Added new feature to list-plugins command
This patch updates the ouput of the list-plugins command.
This also adds the ability to show the available plugins
to install that are published packages on pypi.org.

This also shows the list of installed packages from pypi.org
2021-12-08 17:16:17 -05:00
hemna 28b54c330d more README.rst cleanup 2021-12-07 15:22:08 -05:00
hemna 7c653cc100 Updated README examples
The examples in the README.rst were painfully old.
2021-12-07 15:18:27 -05:00
19 changed files with 415 additions and 193 deletions
+11
View File
@@ -1,9 +1,20 @@
CHANGES
=======
v2.5.7
------
* Fixed an KISS config disabled issue
* Fixed a bug with multiple notify plugins enabled
* Unify the logging to file and stdout
* Added new feature to list-plugins command
* more README.rst cleanup
* Updated README examples
v2.5.6
------
* Changelog
* Tightened up the packet logging
* Added unit tests for USWeatherPlugin, USMetarPlugin
* Added test\_location to test LocationPlugin
+89 -87
View File
@@ -46,56 +46,31 @@ callsigns to look out for. The watch list can notify you when a HAM callsign
in the list is seen and now available to message on the APRS network.
List of core server plugins
===========================
Plugins function by specifying a regex that is searched for in the APRS message.
If it matches, the plugin runs. IF the regex doesn't match, the plugin is skipped.
* EmailPlugin - Check email and reply with contents. Have to configure IMAP and SMTP settings in aprs.yml
* FortunePlugin - Replies with old unix fortune random fortune!
* LocationPlugin - Checks location of ham operator
* PingPlugin - Sends pong with timestamp
* QueryPlugin - Allows querying the list of delayed messages that were not ACK'd by radio
* TimePlugin - Current time of day
* WeatherPlugin - Get weather conditions for current location of HAM callsign
* VersionPlugin - Reports the version information for aprsd
* NotifySeenPlugin - Send a message when a message is seen from a callsign in
the watch list. This is helpful when you want to know
when a friend is online in the ARPS network, but haven't
been seen in a while.
Current messages this will respond to:
Current List of built-in plugins:
======================================
::
APRS messages:
l(ocation) [callsign] = descriptive current location of your radio
8 Miles E Auburn CA 1673' 39.92150,-120.93950 0.1h ago
w(eather) = weather forecast for your radio's current position
58F(58F/46F) Partly Cloudy. Tonight, Heavy Rain.
t(ime) = respond with the current time
f(ortune) = respond with a short fortune
-email_addr email text = send an email, say "mapme" to send a current position/map
-2 = resend the last 2 emails from your imap inbox to this radio
p(ing) = respond with Pong!/time
v(ersion) = Respond with current APRSD Version string
anything else = respond with usage
└─> aprsd list-plugins
Plugin Name Plugin Path Type Info
---------------------- ----------------------------------------- ------------ ----------------------------------------------------------
EmailPlugin aprsd.plugins.email.EmailPlugin RegexCommand Send and Receive email
FortunePlugin aprsd.plugins.fortune.FortunePlugin RegexCommand Give me a fortune
LocationPlugin aprsd.plugins.location.LocationPlugin RegexCommand Where in the world is a CALLSIGN's last GPS beacon?
NotifySeenPlugin aprsd.plugins.notify.NotifySeenPlugin WatchList Notify me when a CALLSIGN is recently seen on APRS-IS
PingPlugin aprsd.plugins.ping.PingPlugin RegexCommand reply with a Pong!
QueryPlugin aprsd.plugins.query.QueryPlugin RegexCommand APRSD Owner command to query messages in the MsgTrack
TimeOWMPlugin aprsd.plugins.time.TimeOWMPlugin RegexCommand Current time of GPS beacon's timezone. Uses OpenWeatherMap
TimeOpenCageDataPlugin aprsd.plugins.time.TimeOpenCageDataPlugin RegexCommand Current time of GPS beacon timezone. Uses OpenCage
TimePlugin aprsd.plugins.time.TimePlugin RegexCommand What is the current local time.
VersionPlugin aprsd.plugins.version.VersionPlugin RegexCommand What is the APRSD Version
AVWXWeatherPlugin aprsd.plugins.weather.AVWXWeatherPlugin RegexCommand AVWX weather of GPS Beacon location
OWMWeatherPlugin aprsd.plugins.weather.OWMWeatherPlugin RegexCommand OpenWeatherMap weather of GPS Beacon location
USMetarPlugin aprsd.plugins.weather.USMetarPlugin RegexCommand USA only METAR of GPS Beacon location
USWeatherPlugin aprsd.plugins.weather.USWeatherPlugin RegexCommand Provide USA only weather of GPS Beacon location
Meanwhile this code will monitor a single imap mailbox and forward email
to your BASECALLSIGN over the air. Only radios using the BASECALLSIGN are allowed
to send email, so consider this security risk before using this (or Amatuer radio in
general). Email is single user at this time.
There are additional parameters in the code (sorry), so be sure to set your
email server, and associated logins, passwords. search for "yourdomain",
"password". Search for "shortcuts" to setup email aliases as well.
Installation:
installation:
=============
pip install aprsd
@@ -112,20 +87,21 @@ Help
└─[$] > aprsd -h
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
Shell completion for click-completion-command Available shell types:
bash Bourne again shell fish Friendly interactive shell
powershell Windows PowerShell zsh Z shell Default type: auto
Options:
--version Show the version and exit.
-h, --help Show this message and exit.
Commands:
install Install the click-completion-command completion
check-version Check this version against the latest in pypi.org.
completion Click Completion subcommands
dev Development type subcommands
healthcheck Check the health of the running aprsd server.
list-plugins List the built in plugins available to APRSD.
listen Listen to packets on the APRS-IS Network based on FILTER.
sample-config This dumps the config to stdout.
send-message Send a message to a callsign via APRS_IS.
server Start the aprsd server process.
show Show the click-completion-command completion code
server Start the aprsd server gateway process.
version Show the APRSD version.
@@ -145,8 +121,12 @@ Output
└─> aprsd sample-config
aprs:
# Set enabled to False if there is no internet connectivity.
# This is useful for a direwolf KISS aprs connection only.
# Get the passcode for your callsign here:
# https://apps.magicbug.co.uk/passcode
enabled: true
host: rotate.aprs2.net
login: CALLSIGN
password: '00000'
@@ -184,18 +164,38 @@ Output
- aprsd.plugins.weather.USWeatherPlugin
- aprsd.plugins.version.VersionPlugin
logfile: /tmp/aprsd.log
logformat: '[%(asctime)s] [%(threadName)-12s] [%(levelname)-5.5s] %(message)s - [%(pathname)s:%(lineno)d]'
logformat: '[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s] %(message)s
- [%(pathname)s:%(lineno)d]'
rich_logging: false
save_location: /Users/i530566/.config/aprsd/
trace: false
units: imperial
watch_list:
alert_callsign: NOCALL
alert_time_seconds: 43200
callsigns: []
enabled: false
enabled_plugins:
- aprsd.plugins.notify.NotifySeenPlugin
packet_keep_count: 10
web:
enabled: true
host: 0.0.0.0
logging_enabled: true
port: 8001
users:
admin: aprsd
admin: password-here
ham:
callsign: CALLSIGN
callsign: NOCALL
kiss:
serial:
baudrate: 9600
device: /dev/ttyS0
enabled: false
tcp:
enabled: false
host: direwolf.ip.address
port: '8001'
services:
aprs.fi:
# Get the apiKey from your aprs.fi account here:
@@ -229,35 +229,35 @@ look for incomming commands to the callsign configured in the config file
::
└─[$] > aprsd server --help
Usage: aprsd server [OPTIONS]
Usage: aprsd server [OPTIONS]
Start the aprsd server process.
Start the aprsd server gateway process.
Options:
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
The log level to use for aprsd.log
[default: INFO]
Options:
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
The log level to use for aprsd.log
[default: INFO]
-c, --config TEXT The aprsd config file to use for options.
[default:
/Users/i530566/.config/aprsd/aprsd.yml]
--quiet Don't log to stdout
-f, --flush Flush out all old aged messages on disk.
[default: False]
-h, --help Show this message and exit.
--quiet Don't log to stdout
--disable-validation Disable email shortcut validation. Bad
email addresses can result in broken email
responses!!
-c, --config TEXT The aprsd config file to use for options.
[default:
/home/waboring/.config/aprsd/aprsd.yml]
-f, --flush Flush out all old aged messages on disk.
[default: False]
-h, --help Show this message and exit.
$ aprsd server
└─> aprsd server
Load config
[02/13/2021 09:22:09 AM] [MainThread ] [INFO ] APRSD Started version: 1.6.0
[02/13/2021 09:22:09 AM] [MainThread ] [INFO ] Checking IMAP configuration
[02/13/2021 09:22:09 AM] [MainThread ] [INFO ] Checking SMTP configuration
[02/13/2021 09:22:10 AM] [MainThread ] [INFO ] Validating 2 Email shortcuts. This can take up to 10 seconds per shortcut
12/07/2021 03:16:17 PM MainThread INFO APRSD is up to date server.py:51
12/07/2021 03:16:17 PM MainThread INFO APRSD Started version: 2.5.6 server.py:52
12/07/2021 03:16:17 PM MainThread INFO Using CONFIG values: server.py:55
12/07/2021 03:16:17 PM MainThread INFO ham.callsign = WB4BOR server.py:60
12/07/2021 03:16:17 PM MainThread INFO aprs.login = WB4BOR-12 server.py:60
12/07/2021 03:16:17 PM MainThread INFO aprs.password = XXXXXXXXXXXXXXXXXXX server.py:58
12/07/2021 03:16:17 PM MainThread INFO aprs.host = noam.aprs2.net server.py:60
12/07/2021 03:16:17 PM MainThread INFO aprs.port = 14580 server.py:60
12/07/2021 03:16:17 PM MainThread INFO aprs.logfile = /tmp/aprsd.log server.py:60
send-message
@@ -269,25 +269,27 @@ test messages
::
└─[$] > aprsd send-message -h
Usage: aprsd send-message [OPTIONS] TOCALLSIGN [COMMAND]...
Usage: aprsd send-message [OPTIONS] TOCALLSIGN COMMAND...
Send a message to a callsign via APRS_IS.
Options:
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
The log level to use for aprsd.log
[default: DEBUG]
--quiet Don't log to stdout
[default: INFO]
-c, --config TEXT The aprsd config file to use for options.
[default: ~/.config/aprsd/aprsd.yml]
[default:
/Users/i530566/.config/aprsd/aprsd.yml]
--quiet Don't log to stdout
--aprs-login TEXT What callsign to send the message from.
[env var: APRS_LOGIN]
--aprs-password TEXT the APRS-IS password for APRS_LOGIN [env
var: APRS_PASSWORD]
-n, --no-ack Don't wait for an ack, just sent it to APRS-
IS and bail. [default: False]
-w, --wait-response Wait for a response to the message?
[default: False]
--raw TEXT Send a raw message. Implies --no-ack
-h, --help Show this message and exit.
+2
View File
@@ -131,6 +131,8 @@ class KISSClient(Client):
if config.get("kiss.tcp.enabled", default=False):
return True
return False
@staticmethod
def transport(config):
if config.get("kiss.serial.enabled", default=False):
+1 -1
View File
@@ -1,7 +1,7 @@
import click
import click_completion
from ..aprsd import cli
from aprsd.aprsd import cli
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
+1 -2
View File
@@ -9,8 +9,7 @@ import click
# local imports here
from aprsd import cli_helper, client, messaging, packets, plugin, stats, trace
from ..aprsd import cli
from aprsd.aprsd import cli
LOG = logging.getLogger("APRSD")
+37 -32
View File
@@ -11,17 +11,18 @@ import sys
import click
import requests
from rich.console import Console
import aprsd
from aprsd import cli_helper, utils
# local imports here
from ..aprsd import cli
from aprsd.aprsd import cli
# setup the global logger
# logging.basicConfig(level=logging.DEBUG) # level=10
LOG = logging.getLogger("APRSD")
console = Console()
@cli.command()
@@ -43,36 +44,40 @@ LOG = logging.getLogger("APRSD")
@cli_helper.process_standard_options_no_config
def healthcheck(ctx, health_url, timeout):
"""Check the health of the running aprsd server."""
LOG.debug(f"APRSD HealthCheck version: {aprsd.__version__}")
try:
url = health_url
response = requests.get(url, timeout=timeout)
response.raise_for_status()
except Exception as ex:
LOG.error(f"Failed to fetch healthcheck url '{url}' : '{ex}'")
sys.exit(-1)
else:
stats = json.loads(response.text)
LOG.debug(stats)
email_thread_last_update = stats["stats"]["email"]["thread_last_update"]
delta = utils.parse_delta_str(email_thread_last_update)
d = datetime.timedelta(**delta)
max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
max_delta = datetime.timedelta(**max_timeout)
if d > max_delta:
LOG.error(f"Email thread is very old! {d}")
console.log(f"APRSD HealthCheck version: {aprsd.__version__}")
with console.status(f"APRSD HealthCheck version: {aprsd.__version__}") as status:
try:
url = health_url
response = requests.get(url, timeout=timeout)
response.raise_for_status()
except Exception as ex:
console.log(f"Failed to fetch healthcheck url '{url}' : '{ex}'")
sys.exit(-1)
else:
import time
time.sleep(2)
status.update("PISS")
time.sleep(2)
aprsis_last_update = stats["stats"]["aprs-is"]["last_update"]
delta = utils.parse_delta_str(aprsis_last_update)
d = datetime.timedelta(**delta)
max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
max_delta = datetime.timedelta(**max_timeout)
if d > max_delta:
LOG.error(f"APRS-IS last update is very old! {d}")
sys.exit(-1)
stats = json.loads(response.text)
email_thread_last_update = stats["stats"]["email"]["thread_last_update"]
sys.exit(0)
if email_thread_last_update != "never":
delta = utils.parse_delta_str(email_thread_last_update)
d = datetime.timedelta(**delta)
max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
max_delta = datetime.timedelta(**max_timeout)
if d > max_delta:
console.log(f"Email thread is very old! {d}")
sys.exit(-1)
aprsis_last_update = stats["stats"]["aprs-is"]["last_update"]
delta = utils.parse_delta_str(aprsis_last_update)
d = datetime.timedelta(**delta)
max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
max_delta = datetime.timedelta(**max_timeout)
if d > max_delta:
LOG.error(f"APRS-IS last update is very old! {d}")
sys.exit(-1)
sys.exit(0)
+189 -14
View File
@@ -1,21 +1,183 @@
import fnmatch
import importlib
import inspect
import logging
from textwrap import indent
import os
import pkgutil
import re
import sys
from traceback import print_tb
from urllib.parse import urljoin
from bs4 import BeautifulSoup
import click
from tabulate import tabulate
import requests
from rich.console import Console
from rich.table import Table
from rich.text import Text
from thesmuggler import smuggle
from aprsd import cli_helper, plugin
from aprsd import cli_helper
from aprsd import plugin as aprsd_plugin
from aprsd.aprsd import cli
from aprsd.plugins import (
email, fortune, location, notify, ping, query, time, version, weather,
)
from ..aprsd import cli
LOG = logging.getLogger("APRSD")
def onerror(name):
print(f"Error importing module {name}")
type, value, traceback = sys.exc_info()
print_tb(traceback)
def is_plugin(obj):
for c in inspect.getmro(obj):
if issubclass(c, aprsd_plugin.APRSDPluginBase):
return True
return False
def plugin_type(obj):
for c in inspect.getmro(obj):
if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
return "RegexCommand"
if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
return "WatchList"
if issubclass(c, aprsd_plugin.APRSDPluginBase):
return "APRSDPluginBase"
return "Unknown"
def walk_package(package):
return pkgutil.walk_packages(
package.__path__,
package.__name__ + ".",
onerror=onerror,
)
def get_module_info(package_name, module_name, module_path):
if not os.path.exists(module_path):
return None
dir_path = os.path.realpath(module_path)
pattern = "*.py"
obj_list = []
for path, _subdirs, files in os.walk(dir_path):
for name in files:
if fnmatch.fnmatch(name, pattern):
module = smuggle(f"{path}/{name}")
for mem_name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and is_plugin(obj):
obj_list.append(
{
"package": package_name,
"name": mem_name, "obj": obj,
"version": obj.version,
"path": f"{'.'.join([module_name, obj.__name__])}",
},
)
return obj_list
def get_installed_plugins():
# installed plugins
ip = {}
for finder, name, ispkg in pkgutil.iter_modules():
if name.startswith("aprsd_"):
if ispkg:
module = importlib.import_module(name)
pkgs = walk_package(module)
for pkg in pkgs:
pkg_info = get_module_info(module.__name__, pkg.name, module.__path__[0])
ip[name] = pkg_info
return ip
def show_pypi_plugins(installed_plugins, console):
query = "aprsd"
api_url = "https://pypi.org/search/"
snippets = []
s = requests.Session()
for page in range(1, 3):
params = {"q": query, "page": page}
r = s.get(api_url, params=params)
soup = BeautifulSoup(r.text, "html.parser")
snippets += soup.select('a[class*="snippet"]')
if not hasattr(s, "start_url"):
s.start_url = r.url.rsplit("&page", maxsplit=1).pop(0)
title = Text.assemble(
("Pypi.org APRSD Installable Plugin Packages\n\n", "bold magenta"),
("Install any of the following plugins with ", "bold yellow"),
("pip install ", "bold white"),
("<Plugin Package Name>", "cyan"),
)
table = Table(title=title)
table.add_column("Plugin Package Name", style="cyan", no_wrap=True)
table.add_column("Description", style="yellow")
table.add_column("Version", style="yellow")
table.add_column("Released", style="bold green")
table.add_column("Installed?", style="red")
table.add_column("Version Installed", style="yellow")
for snippet in snippets:
link = urljoin(api_url, snippet.get("href"))
package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
released = re.sub(r"\s+", " ", snippet.select_one('span[class*="released"]').text.strip())
description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
emoji = ":open_file_folder:"
if "aprsd-" not in package or "-plugin" not in package:
continue
under = package.replace("-", "_")
if under in installed_plugins:
installed = "Yes"
try:
version_installed = installed_plugins[under].__version__
except Exception:
version_installed = "Unknown"
else:
installed = "No"
version_installed = "Unknown"
table.add_row(
f"[link={link}]{emoji}[/link] {package}",
description, version, released, installed, version_installed,
)
console.print("\n")
console.print(table)
return
def show_installed_plugins(installed_plugins, console):
if not installed_plugins:
return
table = Table(
title="[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]",
)
table.add_column("Package Name", style="white", no_wrap=True)
table.add_column("Plugin Name", style="cyan", no_wrap=True)
table.add_column("Type", style="bold green")
table.add_column("Plugin Path", style="bold blue")
for name in installed_plugins:
for plugin in installed_plugins[name]:
table.add_row(name.replace("_", "-"), plugin["name"], plugin_type(plugin["obj"]), plugin["path"])
console.print("\n")
console.print(table)
@cli.command()
@cli_helper.add_options(cli_helper.common_options)
@click.pass_context
@@ -30,7 +192,7 @@ def list_plugins(ctx):
entries = inspect.getmembers(module, inspect.isclass)
for entry in entries:
cls = entry[1]
if issubclass(cls, plugin.APRSDPluginBase):
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
info = {
"name": cls.__qualname__,
"path": f"{cls.__module__}.{cls.__qualname__}",
@@ -39,21 +201,34 @@ def list_plugins(ctx):
"short_desc": cls.short_description,
}
if issubclass(cls, plugin.APRSDRegexCommandPluginBase):
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
info["command_regex"] = cls.command_regex
info["type"] = "RegexCommand"
if issubclass(cls, plugin.APRSDWatchListPluginBase):
if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
info["type"] = "WatchList"
plugins.append(info)
lines = []
headers = ("Plugin Name", "Plugin Path", "Type", "Info")
plugins = sorted(plugins, key=lambda i: i["name"])
table = Table(
title="[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]",
)
table.add_column("Plugin Name", style="cyan", no_wrap=True)
table.add_column("Info", style="bold yellow")
table.add_column("Type", style="bold green")
table.add_column("Plugin Path", style="bold blue")
for entry in plugins:
lines.append(
(entry["name"], entry["path"], entry["type"], entry["short_desc"]),
)
table.add_row(entry["name"], entry["short_desc"], entry["type"], entry["path"])
click.echo(indent(tabulate(lines, headers, disable_numparse=True), " "))
console = Console()
console.print(table)
# now find any from pypi?
installed_plugins = get_installed_plugins()
with console.status("Fetching pypi.org plugins"):
show_pypi_plugins(installed_plugins, console)
with console.status("Looking for installed aprsd plugins"):
show_installed_plugins(installed_plugins, console)
+7 -4
View File
@@ -10,19 +10,20 @@ import time
import aprslib
import click
from rich.console import Console
# local imports here
import aprsd
from aprsd import (
cli_helper, client, messaging, packets, stats, threads, trace, utils,
)
from ..aprsd import cli
from aprsd.aprsd import cli
# setup the global logger
# logging.basicConfig(level=logging.DEBUG) # level=10
LOG = logging.getLogger("APRSD")
console = Console()
def signal_handler(sig, frame):
@@ -114,7 +115,7 @@ def listen(
resp = packet.get("response", None)
if resp == "ack":
ack_num = packet.get("msgNo")
LOG.info(f"We saw an ACK {ack_num} Ignoring")
console.log(f"We saw an ACK {ack_num} Ignoring")
messaging.log_packet(packet)
else:
message = packet.get("message_text", None)
@@ -126,6 +127,7 @@ def listen(
message,
fromcall=fromcall,
ack=msg_number,
console=console,
)
# Initialize the client factory and create
@@ -149,7 +151,8 @@ def listen(
# This will register a packet consumer with aprslib
# When new packets come in the consumer will process
# the packet
aprs_client.consumer(rx_packet, raw=False)
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)
+1 -2
View File
@@ -8,8 +8,7 @@ import click
import aprsd
from aprsd import cli_helper, client, messaging, packets
from ..aprsd import cli
from aprsd.aprsd import cli
LOG = logging.getLogger("APRSD")
+1 -2
View File
@@ -10,8 +10,7 @@ from aprsd import (
trace, utils,
)
from aprsd import aprsd as aprsd_main
from ..aprsd import cli
from aprsd.aprsd import cli
LOG = logging.getLogger("APRSD")
+33 -22
View File
@@ -1,9 +1,7 @@
import datetime
import json
import logging
from logging import NullHandler
from logging.handlers import RotatingFileHandler
import sys
import threading
import time
@@ -11,6 +9,7 @@ import aprslib
from aprslib.exceptions import LoginError
import flask
from flask import request
from flask.logging import default_handler
import flask_classful
from flask_httpauth import HTTPBasicAuth
from flask_socketio import Namespace, SocketIO
@@ -21,6 +20,7 @@ from aprsd import client
from aprsd import config as aprsd_config
from aprsd import log, messaging, packets, plugin, stats, threads, utils
from aprsd.clients import aprsis
from aprsd.logging import logging as aprsd_logging
LOG = logging.getLogger("APRSD")
@@ -552,6 +552,15 @@ class LoggingNamespace(Namespace):
def setup_logging(config, flask_app, loglevel, quiet):
flask_log = logging.getLogger("werkzeug")
flask_app.logger.removeHandler(default_handler)
flask_log.removeHandler(default_handler)
log_level = aprsd_config.LOG_LEVELS[loglevel]
flask_log.setLevel(log_level)
date_format = config["aprsd"].get(
"dateformat",
aprsd_config.DEFAULT_DATE_FORMAT,
)
if not config["aprsd"]["web"].get("logging_enabled", False):
# disable web logging
@@ -559,28 +568,30 @@ def setup_logging(config, flask_app, loglevel, quiet):
flask_app.logger.disabled = True
return
log_level = aprsd_config.LOG_LEVELS[loglevel]
LOG.setLevel(log_level)
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
if config["aprsd"].get("rich_logging", False) and not quiet:
log_format = "%(message)s"
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
rh = aprsd_logging.APRSDRichHandler(
show_thread=True, thread_width=15,
rich_tracebacks=True, omit_repeated_times=False,
)
rh.setFormatter(log_formatter)
flask_log.addHandler(rh)
log_file = config["aprsd"].get("logfile", None)
if log_file:
fh = RotatingFileHandler(log_file, maxBytes=(10248576 * 5), backupCount=4)
else:
fh = NullHandler()
fh.setFormatter(log_formatter)
for handler in flask_app.logger.handlers:
handler.setFormatter(log_formatter)
print(handler)
flask_log.addHandler(fh)
if not quiet:
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(log_formatter)
flask_log.addHandler(sh)
log_format = config["aprsd"].get(
"logformat",
aprsd_config.DEFAULT_LOG_FORMAT,
)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
fh = RotatingFileHandler(
log_file, maxBytes=(10248576 * 5),
backupCount=4,
)
fh.setFormatter(log_formatter)
flask_log.addHandler(fh)
def init_flask(config, loglevel, quiet):
+11 -15
View File
@@ -18,35 +18,31 @@ logging_queue = queue.Queue()
def setup_logging(config, loglevel, quiet):
log_level = aprsd_config.LOG_LEVELS[loglevel]
LOG.setLevel(log_level)
if config["aprsd"].get("rich_logging", False):
log_format = "%(message)s"
else:
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
log_file = config["aprsd"].get("logfile", None)
rich_logging = False
if config["aprsd"].get("rich_logging", False):
if config["aprsd"].get("rich_logging", False) and not quiet:
log_format = "%(message)s"
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
rh = aprsd_logging.APRSDRichHandler(
show_thread=True, thread_width=15,
show_thread=True, thread_width=20,
rich_tracebacks=True, omit_repeated_times=False,
)
rh.setFormatter(log_formatter)
LOG.addHandler(rh)
rich_logging = True
if log_file:
fh = RotatingFileHandler(log_file, maxBytes=(10248576 * 5), backupCount=4)
else:
fh = NullHandler()
log_file = config["aprsd"].get("logfile", None)
fh.setFormatter(log_formatter)
LOG.addHandler(fh)
if log_file:
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
fh = RotatingFileHandler(log_file, maxBytes=(10248576 * 5), backupCount=4)
fh.setFormatter(log_formatter)
LOG.addHandler(fh)
imap_logger = None
if config.get("aprsd.email.enabled", default=False) and config.get("aprsd.email.imap.debug", default=False):
imap_logger = logging.getLogger("imapclient.imaplib")
imap_logger.setLevel(log_level)
imap_logger.addHandler(fh)
+18 -7
View File
@@ -190,6 +190,7 @@ class Message(metaclass=abc.ABCMeta):
fromcall,
tocall,
msg_id=None,
allow_delay=True,
):
self.fromcall = fromcall
self.tocall = tocall
@@ -199,6 +200,10 @@ class Message(metaclass=abc.ABCMeta):
msg_id = c.value
self.id = msg_id
# do we try and save this message for later if we don't get
# an ack? Some messages we don't want to do this ever.
self.allow_delay = allow_delay
@abc.abstractmethod
def send(self):
"""Child class must declare."""
@@ -214,8 +219,8 @@ class RawMessage(Message):
message = None
def __init__(self, message):
super().__init__(None, None, msg_id=None)
def __init__(self, message, allow_delay=True):
super().__init__(fromcall=None, tocall=None, msg_id=None, allow_delay=allow_delay)
self.message = message
def dict(self):
@@ -269,11 +274,11 @@ class TextMessage(Message):
msg_id=None,
allow_delay=True,
):
super().__init__(fromcall, tocall, msg_id)
super().__init__(
fromcall=fromcall, tocall=tocall,
msg_id=msg_id, allow_delay=allow_delay,
)
self.message = message
# do we try and save this message for later if we don't get
# an ack? Some messages we don't want to do this ever.
self.allow_delay = allow_delay
def dict(self):
now = datetime.datetime.now()
@@ -369,6 +374,8 @@ class SendMessageThread(threads.APRSDThread):
# 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.")
if not msg.allow_delay:
tracker.remove(msg.id)
return False
# Message is still outstanding and needs to be acked.
@@ -527,6 +534,7 @@ def log_packet(packet):
def log_message(
header, raw, message, tocall=None, fromcall=None, msg_num=None,
retry_number=None, ack=None, packet_type=None, uuid=None,
console=None,
):
"""
@@ -565,4 +573,7 @@ def log_message(
log_list.append(f" UUID : {uuid}")
log_list.append(f"{header} _______________ Complete")
LOG.info("\n".join(log_list))
if console:
console.log("\n".join(log_list))
else:
LOG.info("\n".join(log_list))
+1 -1
View File
@@ -86,7 +86,7 @@ class WatchList(objectstore.ObjectStoreMixin):
if config:
self.config = config
ring_size = config["aprsd"]["watch_list"]["packet_keep_count"]
ring_size = config["aprsd"]["watch_list"].get("packet_keep_count", 10)
for callsign in config["aprsd"]["watch_list"].get("callsigns", []):
call = callsign.replace("*", "")
-1
View File
@@ -177,7 +177,6 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
)
if result:
self.tx_inc()
wl.update_seen(packet)
else:
LOG.warning(f"{self.__class__} plugin is not enabled")
+4 -2
View File
@@ -1,6 +1,6 @@
import logging
from aprsd import messaging, packets, plugin
from aprsd import messaging, packets, plugin, trace
LOG = logging.getLogger("APRSD")
@@ -17,6 +17,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
short_description = "Notify me when a CALLSIGN is recently seen on APRS-IS"
@trace.trace
def process(self, packet):
LOG.info("NotifySeenPlugin")
@@ -46,10 +47,11 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
)
return msg
else:
LOG.debug("fromcall and notify_callsign are the same, not notifying")
return messaging.NULL_MESSAGE
else:
LOG.debug(
"Not old enough to notify callsign '{}' : {} < {}".format(
"Not old enough to notify on callsign '{}' : {} < {}".format(
fromcall,
age,
wl.max_delta(),
+3 -1
View File
@@ -202,7 +202,7 @@ class APRSDProcessPacketThread(APRSDThread):
self.packet = packet
self.config = config
name = self.packet["raw"][:10]
super().__init__(f"RX_PACKET-{name}")
super().__init__(f"RXPKT-{name}")
def process_ack_packet(self, packet):
ack_num = packet.get("msgNo")
@@ -261,6 +261,8 @@ class APRSDProcessPacketThread(APRSDThread):
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):
+2
View File
@@ -22,3 +22,5 @@ flask-socketio
eventlet
tabulate
rich
# For the list-plugins pypi.org search scraping
beautifulsoup4
+4
View File
@@ -10,6 +10,8 @@ aprslib==0.7.0
# via -r requirements.in
backoff==1.11.1
# via opencage
beautifulsoup4==4.10.0
# via -r requirements.in
bidict==0.21.2
# via python-socketio
certifi==2021.5.30
@@ -112,6 +114,8 @@ six==1.16.0
# imapclient
# pyopenssl
# signalslot
soupsieve==2.3.1
# via beautifulsoup4
tabulate==0.8.9
# via -r requirements.in
thesmuggler==1.0.1