mirror of
https://github.com/craigerl/aprsd.git
synced 2026-08-17 17:13:49 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4402fc6cce |
@@ -4,22 +4,6 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
#### [4.1.2](https://github.com/craigerl/aprsd/compare/4.1.1...4.1.2)
|
|
||||||
|
|
||||||
> 6 March 2025
|
|
||||||
|
|
||||||
- Allow passing in a custom handler to setup_logging [`d262589`](https://github.com/craigerl/aprsd/commit/d2625893134f498748859da3b1684b04d456f790)
|
|
||||||
|
|
||||||
#### [4.1.1](https://github.com/craigerl/aprsd/compare/4.1.0...4.1.1)
|
|
||||||
|
|
||||||
> 5 March 2025
|
|
||||||
|
|
||||||
- Added new config to disable logging to console [`0fa5b07`](https://github.com/craigerl/aprsd/commit/0fa5b07d4bf4bc5d5aaad1de52b78058e472fe24)
|
|
||||||
- Added threads.service [`c1c89fd`](https://github.com/craigerl/aprsd/commit/c1c89fd2c2c69c5e6c5d29a736a7b89e3d45cfe2)
|
|
||||||
- Update requirements [`2b185ee`](https://github.com/craigerl/aprsd/commit/2b185ee1b84598c832d8a5d73753cb428854b932)
|
|
||||||
- Fixed some more ruff checks [`94ba915`](https://github.com/craigerl/aprsd/commit/94ba915ed44b11eaabc885e033669d67d8c341a5)
|
|
||||||
- 4.1.1 release [`7ed8028`](https://github.com/craigerl/aprsd/commit/7ed80283071c1ccebf1e3373727608edd0a56ee9)
|
|
||||||
|
|
||||||
#### [4.1.0](https://github.com/craigerl/aprsd/compare/4.0.2...4.1.0)
|
#### [4.1.0](https://github.com/craigerl/aprsd/compare/4.0.2...4.1.0)
|
||||||
|
|
||||||
> 20 February 2025
|
> 20 February 2025
|
||||||
@@ -37,7 +21,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- fix for None packet in rx thread [`d82a81a`](https://github.com/craigerl/aprsd/commit/d82a81a2c3c1a7f50177a0a6435a555daeb858aa)
|
- fix for None packet in rx thread [`d82a81a`](https://github.com/craigerl/aprsd/commit/d82a81a2c3c1a7f50177a0a6435a555daeb858aa)
|
||||||
- Fix runaway KISS driver on failed connnection [`b6da0eb`](https://github.com/craigerl/aprsd/commit/b6da0ebb0d2f4d7078dbbf91d8c03715412d89ea)
|
- Fix runaway KISS driver on failed connnection [`b6da0eb`](https://github.com/craigerl/aprsd/commit/b6da0ebb0d2f4d7078dbbf91d8c03715412d89ea)
|
||||||
- CONF.logging.enable_color option added [`06bdb34`](https://github.com/craigerl/aprsd/commit/06bdb34642640d91ea96e3c6e8d8b5a4b8230611)
|
- CONF.logging.enable_color option added [`06bdb34`](https://github.com/craigerl/aprsd/commit/06bdb34642640d91ea96e3c6e8d8b5a4b8230611)
|
||||||
- Update Changelog for 4.1.0 release [`a3cda9f`](https://github.com/craigerl/aprsd/commit/a3cda9f37d4c9b955b523f46b2eb8cf412a84407)
|
|
||||||
|
|
||||||
#### [4.0.2](https://github.com/craigerl/aprsd/compare/4.0.1...4.0.2)
|
#### [4.0.2](https://github.com/craigerl/aprsd/compare/4.0.1...4.0.2)
|
||||||
|
|
||||||
|
|||||||
+35
-36
@@ -13,35 +13,35 @@ from aprsd.utils import trace
|
|||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
home = str(Path.home())
|
home = str(Path.home())
|
||||||
DEFAULT_CONFIG_DIR = f'{home}/.config/aprsd/'
|
DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
|
||||||
DEFAULT_SAVE_FILE = f'{home}/.config/aprsd/aprsd.p'
|
DEFAULT_SAVE_FILE = f"{home}/.config/aprsd/aprsd.p"
|
||||||
DEFAULT_CONFIG_FILE = f'{home}/.config/aprsd/aprsd.conf'
|
DEFAULT_CONFIG_FILE = f"{home}/.config/aprsd/aprsd.conf"
|
||||||
|
|
||||||
|
|
||||||
F = t.TypeVar('F', bound=t.Callable[..., t.Any])
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
common_options = [
|
common_options = [
|
||||||
click.option(
|
click.option(
|
||||||
'--loglevel',
|
"--loglevel",
|
||||||
default='INFO',
|
default="INFO",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
type=click.Choice(
|
type=click.Choice(
|
||||||
['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'],
|
["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
|
||||||
case_sensitive=False,
|
case_sensitive=False,
|
||||||
),
|
),
|
||||||
show_choices=True,
|
show_choices=True,
|
||||||
help='The log level to use for aprsd.log',
|
help="The log level to use for aprsd.log",
|
||||||
),
|
),
|
||||||
click.option(
|
click.option(
|
||||||
'-c',
|
"-c",
|
||||||
'--config',
|
"--config",
|
||||||
'config_file',
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=DEFAULT_CONFIG_FILE,
|
default=DEFAULT_CONFIG_FILE,
|
||||||
help='The aprsd config file to use for options.',
|
help="The aprsd config file to use for options.",
|
||||||
),
|
),
|
||||||
click.option(
|
click.option(
|
||||||
'--quiet',
|
"--quiet",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Don't log to stdout",
|
help="Don't log to stdout",
|
||||||
@@ -59,7 +59,7 @@ class AliasedGroup(click.Group):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
aliases = kwargs.pop('aliases', [])
|
aliases = kwargs.pop("aliases", [])
|
||||||
cmd = click.decorators.command(*args, **kwargs)(f)
|
cmd = click.decorators.command(*args, **kwargs)(f)
|
||||||
self.add_command(cmd)
|
self.add_command(cmd)
|
||||||
for alias in aliases:
|
for alias in aliases:
|
||||||
@@ -77,7 +77,7 @@ class AliasedGroup(click.Group):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
aliases = kwargs.pop('aliases', [])
|
aliases = kwargs.pop("aliases", [])
|
||||||
cmd = click.decorators.group(*args, **kwargs)(f)
|
cmd = click.decorators.group(*args, **kwargs)(f)
|
||||||
self.add_command(cmd)
|
self.add_command(cmd)
|
||||||
for alias in aliases:
|
for alias in aliases:
|
||||||
@@ -101,37 +101,36 @@ def process_standard_options(f: F) -> F:
|
|||||||
ctx = args[0]
|
ctx = args[0]
|
||||||
ctx.ensure_object(dict)
|
ctx.ensure_object(dict)
|
||||||
config_file_found = True
|
config_file_found = True
|
||||||
if kwargs['config_file']:
|
if kwargs["config_file"]:
|
||||||
default_config_files = [kwargs['config_file']]
|
default_config_files = [kwargs["config_file"]]
|
||||||
else:
|
else:
|
||||||
default_config_files = None
|
default_config_files = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
CONF(
|
CONF(
|
||||||
[],
|
[],
|
||||||
project='aprsd',
|
project="aprsd",
|
||||||
version=aprsd.__version__,
|
version=aprsd.__version__,
|
||||||
default_config_files=default_config_files,
|
default_config_files=default_config_files,
|
||||||
)
|
)
|
||||||
except cfg.ConfigFilesNotFoundError:
|
except cfg.ConfigFilesNotFoundError:
|
||||||
config_file_found = False
|
config_file_found = False
|
||||||
ctx.obj['loglevel'] = kwargs['loglevel']
|
ctx.obj["loglevel"] = kwargs["loglevel"]
|
||||||
# ctx.obj["config_file"] = kwargs["config_file"]
|
# ctx.obj["config_file"] = kwargs["config_file"]
|
||||||
ctx.obj['quiet'] = kwargs['quiet']
|
ctx.obj["quiet"] = kwargs["quiet"]
|
||||||
log.setup_logging(
|
log.setup_logging(
|
||||||
ctx.obj['loglevel'],
|
ctx.obj["loglevel"],
|
||||||
ctx.obj['quiet'],
|
ctx.obj["quiet"],
|
||||||
)
|
)
|
||||||
if CONF.trace_enabled:
|
if CONF.trace_enabled:
|
||||||
trace.setup_tracing(['method', 'api'])
|
trace.setup_tracing(["method", "api"])
|
||||||
|
|
||||||
if not config_file_found:
|
if not config_file_found:
|
||||||
LOG = logging.getLogger('APRSD') # noqa: N806
|
LOG = logging.getLogger("APRSD") # noqa: N806
|
||||||
LOG.error("No config file found!! run 'aprsd sample-config'")
|
LOG.error("No config file found!! run 'aprsd sample-config'")
|
||||||
|
|
||||||
del kwargs['loglevel']
|
del kwargs["loglevel"]
|
||||||
del kwargs['config_file']
|
del kwargs["config_file"]
|
||||||
del kwargs['quiet']
|
del kwargs["quiet"]
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
return update_wrapper(t.cast(F, new_func), f)
|
return update_wrapper(t.cast(F, new_func), f)
|
||||||
@@ -143,17 +142,17 @@ def process_standard_options_no_config(f: F) -> F:
|
|||||||
def new_func(*args, **kwargs):
|
def new_func(*args, **kwargs):
|
||||||
ctx = args[0]
|
ctx = args[0]
|
||||||
ctx.ensure_object(dict)
|
ctx.ensure_object(dict)
|
||||||
ctx.obj['loglevel'] = kwargs['loglevel']
|
ctx.obj["loglevel"] = kwargs["loglevel"]
|
||||||
ctx.obj['config_file'] = kwargs['config_file']
|
ctx.obj["config_file"] = kwargs["config_file"]
|
||||||
ctx.obj['quiet'] = kwargs['quiet']
|
ctx.obj["quiet"] = kwargs["quiet"]
|
||||||
log.setup_logging(
|
log.setup_logging(
|
||||||
ctx.obj['loglevel'],
|
ctx.obj["loglevel"],
|
||||||
ctx.obj['quiet'],
|
ctx.obj["quiet"],
|
||||||
)
|
)
|
||||||
|
|
||||||
del kwargs['loglevel']
|
del kwargs["loglevel"]
|
||||||
del kwargs['config_file']
|
del kwargs["config_file"]
|
||||||
del kwargs['quiet']
|
del kwargs["quiet"]
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
return update_wrapper(t.cast(F, new_func), f)
|
return update_wrapper(t.cast(F, new_func), f)
|
||||||
|
|||||||
+11
-14
@@ -9,7 +9,7 @@ from aprsd.packets import core
|
|||||||
from aprsd.utils import keepalive_collector
|
from aprsd.utils import keepalive_collector
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
class APRSClient:
|
class APRSClient:
|
||||||
@@ -20,8 +20,8 @@ class APRSClient:
|
|||||||
|
|
||||||
connected = False
|
connected = False
|
||||||
login_status = {
|
login_status = {
|
||||||
'success': False,
|
"success": False,
|
||||||
'message': None,
|
"message": None,
|
||||||
}
|
}
|
||||||
filter = None
|
filter = None
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
@@ -59,20 +59,17 @@ class APRSClient:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def login_success(self):
|
def login_success(self):
|
||||||
return self.login_status.get('success', False)
|
return self.login_status.get("success", False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def login_failure(self):
|
def login_failure(self):
|
||||||
return self.login_status['message']
|
return self.login_status["message"]
|
||||||
|
|
||||||
def set_filter(self, filter):
|
def set_filter(self, filter):
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
if self._client:
|
if self._client:
|
||||||
self._client.set_filter(filter)
|
self._client.set_filter(filter)
|
||||||
|
|
||||||
def get_filter(self):
|
|
||||||
return self.filter
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client(self):
|
def client(self):
|
||||||
if not self._client:
|
if not self._client:
|
||||||
@@ -83,16 +80,16 @@ class APRSClient:
|
|||||||
try:
|
try:
|
||||||
self._client = self.setup_connection()
|
self._client = self.setup_connection()
|
||||||
if self.filter:
|
if self.filter:
|
||||||
LOG.info('Creating APRS client filter')
|
LOG.info("Creating APRS client filter")
|
||||||
self._client.set_filter(self.filter)
|
self._client.set_filter(self.filter)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f'Failed to create APRS client: {e}')
|
LOG.error(f"Failed to create APRS client: {e}")
|
||||||
self._client = None
|
self._client = None
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
if self._client:
|
if self._client:
|
||||||
LOG.info('Stopping client connection.')
|
LOG.info("Stopping client connection.")
|
||||||
self._client.stop()
|
self._client.stop()
|
||||||
|
|
||||||
def send(self, packet: core.Packet) -> None:
|
def send(self, packet: core.Packet) -> None:
|
||||||
@@ -106,16 +103,16 @@ class APRSClient:
|
|||||||
@wrapt.synchronized(lock)
|
@wrapt.synchronized(lock)
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Call this to force a rebuild/reconnect."""
|
"""Call this to force a rebuild/reconnect."""
|
||||||
LOG.info('Resetting client connection.')
|
LOG.info("Resetting client connection.")
|
||||||
if self._client:
|
if self._client:
|
||||||
self._client.close()
|
self._client.close()
|
||||||
del self._client
|
del self._client
|
||||||
self._create_client()
|
self._create_client()
|
||||||
else:
|
else:
|
||||||
LOG.warning('Client not initialized, nothing to reset.')
|
LOG.warning("Client not initialized, nothing to reset.")
|
||||||
|
|
||||||
# Recreate the client
|
# Recreate the client
|
||||||
LOG.info(f'Creating new client {self.client}')
|
LOG.info(f"Creating new client {self.client}")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def setup_connection(self):
|
def setup_connection(self):
|
||||||
|
|||||||
+28
-30
@@ -3,60 +3,58 @@ import sys
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import aprslib
|
import aprslib
|
||||||
import click
|
|
||||||
from aprslib.exceptions import LoginError
|
from aprslib.exceptions import LoginError
|
||||||
|
import click
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
import aprsd.packets # noqa : F401
|
from aprsd import cli_helper, packets
|
||||||
from aprsd import (
|
from aprsd import conf # noqa : F401
|
||||||
cli_helper,
|
|
||||||
conf, # noqa : F401
|
|
||||||
packets,
|
|
||||||
)
|
|
||||||
from aprsd.client import client_factory
|
from aprsd.client import client_factory
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
|
import aprsd.packets # noqa : F401
|
||||||
from aprsd.packets import collector
|
from aprsd.packets import collector
|
||||||
from aprsd.packets import log as packet_log
|
from aprsd.packets import log as packet_log
|
||||||
from aprsd.threads import tx
|
from aprsd.threads import tx
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@cli_helper.add_options(cli_helper.common_options)
|
@cli_helper.add_options(cli_helper.common_options)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--aprs-login',
|
"--aprs-login",
|
||||||
envvar='APRS_LOGIN',
|
envvar="APRS_LOGIN",
|
||||||
show_envvar=True,
|
show_envvar=True,
|
||||||
help='What callsign to send the message from. Defaults to config entry.',
|
help="What callsign to send the message from. Defaults to config entry.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--aprs-password',
|
"--aprs-password",
|
||||||
envvar='APRS_PASSWORD',
|
envvar="APRS_PASSWORD",
|
||||||
show_envvar=True,
|
show_envvar=True,
|
||||||
help='the APRS-IS password for APRS_LOGIN. Defaults to config entry.',
|
help="the APRS-IS password for APRS_LOGIN. Defaults to config entry.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--no-ack',
|
"--no-ack",
|
||||||
'-n',
|
"-n",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Don't wait for an ack, just sent it to APRS-IS and bail.",
|
help="Don't wait for an ack, just sent it to APRS-IS and bail.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--wait-response',
|
"--wait-response",
|
||||||
'-w',
|
"-w",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=False,
|
default=False,
|
||||||
help='Wait for a response to the message?',
|
help="Wait for a response to the message?",
|
||||||
)
|
)
|
||||||
@click.option('--raw', default=None, help='Send a raw message. Implies --no-ack')
|
@click.option("--raw", default=None, help="Send a raw message. Implies --no-ack")
|
||||||
@click.argument('tocallsign', required=True)
|
@click.argument("tocallsign", required=True)
|
||||||
@click.argument('command', nargs=-1, required=True)
|
@click.argument("command", nargs=-1, required=True)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@cli_helper.process_standard_options
|
@cli_helper.process_standard_options
|
||||||
def send_message(
|
def send_message(
|
||||||
@@ -71,11 +69,11 @@ def send_message(
|
|||||||
):
|
):
|
||||||
"""Send a message to a callsign via APRS_IS."""
|
"""Send a message to a callsign via APRS_IS."""
|
||||||
global got_ack, got_response
|
global got_ack, got_response
|
||||||
quiet = ctx.obj['quiet']
|
quiet = ctx.obj["quiet"]
|
||||||
|
|
||||||
if not aprs_login:
|
if not aprs_login:
|
||||||
if CONF.aprs_network.login == conf.client.DEFAULT_LOGIN:
|
if CONF.aprs_network.login == conf.client.DEFAULT_LOGIN:
|
||||||
click.echo('Must set --aprs_login or APRS_LOGIN')
|
click.echo("Must set --aprs_login or APRS_LOGIN")
|
||||||
ctx.exit(-1)
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@@ -83,15 +81,15 @@ def send_message(
|
|||||||
|
|
||||||
if not aprs_password:
|
if not aprs_password:
|
||||||
if not CONF.aprs_network.password:
|
if not CONF.aprs_network.password:
|
||||||
click.echo('Must set --aprs-password or APRS_PASSWORD')
|
click.echo("Must set --aprs-password or APRS_PASSWORD")
|
||||||
ctx.exit(-1)
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
aprs_password = CONF.aprs_network.password
|
aprs_password = CONF.aprs_network.password
|
||||||
|
|
||||||
LOG.info(f'APRSD LISTEN Started version: {aprsd.__version__}')
|
LOG.info(f"APRSD LISTEN Started version: {aprsd.__version__}")
|
||||||
if type(command) is tuple:
|
if type(command) is tuple:
|
||||||
command = ' '.join(command)
|
command = " ".join(command)
|
||||||
if not quiet:
|
if not quiet:
|
||||||
if raw:
|
if raw:
|
||||||
LOG.info(f"L'{aprs_login}' R'{raw}'")
|
LOG.info(f"L'{aprs_login}' R'{raw}'")
|
||||||
@@ -131,7 +129,7 @@ def send_message(
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client_factory.create().client # noqa: B018
|
client_factory.create().client
|
||||||
except LoginError:
|
except LoginError:
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
@@ -142,7 +140,7 @@ def send_message(
|
|||||||
# message
|
# message
|
||||||
if raw:
|
if raw:
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.Packet(from_call='', to_call='', raw=raw),
|
packets.Packet(from_call="", to_call="", raw=raw),
|
||||||
direct=True,
|
direct=True,
|
||||||
)
|
)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -166,7 +164,7 @@ def send_message(
|
|||||||
aprs_client = client_factory.create().client
|
aprs_client = client_factory.create().client
|
||||||
aprs_client.consumer(rx_packet, raw=False)
|
aprs_client.consumer(rx_packet, raw=False)
|
||||||
except aprslib.exceptions.ConnectionDrop:
|
except aprslib.exceptions.ConnectionDrop:
|
||||||
LOG.error('Connection dropped, reconnecting')
|
LOG.error("Connection dropped, reconnecting")
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
# Force the deletion of the client object connected to aprs
|
# Force the deletion of the client object connected to aprs
|
||||||
# This will cause a reconnect, next time client.get_client()
|
# This will cause a reconnect, next time client.get_client()
|
||||||
|
|||||||
+63
-28
@@ -12,24 +12,59 @@ from aprsd.client import client_factory
|
|||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.packets import collector as packet_collector
|
from aprsd.packets import collector as packet_collector
|
||||||
from aprsd.packets import seen_list
|
from aprsd.packets import seen_list
|
||||||
from aprsd.threads import keepalive, registry, rx, service, tx
|
from aprsd.threads import aprsd as aprsd_threads
|
||||||
|
from aprsd.threads import keepalive, registry, rx, tx
|
||||||
from aprsd.threads import stats as stats_thread
|
from aprsd.threads import stats as stats_thread
|
||||||
|
from aprsd.utils import singleton
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class ServerThreads:
|
||||||
|
"""Registry for threads that the server command runs.
|
||||||
|
|
||||||
|
This enables extensions to register a thread to run during
|
||||||
|
the server command.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.threads: list[aprsd_threads.APRSDThread] = []
|
||||||
|
|
||||||
|
def register(self, thread: aprsd_threads.APRSDThread):
|
||||||
|
if not isinstance(thread, aprsd_threads.APRSDThread):
|
||||||
|
raise TypeError(f"Thread {thread} is not an APRSDThread")
|
||||||
|
self.threads.append(thread)
|
||||||
|
|
||||||
|
def unregister(self, thread: aprsd_threads.APRSDThread):
|
||||||
|
if not isinstance(thread, aprsd_threads.APRSDThread):
|
||||||
|
raise TypeError(f"Thread {thread} is not an APRSDThread")
|
||||||
|
self.threads.remove(thread)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start all threads in the list."""
|
||||||
|
for thread in self.threads:
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def join(self):
|
||||||
|
"""Join all the threads in the list"""
|
||||||
|
for thread in self.threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
|
||||||
# main() ###
|
# main() ###
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@cli_helper.add_options(cli_helper.common_options)
|
@cli_helper.add_options(cli_helper.common_options)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-f',
|
"-f",
|
||||||
'--flush',
|
"--flush",
|
||||||
'flush',
|
"flush",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=False,
|
default=False,
|
||||||
help='Flush out all old aged messages on disk.',
|
help="Flush out all old aged messages on disk.",
|
||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@cli_helper.process_standard_options
|
@cli_helper.process_standard_options
|
||||||
@@ -38,37 +73,37 @@ def server(ctx, flush):
|
|||||||
signal.signal(signal.SIGINT, aprsd_main.signal_handler)
|
signal.signal(signal.SIGINT, aprsd_main.signal_handler)
|
||||||
signal.signal(signal.SIGTERM, aprsd_main.signal_handler)
|
signal.signal(signal.SIGTERM, aprsd_main.signal_handler)
|
||||||
|
|
||||||
service_threads = service.ServiceThreads()
|
server_threads = ServerThreads()
|
||||||
|
|
||||||
level, msg = utils._check_version()
|
level, msg = utils._check_version()
|
||||||
if level:
|
if level:
|
||||||
LOG.warning(msg)
|
LOG.warning(msg)
|
||||||
else:
|
else:
|
||||||
LOG.info(msg)
|
LOG.info(msg)
|
||||||
LOG.info(f'APRSD Started version: {aprsd.__version__}')
|
LOG.info(f"APRSD Started version: {aprsd.__version__}")
|
||||||
|
|
||||||
# Initialize the client factory and create
|
# Initialize the client factory and create
|
||||||
# The correct client object ready for use
|
# The correct client object ready for use
|
||||||
if not client_factory.is_client_enabled():
|
if not client_factory.is_client_enabled():
|
||||||
LOG.error('No Clients are enabled in config.')
|
LOG.error("No Clients are enabled in config.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Make sure we have 1 client transport enabled
|
# Make sure we have 1 client transport enabled
|
||||||
if not client_factory.is_client_enabled():
|
if not client_factory.is_client_enabled():
|
||||||
LOG.error('No Clients are enabled in config.')
|
LOG.error("No Clients are enabled in config.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
if not client_factory.is_client_configured():
|
if not client_factory.is_client_configured():
|
||||||
LOG.error('APRS client is not properly configured in config file.')
|
LOG.error("APRS client is not properly configured in config file.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Creates the client object
|
# Creates the client object
|
||||||
LOG.info('Creating client connection')
|
LOG.info("Creating client connection")
|
||||||
aprs_client = client_factory.create()
|
aprs_client = client_factory.create()
|
||||||
LOG.info(aprs_client)
|
LOG.info(aprs_client)
|
||||||
if not aprs_client.login_success:
|
if not aprs_client.login_success:
|
||||||
# We failed to login, will just quit!
|
# We failed to login, will just quit!
|
||||||
msg = f'Login Failure: {aprs_client.login_failure}'
|
msg = f"Login Failure: {aprs_client.login_failure}"
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
print(msg)
|
print(msg)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
@@ -79,7 +114,7 @@ def server(ctx, flush):
|
|||||||
# We register plugins first here so we can register each
|
# We register plugins first here so we can register each
|
||||||
# plugins config options, so we can dump them all in the
|
# plugins config options, so we can dump them all in the
|
||||||
# log file output.
|
# log file output.
|
||||||
LOG.info('Loading Plugin Manager and registering plugins')
|
LOG.info("Loading Plugin Manager and registering plugins")
|
||||||
plugin_manager = plugin.PluginManager()
|
plugin_manager = plugin.PluginManager()
|
||||||
plugin_manager.setup_plugins(load_help_plugin=CONF.load_help_plugin)
|
plugin_manager.setup_plugins(load_help_plugin=CONF.load_help_plugin)
|
||||||
|
|
||||||
@@ -87,10 +122,10 @@ def server(ctx, flush):
|
|||||||
CONF.log_opt_values(LOG, logging.DEBUG)
|
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||||
message_plugins = plugin_manager.get_message_plugins()
|
message_plugins = plugin_manager.get_message_plugins()
|
||||||
watchlist_plugins = plugin_manager.get_watchlist_plugins()
|
watchlist_plugins = plugin_manager.get_watchlist_plugins()
|
||||||
LOG.info('Message Plugins enabled and running:')
|
LOG.info("Message Plugins enabled and running:")
|
||||||
for p in message_plugins:
|
for p in message_plugins:
|
||||||
LOG.info(p)
|
LOG.info(p)
|
||||||
LOG.info('Watchlist Plugins enabled and running:')
|
LOG.info("Watchlist Plugins enabled and running:")
|
||||||
for p in watchlist_plugins:
|
for p in watchlist_plugins:
|
||||||
LOG.info(p)
|
LOG.info(p)
|
||||||
|
|
||||||
@@ -100,37 +135,37 @@ def server(ctx, flush):
|
|||||||
|
|
||||||
# Now load the msgTrack from disk if any
|
# Now load the msgTrack from disk if any
|
||||||
if flush:
|
if flush:
|
||||||
LOG.debug('Flushing All packet tracking objects.')
|
LOG.debug("Flushing All packet tracking objects.")
|
||||||
packet_collector.PacketCollector().flush()
|
packet_collector.PacketCollector().flush()
|
||||||
else:
|
else:
|
||||||
# Try and load saved MsgTrack list
|
# Try and load saved MsgTrack list
|
||||||
LOG.debug('Loading saved packet tracking data.')
|
LOG.debug("Loading saved packet tracking data.")
|
||||||
packet_collector.PacketCollector().load()
|
packet_collector.PacketCollector().load()
|
||||||
|
|
||||||
# Now start all the main processing threads.
|
# Now start all the main processing threads.
|
||||||
|
|
||||||
service_threads.register(keepalive.KeepAliveThread())
|
server_threads.register(keepalive.KeepAliveThread())
|
||||||
service_threads.register(stats_thread.APRSDStatsStoreThread())
|
server_threads.register(stats_thread.APRSDStatsStoreThread())
|
||||||
service_threads.register(
|
server_threads.register(
|
||||||
rx.APRSDRXThread(
|
rx.APRSDRXThread(
|
||||||
packet_queue=threads.packet_queue,
|
packet_queue=threads.packet_queue,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
service_threads.register(
|
server_threads.register(
|
||||||
rx.APRSDPluginProcessPacketThread(
|
rx.APRSDPluginProcessPacketThread(
|
||||||
packet_queue=threads.packet_queue,
|
packet_queue=threads.packet_queue,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if CONF.enable_beacon:
|
if CONF.enable_beacon:
|
||||||
LOG.info('Beacon Enabled. Starting Beacon thread.')
|
LOG.info("Beacon Enabled. Starting Beacon thread.")
|
||||||
service_threads.register(tx.BeaconSendThread())
|
server_threads.register(tx.BeaconSendThread())
|
||||||
|
|
||||||
if CONF.aprs_registry.enabled:
|
if CONF.aprs_registry.enabled:
|
||||||
LOG.info('Registry Enabled. Starting Registry thread.')
|
LOG.info("Registry Enabled. Starting Registry thread.")
|
||||||
service_threads.register(registry.APRSRegistryThread())
|
server_threads.register(registry.APRSRegistryThread())
|
||||||
|
|
||||||
service_threads.start()
|
server_threads.start()
|
||||||
service_threads.join()
|
server_threads.join()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -54,11 +54,6 @@ logging_opts = [
|
|||||||
default=True,
|
default=True,
|
||||||
help='Enable ANSI color codes in logging',
|
help='Enable ANSI color codes in logging',
|
||||||
),
|
),
|
||||||
cfg.BoolOpt(
|
|
||||||
'enable_console_stdout',
|
|
||||||
default=True,
|
|
||||||
help='Enable logging to the console/stdout.',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+10
-16
@@ -51,7 +51,7 @@ class InterceptHandler(logging.Handler):
|
|||||||
# Setup the log faciility
|
# Setup the log faciility
|
||||||
# to disable log to stdout, but still log to file
|
# to disable log to stdout, but still log to file
|
||||||
# use the --quiet option on the cmdln
|
# use the --quiet option on the cmdln
|
||||||
def setup_logging(loglevel=None, quiet=False, custom_handler=None):
|
def setup_logging(loglevel=None, quiet=False):
|
||||||
if not loglevel:
|
if not loglevel:
|
||||||
log_level = CONF.logging.log_level
|
log_level = CONF.logging.log_level
|
||||||
else:
|
else:
|
||||||
@@ -84,18 +84,15 @@ def setup_logging(loglevel=None, quiet=False, custom_handler=None):
|
|||||||
logging.getLogger(name).handlers = []
|
logging.getLogger(name).handlers = []
|
||||||
logging.getLogger(name).propagate = name not in disable_list
|
logging.getLogger(name).propagate = name not in disable_list
|
||||||
|
|
||||||
handlers = []
|
handlers = [
|
||||||
if CONF.logging.enable_console_stdout:
|
{
|
||||||
handlers.append(
|
'sink': sys.stdout,
|
||||||
{
|
'serialize': False,
|
||||||
'sink': sys.stdout,
|
'format': CONF.logging.logformat,
|
||||||
'serialize': False,
|
'colorize': CONF.logging.enable_color,
|
||||||
'format': CONF.logging.logformat,
|
'level': log_level,
|
||||||
'colorize': CONF.logging.enable_color,
|
},
|
||||||
'level': log_level,
|
]
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if CONF.logging.logfile:
|
if CONF.logging.logfile:
|
||||||
handlers.append(
|
handlers.append(
|
||||||
{
|
{
|
||||||
@@ -107,9 +104,6 @@ def setup_logging(loglevel=None, quiet=False, custom_handler=None):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if custom_handler:
|
|
||||||
handlers.append(custom_handler)
|
|
||||||
|
|
||||||
# configure loguru
|
# configure loguru
|
||||||
logger.configure(handlers=handlers)
|
logger.configure(handlers=handlers)
|
||||||
logger.level('DEBUG', color='<fg #BABABA>')
|
logger.level('DEBUG', color='<fg #BABABA>')
|
||||||
|
|||||||
+22
-22
@@ -39,8 +39,8 @@ from aprsd.stats import collector
|
|||||||
# setup the global logger
|
# setup the global logger
|
||||||
# log.basicConfig(level=log.DEBUG) # level=10
|
# log.basicConfig(level=log.DEBUG) # level=10
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger("APRSD")
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||||
flask_enabled = False
|
flask_enabled = False
|
||||||
|
|
||||||
|
|
||||||
@@ -68,18 +68,18 @@ def main():
|
|||||||
# First import all the possible commands for the CLI
|
# First import all the possible commands for the CLI
|
||||||
# The commands themselves live in the cmds directory
|
# The commands themselves live in the cmds directory
|
||||||
load_commands()
|
load_commands()
|
||||||
utils.load_entry_points('aprsd.extension')
|
utils.load_entry_points("aprsd.extension")
|
||||||
cli(auto_envvar_prefix='APRSD')
|
cli(auto_envvar_prefix="APRSD")
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
global flask_enabled
|
global flask_enabled
|
||||||
|
|
||||||
click.echo('signal_handler: called')
|
click.echo("signal_handler: called")
|
||||||
threads.APRSDThreadList().stop_all()
|
threads.APRSDThreadList().stop_all()
|
||||||
if 'subprocess' not in str(frame):
|
if "subprocess" not in str(frame):
|
||||||
LOG.info(
|
LOG.info(
|
||||||
'Ctrl+C, Sending all threads exit! Can take up to 10 seconds {}'.format(
|
"Ctrl+C, Sending all threads exit! Can take up to 10 seconds {}".format(
|
||||||
datetime.datetime.now(),
|
datetime.datetime.now(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -91,7 +91,7 @@ def signal_handler(sig, frame):
|
|||||||
packets.PacketList().save()
|
packets.PacketList().save()
|
||||||
collector.Collector().collect()
|
collector.Collector().collect()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f'Failed to save data: {e}')
|
LOG.error(f"Failed to save data: {e}")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
# signal.signal(signal.SIGTERM, sys.exit(0))
|
# signal.signal(signal.SIGTERM, sys.exit(0))
|
||||||
# sys.exit(0)
|
# sys.exit(0)
|
||||||
@@ -108,9 +108,9 @@ def check_version(ctx):
|
|||||||
"""Check this version against the latest in pypi.org."""
|
"""Check this version against the latest in pypi.org."""
|
||||||
level, msg = utils._check_version()
|
level, msg = utils._check_version()
|
||||||
if level:
|
if level:
|
||||||
click.secho(msg, fg='yellow')
|
click.secho(msg, fg="yellow")
|
||||||
else:
|
else:
|
||||||
click.secho(msg, fg='green')
|
click.secho(msg, fg="green")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@@ -124,12 +124,12 @@ def sample_config(ctx):
|
|||||||
if sys.version_info < (3, 10):
|
if sys.version_info < (3, 10):
|
||||||
all = imp.entry_points()
|
all = imp.entry_points()
|
||||||
selected = []
|
selected = []
|
||||||
if 'oslo.config.opts' in all:
|
if "oslo.config.opts" in all:
|
||||||
for x in all['oslo.config.opts']:
|
for x in all["oslo.config.opts"]:
|
||||||
if x.group == 'oslo.config.opts':
|
if x.group == "oslo.config.opts":
|
||||||
selected.append(x)
|
selected.append(x)
|
||||||
else:
|
else:
|
||||||
selected = imp.entry_points(group='oslo.config.opts')
|
selected = imp.entry_points(group="oslo.config.opts")
|
||||||
|
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
@@ -139,23 +139,23 @@ def sample_config(ctx):
|
|||||||
# selected = imp.entry_points(group="oslo.config.opts")
|
# selected = imp.entry_points(group="oslo.config.opts")
|
||||||
selected = _get_selected_entry_points()
|
selected = _get_selected_entry_points()
|
||||||
for entry in selected:
|
for entry in selected:
|
||||||
if 'aprsd' in entry.name:
|
if "aprsd" in entry.name:
|
||||||
args.append('--namespace')
|
args.append("--namespace")
|
||||||
args.append(entry.name)
|
args.append(entry.name)
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
args = get_namespaces()
|
args = get_namespaces()
|
||||||
config_version = metadata_version('oslo.config')
|
config_version = metadata_version("oslo.config")
|
||||||
logging.basicConfig(level=logging.WARN)
|
logging.basicConfig(level=logging.WARN)
|
||||||
conf = cfg.ConfigOpts()
|
conf = cfg.ConfigOpts()
|
||||||
generator.register_cli_opts(conf)
|
generator.register_cli_opts(conf)
|
||||||
try:
|
try:
|
||||||
conf(args, version=config_version)
|
conf(args, version=config_version)
|
||||||
except cfg.RequiredOptError as ex:
|
except cfg.RequiredOptError:
|
||||||
conf.print_help()
|
conf.print_help()
|
||||||
if not sys.argv[1:]:
|
if not sys.argv[1:]:
|
||||||
raise SystemExit from ex
|
raise SystemExit
|
||||||
raise
|
raise
|
||||||
generator.generate(conf)
|
generator.generate(conf)
|
||||||
return
|
return
|
||||||
@@ -165,9 +165,9 @@ def sample_config(ctx):
|
|||||||
@click.pass_context
|
@click.pass_context
|
||||||
def version(ctx):
|
def version(ctx):
|
||||||
"""Show the APRSD version."""
|
"""Show the APRSD version."""
|
||||||
click.echo(click.style('APRSD Version : ', fg='white'), nl=False)
|
click.echo(click.style("APRSD Version : ", fg="white"), nl=False)
|
||||||
click.secho(f'{aprsd.__version__}', fg='yellow', bold=True)
|
click.secho(f"{aprsd.__version__}", fg="yellow", bold=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
+52
-52
@@ -17,24 +17,24 @@ from aprsd.packets import watch_list
|
|||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
CORE_MESSAGE_PLUGINS = [
|
CORE_MESSAGE_PLUGINS = [
|
||||||
'aprsd.plugins.email.EmailPlugin',
|
"aprsd.plugins.email.EmailPlugin",
|
||||||
'aprsd.plugins.fortune.FortunePlugin',
|
"aprsd.plugins.fortune.FortunePlugin",
|
||||||
'aprsd.plugins.location.LocationPlugin',
|
"aprsd.plugins.location.LocationPlugin",
|
||||||
'aprsd.plugins.ping.PingPlugin',
|
"aprsd.plugins.ping.PingPlugin",
|
||||||
'aprsd.plugins.time.TimePlugin',
|
"aprsd.plugins.time.TimePlugin",
|
||||||
'aprsd.plugins.weather.USWeatherPlugin',
|
"aprsd.plugins.weather.USWeatherPlugin",
|
||||||
'aprsd.plugins.version.VersionPlugin',
|
"aprsd.plugins.version.VersionPlugin",
|
||||||
]
|
]
|
||||||
|
|
||||||
CORE_NOTIFY_PLUGINS = [
|
CORE_NOTIFY_PLUGINS = [
|
||||||
'aprsd.plugins.notify.NotifySeenPlugin',
|
"aprsd.plugins.notify.NotifySeenPlugin",
|
||||||
]
|
]
|
||||||
|
|
||||||
hookspec = pluggy.HookspecMarker('aprsd')
|
hookspec = pluggy.HookspecMarker("aprsd")
|
||||||
hookimpl = pluggy.HookimplMarker('aprsd')
|
hookimpl = pluggy.HookimplMarker("aprsd")
|
||||||
|
|
||||||
|
|
||||||
class APRSDPluginSpec:
|
class APRSDPluginSpec:
|
||||||
@@ -76,14 +76,14 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
|
|||||||
else:
|
else:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Can't start thread {}:{}, Must be a child "
|
"Can't start thread {}:{}, Must be a child "
|
||||||
'of aprsd.threads.APRSDThread'.format(
|
"of aprsd.threads.APRSDThread".format(
|
||||||
self,
|
self,
|
||||||
thread,
|
thread,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
'Failed to start threads for plugin {}'.format(
|
"Failed to start threads for plugin {}".format(
|
||||||
self,
|
self,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -93,7 +93,7 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
|
|||||||
return self.message_counter
|
return self.message_counter
|
||||||
|
|
||||||
def help(self) -> str:
|
def help(self) -> str:
|
||||||
return 'Help!'
|
return "Help!"
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@@ -147,10 +147,10 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
# make sure the timeout is set or this doesn't work
|
# make sure the timeout is set or this doesn't work
|
||||||
if watch_list:
|
if watch_list:
|
||||||
aprs_client = client.client_factory.create().client
|
aprs_client = client.client_factory.create().client
|
||||||
filter_str = 'b/{}'.format('/'.join(watch_list))
|
filter_str = "b/{}".format("/".join(watch_list))
|
||||||
aprs_client.set_filter(filter_str)
|
aprs_client.set_filter(filter_str)
|
||||||
else:
|
else:
|
||||||
LOG.warning('Watch list enabled, but no callsigns set.')
|
LOG.warning("Watch list enabled, but no callsigns set.")
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def filter(self, packet: type[packets.Packet]) -> str | packets.MessagePacket:
|
def filter(self, packet: type[packets.Packet]) -> str | packets.MessagePacket:
|
||||||
@@ -164,7 +164,7 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
result = self.process(packet)
|
result = self.process(packet)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
'Plugin {} failed to process packet {}'.format(
|
"Plugin {} failed to process packet {}".format(
|
||||||
self.__class__,
|
self.__class__,
|
||||||
ex,
|
ex,
|
||||||
),
|
),
|
||||||
@@ -172,7 +172,7 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
if result:
|
if result:
|
||||||
self.tx_inc()
|
self.tx_inc()
|
||||||
else:
|
else:
|
||||||
LOG.warning(f'{self.__class__} plugin is not enabled')
|
LOG.warning(f"{self.__class__} plugin is not enabled")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def help(self):
|
def help(self):
|
||||||
return '{}: {}'.format(
|
return "{}: {}".format(
|
||||||
self.command_name.lower(),
|
self.command_name.lower(),
|
||||||
self.command_regex,
|
self.command_regex,
|
||||||
)
|
)
|
||||||
@@ -207,7 +207,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def filter(self, packet: packets.MessagePacket) -> str | packets.MessagePacket:
|
def filter(self, packet: packets.MessagePacket) -> str | packets.MessagePacket:
|
||||||
LOG.debug(f'{self.__class__.__name__} called')
|
LOG.debug(f"{self.__class__.__name__} called")
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
result = f"{self.__class__.__name__} isn't enabled"
|
result = f"{self.__class__.__name__} isn't enabled"
|
||||||
LOG.warning(result)
|
LOG.warning(result)
|
||||||
@@ -215,7 +215,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
if not isinstance(packet, packets.MessagePacket):
|
if not isinstance(packet, packets.MessagePacket):
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
f'{self.__class__.__name__} Got a {packet.__class__.__name__} ignoring'
|
f"{self.__class__.__name__} Got a {packet.__class__.__name__} ignoring"
|
||||||
)
|
)
|
||||||
return packets.NULL_MESSAGE
|
return packets.NULL_MESSAGE
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
result = self.process(packet)
|
result = self.process(packet)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
'Plugin {} failed to process packet {}'.format(
|
"Plugin {} failed to process packet {}".format(
|
||||||
self.__class__,
|
self.__class__,
|
||||||
ex,
|
ex,
|
||||||
),
|
),
|
||||||
@@ -254,7 +254,7 @@ class APRSFIKEYMixin:
|
|||||||
|
|
||||||
def ensure_aprs_fi_key(self):
|
def ensure_aprs_fi_key(self):
|
||||||
if not CONF.aprs_fi.apiKey:
|
if not CONF.aprs_fi.apiKey:
|
||||||
LOG.error('Config aprs_fi.apiKey is not set')
|
LOG.error("Config aprs_fi.apiKey is not set")
|
||||||
self.enabled = False
|
self.enabled = False
|
||||||
else:
|
else:
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
@@ -266,25 +266,25 @@ class HelpPlugin(APRSDRegexCommandPluginBase):
|
|||||||
This plugin is in this file to prevent a circular import.
|
This plugin is in this file to prevent a circular import.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
command_regex = '^[hH]'
|
command_regex = "^[hH]"
|
||||||
command_name = 'help'
|
command_name = "help"
|
||||||
|
|
||||||
def help(self):
|
def help(self):
|
||||||
return 'Help: send APRS help or help <plugin>'
|
return "Help: send APRS help or help <plugin>"
|
||||||
|
|
||||||
def process(self, packet: packets.MessagePacket):
|
def process(self, packet: packets.MessagePacket):
|
||||||
LOG.info('HelpPlugin')
|
LOG.info("HelpPlugin")
|
||||||
# fromcall = packet.get("from")
|
# fromcall = packet.get("from")
|
||||||
message = packet.message_text
|
message = packet.message_text
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
a = re.search(r'^.*\s+(.*)', message)
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
command_name = None
|
command_name = None
|
||||||
if a is not None:
|
if a is not None:
|
||||||
command_name = a.group(1).lower()
|
command_name = a.group(1).lower()
|
||||||
|
|
||||||
pm = PluginManager()
|
pm = PluginManager()
|
||||||
|
|
||||||
if command_name and '?' not in command_name:
|
if command_name and "?" not in command_name:
|
||||||
# user wants help for a specific plugin
|
# user wants help for a specific plugin
|
||||||
reply = None
|
reply = None
|
||||||
for p in pm.get_plugins():
|
for p in pm.get_plugins():
|
||||||
@@ -303,20 +303,20 @@ class HelpPlugin(APRSDRegexCommandPluginBase):
|
|||||||
LOG.debug(p)
|
LOG.debug(p)
|
||||||
if p.enabled and isinstance(p, APRSDRegexCommandPluginBase):
|
if p.enabled and isinstance(p, APRSDRegexCommandPluginBase):
|
||||||
name = p.command_name.lower()
|
name = p.command_name.lower()
|
||||||
if name not in list and 'help' not in name:
|
if name not in list and "help" not in name:
|
||||||
list.append(name)
|
list.append(name)
|
||||||
|
|
||||||
list.sort()
|
list.sort()
|
||||||
reply = ' '.join(list)
|
reply = " ".join(list)
|
||||||
lines = textwrap.wrap(reply, 60)
|
lines = textwrap.wrap(reply, 60)
|
||||||
replies = ["Send APRS MSG of 'help' or 'help <plugin>'"]
|
replies = ["Send APRS MSG of 'help' or 'help <plugin>'"]
|
||||||
for line in lines:
|
for line in lines:
|
||||||
replies.append(f'plugins: {line}')
|
replies.append(f"plugins: {line}")
|
||||||
|
|
||||||
for entry in replies:
|
for entry in replies:
|
||||||
LOG.debug(f'{len(entry)} {entry}')
|
LOG.debug(f"{len(entry)} {entry}")
|
||||||
|
|
||||||
LOG.debug(f'{replies}')
|
LOG.debug(f"{replies}")
|
||||||
return replies
|
return replies
|
||||||
|
|
||||||
|
|
||||||
@@ -341,17 +341,17 @@ class PluginManager:
|
|||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def _init(self):
|
def _init(self):
|
||||||
self._pluggy_pm = pluggy.PluginManager('aprsd')
|
self._pluggy_pm = pluggy.PluginManager("aprsd")
|
||||||
self._pluggy_pm.add_hookspecs(APRSDPluginSpec)
|
self._pluggy_pm.add_hookspecs(APRSDPluginSpec)
|
||||||
# For the watchlist plugins
|
# For the watchlist plugins
|
||||||
self._watchlist_pm = pluggy.PluginManager('aprsd')
|
self._watchlist_pm = pluggy.PluginManager("aprsd")
|
||||||
self._watchlist_pm.add_hookspecs(APRSDPluginSpec)
|
self._watchlist_pm.add_hookspecs(APRSDPluginSpec)
|
||||||
|
|
||||||
def stats(self, serializable=False) -> dict:
|
def stats(self, serializable=False) -> dict:
|
||||||
"""Collect and return stats for all plugins."""
|
"""Collect and return stats for all plugins."""
|
||||||
|
|
||||||
def full_name_with_qualname(obj):
|
def full_name_with_qualname(obj):
|
||||||
return '{}.{}'.format(
|
return "{}.{}".format(
|
||||||
obj.__class__.__module__,
|
obj.__class__.__module__,
|
||||||
obj.__class__.__qualname__,
|
obj.__class__.__qualname__,
|
||||||
)
|
)
|
||||||
@@ -361,10 +361,10 @@ class PluginManager:
|
|||||||
if plugins:
|
if plugins:
|
||||||
for p in plugins:
|
for p in plugins:
|
||||||
plugin_stats[full_name_with_qualname(p)] = {
|
plugin_stats[full_name_with_qualname(p)] = {
|
||||||
'enabled': p.enabled,
|
"enabled": p.enabled,
|
||||||
'rx': p.rx_count,
|
"rx": p.rx_count,
|
||||||
'tx': p.tx_count,
|
"tx": p.tx_count,
|
||||||
'version': p.version,
|
"version": p.version,
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin_stats
|
return plugin_stats
|
||||||
@@ -392,19 +392,19 @@ class PluginManager:
|
|||||||
module_name = None
|
module_name = None
|
||||||
class_name = None
|
class_name = None
|
||||||
try:
|
try:
|
||||||
module_name, class_name = module_class_string.rsplit('.', 1)
|
module_name, class_name = module_class_string.rsplit(".", 1)
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
# Commented out because the email thread starts in a different context
|
# Commented out because the email thread starts in a different context
|
||||||
# and hence gives a different singleton for the EmailStats
|
# and hence gives a different singleton for the EmailStats
|
||||||
# module = importlib.reload(module)
|
# module = importlib.reload(module)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if not module_name:
|
if not module_name:
|
||||||
LOG.error(f'Failed to load Plugin {module_class_string}')
|
LOG.error(f"Failed to load Plugin {module_class_string}")
|
||||||
else:
|
else:
|
||||||
LOG.error(f"Failed to load Plugin '{module_name}' : '{ex}'")
|
LOG.error(f"Failed to load Plugin '{module_name}' : '{ex}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
assert hasattr(module, class_name), 'class {} is not in {}'.format(
|
assert hasattr(module, class_name), "class {} is not in {}".format(
|
||||||
class_name,
|
class_name,
|
||||||
module_name,
|
module_name,
|
||||||
)
|
)
|
||||||
@@ -412,7 +412,7 @@ class PluginManager:
|
|||||||
# class_name, module_name))
|
# class_name, module_name))
|
||||||
cls = getattr(module, class_name)
|
cls = getattr(module, class_name)
|
||||||
if super_cls is not None:
|
if super_cls is not None:
|
||||||
assert issubclass(cls, super_cls), 'class {} should inherit from {}'.format(
|
assert issubclass(cls, super_cls), "class {} should inherit from {}".format(
|
||||||
class_name,
|
class_name,
|
||||||
super_cls.__name__,
|
super_cls.__name__,
|
||||||
)
|
)
|
||||||
@@ -444,7 +444,7 @@ class PluginManager:
|
|||||||
self._watchlist_pm.register(plugin_obj)
|
self._watchlist_pm.register(plugin_obj)
|
||||||
else:
|
else:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
f'Plugin {plugin_obj.__class__.__name__} is disabled'
|
f"Plugin {plugin_obj.__class__.__name__} is disabled"
|
||||||
)
|
)
|
||||||
elif isinstance(plugin_obj, APRSDRegexCommandPluginBase):
|
elif isinstance(plugin_obj, APRSDRegexCommandPluginBase):
|
||||||
if plugin_obj.enabled:
|
if plugin_obj.enabled:
|
||||||
@@ -458,7 +458,7 @@ class PluginManager:
|
|||||||
self._pluggy_pm.register(plugin_obj)
|
self._pluggy_pm.register(plugin_obj)
|
||||||
else:
|
else:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
f'Plugin {plugin_obj.__class__.__name__} is disabled'
|
f"Plugin {plugin_obj.__class__.__name__} is disabled"
|
||||||
)
|
)
|
||||||
elif isinstance(plugin_obj, APRSDPluginBase):
|
elif isinstance(plugin_obj, APRSDPluginBase):
|
||||||
if plugin_obj.enabled:
|
if plugin_obj.enabled:
|
||||||
@@ -471,7 +471,7 @@ class PluginManager:
|
|||||||
self._pluggy_pm.register(plugin_obj)
|
self._pluggy_pm.register(plugin_obj)
|
||||||
else:
|
else:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
f'Plugin {plugin_obj.__class__.__name__} is disabled'
|
f"Plugin {plugin_obj.__class__.__name__} is disabled"
|
||||||
)
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Couldn't load plugin '{plugin_name}'")
|
LOG.error(f"Couldn't load plugin '{plugin_name}'")
|
||||||
@@ -485,11 +485,11 @@ class PluginManager:
|
|||||||
def setup_plugins(
|
def setup_plugins(
|
||||||
self,
|
self,
|
||||||
load_help_plugin=True,
|
load_help_plugin=True,
|
||||||
plugin_list=None,
|
plugin_list=[],
|
||||||
):
|
):
|
||||||
"""Create the plugin manager and register plugins."""
|
"""Create the plugin manager and register plugins."""
|
||||||
|
|
||||||
LOG.info('Loading APRSD Plugins')
|
LOG.info("Loading APRSD Plugins")
|
||||||
# Help plugin is always enabled.
|
# Help plugin is always enabled.
|
||||||
if load_help_plugin:
|
if load_help_plugin:
|
||||||
_help = HelpPlugin()
|
_help = HelpPlugin()
|
||||||
@@ -509,7 +509,7 @@ class PluginManager:
|
|||||||
for p_name in CORE_MESSAGE_PLUGINS:
|
for p_name in CORE_MESSAGE_PLUGINS:
|
||||||
self._load_plugin(p_name)
|
self._load_plugin(p_name)
|
||||||
|
|
||||||
LOG.info('Completed Plugin Loading.')
|
LOG.info("Completed Plugin Loading.")
|
||||||
|
|
||||||
def run(self, packet: packets.MessagePacket):
|
def run(self, packet: packets.MessagePacket):
|
||||||
"""Execute all the plugins run method."""
|
"""Execute all the plugins run method."""
|
||||||
@@ -524,7 +524,7 @@ class PluginManager:
|
|||||||
"""Stop all threads created by all plugins."""
|
"""Stop all threads created by all plugins."""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
for p in self.get_plugins():
|
for p in self.get_plugins():
|
||||||
if hasattr(p, 'stop_threads'):
|
if hasattr(p, "stop_threads"):
|
||||||
p.stop_threads()
|
p.stop_threads()
|
||||||
|
|
||||||
def register_msg(self, obj):
|
def register_msg(self, obj):
|
||||||
|
|||||||
+21
-20
@@ -4,20 +4,21 @@ import logging
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
LOG = logging.getLogger('APRSD')
|
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
def get_aprs_fi(api_key, callsign):
|
def get_aprs_fi(api_key, callsign):
|
||||||
LOG.debug(f"Fetch aprs.fi location for '{callsign}'")
|
LOG.debug(f"Fetch aprs.fi location for '{callsign}'")
|
||||||
try:
|
try:
|
||||||
url = (
|
url = (
|
||||||
'http://api.aprs.fi/api/get?&what=loc&apikey={}&format=json&name={}'.format(
|
"http://api.aprs.fi/api/get?"
|
||||||
api_key, callsign
|
"&what=loc&apikey={}&format=json"
|
||||||
)
|
"&name={}".format(api_key, callsign)
|
||||||
)
|
)
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
raise Exception('Failed to get aprs.fi location') from e
|
raise Exception("Failed to get aprs.fi location")
|
||||||
else:
|
else:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.loads(response.text)
|
return json.loads(response.text)
|
||||||
@@ -25,22 +26,22 @@ def get_aprs_fi(api_key, callsign):
|
|||||||
|
|
||||||
def get_weather_gov_for_gps(lat, lon):
|
def get_weather_gov_for_gps(lat, lon):
|
||||||
# FIXME(hemna) This is currently BROKEN
|
# FIXME(hemna) This is currently BROKEN
|
||||||
LOG.debug(f'Fetch station at {lat}, {lon}')
|
LOG.debug(f"Fetch station at {lat}, {lon}")
|
||||||
headers = requests.utils.default_headers()
|
headers = requests.utils.default_headers()
|
||||||
headers.update(
|
headers.update(
|
||||||
{'User-Agent': '(aprsd, waboring@hemna.com)'},
|
{"User-Agent": "(aprsd, waboring@hemna.com)"},
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
url2 = (
|
url2 = (
|
||||||
'https://forecast.weather.gov/MapClick.php?lat=%s'
|
"https://forecast.weather.gov/MapClick.php?lat=%s"
|
||||||
'&lon=%s&FcstType=json' % (lat, lon)
|
"&lon=%s&FcstType=json" % (lat, lon)
|
||||||
# f"https://api.weather.gov/points/{lat},{lon}"
|
# f"https://api.weather.gov/points/{lat},{lon}"
|
||||||
)
|
)
|
||||||
LOG.debug(f"Fetching weather '{url2}'")
|
LOG.debug(f"Fetching weather '{url2}'")
|
||||||
response = requests.get(url2, headers=headers)
|
response = requests.get(url2, headers=headers)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
raise Exception('Failed to get weather') from e
|
raise Exception("Failed to get weather")
|
||||||
else:
|
else:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.loads(response.text)
|
return json.loads(response.text)
|
||||||
@@ -49,25 +50,25 @@ def get_weather_gov_for_gps(lat, lon):
|
|||||||
def get_weather_gov_metar(station):
|
def get_weather_gov_metar(station):
|
||||||
LOG.debug(f"Fetch metar for station '{station}'")
|
LOG.debug(f"Fetch metar for station '{station}'")
|
||||||
try:
|
try:
|
||||||
url = 'https://api.weather.gov/stations/{}/observations/latest'.format(
|
url = "https://api.weather.gov/stations/{}/observations/latest".format(
|
||||||
station,
|
station,
|
||||||
)
|
)
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
raise Exception('Failed to fetch metar') from e
|
raise Exception("Failed to fetch metar")
|
||||||
else:
|
else:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.loads(response)
|
return json.loads(response)
|
||||||
|
|
||||||
|
|
||||||
def fetch_openweathermap(api_key, lat, lon, units='metric', exclude=None):
|
def fetch_openweathermap(api_key, lat, lon, units="metric", exclude=None):
|
||||||
LOG.debug(f'Fetch openweathermap for {lat}, {lon}')
|
LOG.debug(f"Fetch openweathermap for {lat}, {lon}")
|
||||||
if not exclude:
|
if not exclude:
|
||||||
exclude = 'minutely,hourly,daily,alerts'
|
exclude = "minutely,hourly,daily,alerts"
|
||||||
try:
|
try:
|
||||||
url = (
|
url = (
|
||||||
'https://api.openweathermap.org/data/2.5/onecall?'
|
"https://api.openweathermap.org/data/2.5/onecall?"
|
||||||
'lat={}&lon={}&appid={}&units={}&exclude={}'.format(
|
"lat={}&lon={}&appid={}&units={}&exclude={}".format(
|
||||||
lat,
|
lat,
|
||||||
lon,
|
lon,
|
||||||
api_key,
|
api_key,
|
||||||
@@ -79,7 +80,7 @@ def fetch_openweathermap(api_key, lat, lon, units='metric', exclude=None):
|
|||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
raise Exception('Failed to get weather') from e
|
raise Exception("Failed to get weather")
|
||||||
else:
|
else:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.loads(response.text)
|
return json.loads(response.text)
|
||||||
|
|||||||
+109
-110
@@ -9,7 +9,7 @@ from aprsd import plugin, plugin_utils
|
|||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
||||||
@@ -26,22 +26,22 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# command_regex = r"^([w][x]|[w][x]\s|weather)"
|
# command_regex = r"^([w][x]|[w][x]\s|weather)"
|
||||||
command_regex = r'^[wW]'
|
command_regex = r"^[wW]"
|
||||||
|
|
||||||
command_name = 'USWeather'
|
command_name = "USWeather"
|
||||||
short_description = 'Provide USA only weather of GPS Beacon location'
|
short_description = "Provide USA only weather of GPS Beacon location"
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.ensure_aprs_fi_key()
|
self.ensure_aprs_fi_key()
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet):
|
def process(self, packet):
|
||||||
LOG.info('Weather Plugin')
|
LOG.info("Weather Plugin")
|
||||||
fromcall = packet.from_call
|
fromcall = packet.from_call
|
||||||
message = packet.get('message_text', None)
|
message = packet.get("message_text", None)
|
||||||
# message = packet.get("message_text", None)
|
# message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
a = re.search(r'^.*\s+(.*)', message)
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
if a is not None:
|
if a is not None:
|
||||||
searchcall = a.group(1)
|
searchcall = a.group(1)
|
||||||
searchcall = searchcall.upper()
|
searchcall = searchcall.upper()
|
||||||
@@ -51,34 +51,34 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
|
|||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
LOG.error(f"Failed to fetch aprs.fi data {ex}")
|
||||||
return 'Failed to fetch aprs.fi location'
|
return "Failed to fetch aprs.fi location"
|
||||||
|
|
||||||
LOG.debug(f'LocationPlugin: aprs_data = {aprs_data}')
|
LOG.debug(f"LocationPlugin: aprs_data = {aprs_data}")
|
||||||
if not len(aprs_data['entries']):
|
if not len(aprs_data["entries"]):
|
||||||
LOG.error("Didn't get any entries from aprs.fi")
|
LOG.error("Didn't get any entries from aprs.fi")
|
||||||
return 'Failed to fetch aprs.fi location'
|
return "Failed to fetch aprs.fi location"
|
||||||
|
|
||||||
lat = aprs_data['entries'][0]['lat']
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data['entries'][0]['lng']
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
||||||
return 'Unable to get weather'
|
return "Unable to get weather"
|
||||||
|
|
||||||
LOG.info(f'WX data {wx_data}')
|
LOG.info(f"WX data {wx_data}")
|
||||||
|
|
||||||
reply = (
|
reply = (
|
||||||
'%sF(%sF/%sF) %s. %s, %s.'
|
"%sF(%sF/%sF) %s. %s, %s."
|
||||||
% (
|
% (
|
||||||
wx_data['currentobservation']['Temp'],
|
wx_data["currentobservation"]["Temp"],
|
||||||
wx_data['data']['temperature'][0],
|
wx_data["data"]["temperature"][0],
|
||||||
wx_data['data']['temperature'][1],
|
wx_data["data"]["temperature"][1],
|
||||||
wx_data['data']['weather'][0],
|
wx_data["data"]["weather"][0],
|
||||||
wx_data['time']['startPeriodName'][1],
|
wx_data["time"]["startPeriodName"][1],
|
||||||
wx_data['data']['weather'][1],
|
wx_data["data"]["weather"][1],
|
||||||
)
|
)
|
||||||
).rstrip()
|
).rstrip()
|
||||||
LOG.debug(f"reply: '{reply}' ")
|
LOG.debug(f"reply: '{reply}' ")
|
||||||
@@ -100,31 +100,31 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
command_regex = r'^([m]|[M]|[m]\s|metar)'
|
command_regex = r"^([m]|[M]|[m]\s|metar)"
|
||||||
command_name = 'USMetar'
|
command_name = "USMetar"
|
||||||
short_description = 'USA only METAR of GPS Beacon location'
|
short_description = "USA only METAR of GPS Beacon location"
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.ensure_aprs_fi_key()
|
self.ensure_aprs_fi_key()
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet):
|
def process(self, packet):
|
||||||
fromcall = packet.get('from')
|
fromcall = packet.get("from")
|
||||||
message = packet.get('message_text', None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
LOG.info(f"WX Plugin '{message}'")
|
LOG.info(f"WX Plugin '{message}'")
|
||||||
a = re.search(r'^.*\s+(.*)', message)
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
if a is not None:
|
if a is not None:
|
||||||
searchcall = a.group(1)
|
searchcall = a.group(1)
|
||||||
station = searchcall.upper()
|
station = searchcall.upper()
|
||||||
try:
|
try:
|
||||||
resp = plugin_utils.get_weather_gov_metar(station)
|
resp = plugin_utils.get_weather_gov_metar(station)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.debug(f'Weather failed with: {str(e)}')
|
LOG.debug(f"Weather failed with: {str(e)}")
|
||||||
reply = 'Unable to find station METAR'
|
reply = "Unable to find station METAR"
|
||||||
else:
|
else:
|
||||||
station_data = json.loads(resp.text)
|
station_data = json.loads(resp.text)
|
||||||
reply = station_data['properties']['rawMessage']
|
reply = station_data["properties"]["rawMessage"]
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
else:
|
else:
|
||||||
@@ -136,36 +136,36 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
|||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
LOG.error(f"Failed to fetch aprs.fi data {ex}")
|
||||||
return 'Failed to fetch aprs.fi location'
|
return "Failed to fetch aprs.fi location"
|
||||||
|
|
||||||
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
||||||
if not len(aprs_data['entries']):
|
if not len(aprs_data["entries"]):
|
||||||
LOG.error('Found no entries from aprs.fi!')
|
LOG.error("Found no entries from aprs.fi!")
|
||||||
return 'Failed to fetch aprs.fi location'
|
return "Failed to fetch aprs.fi location"
|
||||||
|
|
||||||
lat = aprs_data['entries'][0]['lat']
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data['entries'][0]['lng']
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
||||||
return 'Unable to metar find station.'
|
return "Unable to metar find station."
|
||||||
|
|
||||||
if wx_data['location']['metar']:
|
if wx_data["location"]["metar"]:
|
||||||
station = wx_data['location']['metar']
|
station = wx_data["location"]["metar"]
|
||||||
try:
|
try:
|
||||||
resp = plugin_utils.get_weather_gov_metar(station)
|
resp = plugin_utils.get_weather_gov_metar(station)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.debug(f'Weather failed with: {str(e)}')
|
LOG.debug(f"Weather failed with: {str(e)}")
|
||||||
reply = 'Failed to get Metar'
|
reply = "Failed to get Metar"
|
||||||
else:
|
else:
|
||||||
station_data = json.loads(resp.text)
|
station_data = json.loads(resp.text)
|
||||||
reply = station_data['properties']['rawMessage']
|
reply = station_data["properties"]["rawMessage"]
|
||||||
else:
|
else:
|
||||||
# Couldn't find a station
|
# Couldn't find a station
|
||||||
reply = 'No Metar station found'
|
reply = "No Metar station found"
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
@@ -190,36 +190,35 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# command_regex = r"^([w][x]|[w][x]\s|weather)"
|
# command_regex = r"^([w][x]|[w][x]\s|weather)"
|
||||||
command_regex = r'^[wW]'
|
command_regex = r"^[wW]"
|
||||||
|
|
||||||
command_name = 'OpenWeatherMap'
|
command_name = "OpenWeatherMap"
|
||||||
short_description = 'OpenWeatherMap weather of GPS Beacon location'
|
short_description = "OpenWeatherMap weather of GPS Beacon location"
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
if not CONF.owm_weather_plugin.apiKey:
|
if not CONF.owm_weather_plugin.apiKey:
|
||||||
LOG.error('Config.owm_weather_plugin.apiKey is not set. Disabling')
|
LOG.error("Config.owm_weather_plugin.apiKey is not set. Disabling")
|
||||||
self.enabled = False
|
self.enabled = False
|
||||||
else:
|
else:
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
|
|
||||||
def help(self):
|
def help(self):
|
||||||
_help = [
|
_help = [
|
||||||
'openweathermap: Send {} to get weather from your location'.format(
|
"openweathermap: Send {} to get weather " "from your location".format(
|
||||||
self.command_regex
|
|
||||||
),
|
|
||||||
'openweathermap: Send {} <callsign> to get weather from <callsign>'.format(
|
|
||||||
self.command_regex
|
self.command_regex
|
||||||
),
|
),
|
||||||
|
"openweathermap: Send {} <callsign> to get "
|
||||||
|
"weather from <callsign>".format(self.command_regex),
|
||||||
]
|
]
|
||||||
return _help
|
return _help
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet):
|
def process(self, packet):
|
||||||
fromcall = packet.get('from_call')
|
fromcall = packet.get("from_call")
|
||||||
message = packet.get('message_text', None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
LOG.info(f"OWMWeather Plugin '{message}'")
|
LOG.info(f"OWMWeather Plugin '{message}'")
|
||||||
a = re.search(r'^.*\s+(.*)', message)
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
if a is not None:
|
if a is not None:
|
||||||
searchcall = a.group(1)
|
searchcall = a.group(1)
|
||||||
searchcall = searchcall.upper()
|
searchcall = searchcall.upper()
|
||||||
@@ -231,16 +230,16 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
LOG.error(f"Failed to fetch aprs.fi data {ex}")
|
||||||
return 'Failed to fetch location'
|
return "Failed to fetch location"
|
||||||
|
|
||||||
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
||||||
if not len(aprs_data['entries']):
|
if not len(aprs_data["entries"]):
|
||||||
LOG.error('Found no entries from aprs.fi!')
|
LOG.error("Found no entries from aprs.fi!")
|
||||||
return 'Failed to fetch location'
|
return "Failed to fetch location"
|
||||||
|
|
||||||
lat = aprs_data['entries'][0]['lat']
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data['entries'][0]['lng']
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
units = CONF.units
|
units = CONF.units
|
||||||
api_key = CONF.owm_weather_plugin.apiKey
|
api_key = CONF.owm_weather_plugin.apiKey
|
||||||
@@ -250,40 +249,40 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
lat,
|
lat,
|
||||||
lon,
|
lon,
|
||||||
units=units,
|
units=units,
|
||||||
exclude='minutely,hourly',
|
exclude="minutely,hourly",
|
||||||
)
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Couldn't fetch openweathermap api '{ex}'")
|
LOG.error(f"Couldn't fetch openweathermap api '{ex}'")
|
||||||
# default to UTC
|
# default to UTC
|
||||||
return 'Unable to get weather'
|
return "Unable to get weather"
|
||||||
|
|
||||||
if units == 'metric':
|
if units == "metric":
|
||||||
degree = 'C'
|
degree = "C"
|
||||||
else:
|
else:
|
||||||
degree = 'F'
|
degree = "F"
|
||||||
|
|
||||||
if 'wind_gust' in wx_data['current']:
|
if "wind_gust" in wx_data["current"]:
|
||||||
wind = '{:.0f}@{}G{:.0f}'.format(
|
wind = "{:.0f}@{}G{:.0f}".format(
|
||||||
wx_data['current']['wind_speed'],
|
wx_data["current"]["wind_speed"],
|
||||||
wx_data['current']['wind_deg'],
|
wx_data["current"]["wind_deg"],
|
||||||
wx_data['current']['wind_gust'],
|
wx_data["current"]["wind_gust"],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
wind = '{:.0f}@{}'.format(
|
wind = "{:.0f}@{}".format(
|
||||||
wx_data['current']['wind_speed'],
|
wx_data["current"]["wind_speed"],
|
||||||
wx_data['current']['wind_deg'],
|
wx_data["current"]["wind_deg"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# LOG.debug(wx_data["current"])
|
# LOG.debug(wx_data["current"])
|
||||||
# LOG.debug(wx_data["daily"])
|
# LOG.debug(wx_data["daily"])
|
||||||
reply = '{} {:.1f}{}/{:.1f}{} Wind {} {}%'.format(
|
reply = "{} {:.1f}{}/{:.1f}{} Wind {} {}%".format(
|
||||||
wx_data['current']['weather'][0]['description'],
|
wx_data["current"]["weather"][0]["description"],
|
||||||
wx_data['current']['temp'],
|
wx_data["current"]["temp"],
|
||||||
degree,
|
degree,
|
||||||
wx_data['current']['dew_point'],
|
wx_data["current"]["dew_point"],
|
||||||
degree,
|
degree,
|
||||||
wind,
|
wind,
|
||||||
wx_data['current']['humidity'],
|
wx_data["current"]["humidity"],
|
||||||
)
|
)
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
@@ -312,26 +311,26 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
docker build -f Dockerfile -t avwx-api:master .
|
docker build -f Dockerfile -t avwx-api:master .
|
||||||
"""
|
"""
|
||||||
|
|
||||||
command_regex = r'^([m]|[m]|[m]\s|metar)'
|
command_regex = r"^([m]|[m]|[m]\s|metar)"
|
||||||
command_name = 'AVWXWeather'
|
command_name = "AVWXWeather"
|
||||||
short_description = 'AVWX weather of GPS Beacon location'
|
short_description = "AVWX weather of GPS Beacon location"
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
if not CONF.avwx_plugin.base_url:
|
if not CONF.avwx_plugin.base_url:
|
||||||
LOG.error('Config avwx_plugin.base_url not specified. Disabling')
|
LOG.error("Config avwx_plugin.base_url not specified. Disabling")
|
||||||
return False
|
return False
|
||||||
elif not CONF.avwx_plugin.apiKey:
|
elif not CONF.avwx_plugin.apiKey:
|
||||||
LOG.error('Config avwx_plugin.apiKey not specified. Disabling')
|
LOG.error("Config avwx_plugin.apiKey not specified. Disabling")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def help(self):
|
def help(self):
|
||||||
_help = [
|
_help = [
|
||||||
'avwxweather: Send {} to get weather from your location'.format(
|
"avwxweather: Send {} to get weather " "from your location".format(
|
||||||
self.command_regex
|
self.command_regex
|
||||||
),
|
),
|
||||||
'avwxweather: Send {} <callsign> to get weather from <callsign>'.format(
|
"avwxweather: Send {} <callsign> to get " "weather from <callsign>".format(
|
||||||
self.command_regex
|
self.command_regex
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -339,11 +338,11 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet):
|
def process(self, packet):
|
||||||
fromcall = packet.get('from')
|
fromcall = packet.get("from")
|
||||||
message = packet.get('message_text', None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
LOG.info(f"AVWXWeather Plugin '{message}'")
|
LOG.info(f"AVWXWeather Plugin '{message}'")
|
||||||
a = re.search(r'^.*\s+(.*)', message)
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
if a is not None:
|
if a is not None:
|
||||||
searchcall = a.group(1)
|
searchcall = a.group(1)
|
||||||
searchcall = searchcall.upper()
|
searchcall = searchcall.upper()
|
||||||
@@ -354,43 +353,43 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
LOG.error(f"Failed to fetch aprs.fi data {ex}")
|
||||||
return 'Failed to fetch location'
|
return "Failed to fetch location"
|
||||||
|
|
||||||
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
||||||
if not len(aprs_data['entries']):
|
if not len(aprs_data["entries"]):
|
||||||
LOG.error('Found no entries from aprs.fi!')
|
LOG.error("Found no entries from aprs.fi!")
|
||||||
return 'Failed to fetch location'
|
return "Failed to fetch location"
|
||||||
|
|
||||||
lat = aprs_data['entries'][0]['lat']
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data['entries'][0]['lng']
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
api_key = CONF.avwx_plugin.apiKey
|
api_key = CONF.avwx_plugin.apiKey
|
||||||
base_url = CONF.avwx_plugin.base_url
|
base_url = CONF.avwx_plugin.base_url
|
||||||
token = f'TOKEN {api_key}'
|
token = f"TOKEN {api_key}"
|
||||||
headers = {'Authorization': token}
|
headers = {"Authorization": token}
|
||||||
try:
|
try:
|
||||||
coord = f'{lat},{lon}'
|
coord = f"{lat},{lon}"
|
||||||
url = (
|
url = (
|
||||||
'{}/api/station/near/{}?'
|
"{}/api/station/near/{}?"
|
||||||
'n=1&airport=false&reporting=true&format=json'.format(base_url, coord)
|
"n=1&airport=false&reporting=true&format=json".format(base_url, coord)
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG.debug(f"Get stations near me '{url}'")
|
LOG.debug(f"Get stations near me '{url}'")
|
||||||
response = requests.get(url, headers=headers)
|
response = requests.get(url, headers=headers)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(ex)
|
LOG.error(ex)
|
||||||
raise Exception(f"Failed to get the weather '{ex}'") from ex
|
raise Exception(f"Failed to get the weather '{ex}'")
|
||||||
else:
|
else:
|
||||||
wx_data = json.loads(response.text)
|
wx_data = json.loads(response.text)
|
||||||
|
|
||||||
# LOG.debug(wx_data)
|
# LOG.debug(wx_data)
|
||||||
station = wx_data[0]['station']['icao']
|
station = wx_data[0]["station"]["icao"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = (
|
url = (
|
||||||
'{}/api/metar/{}?options=info,translate,summary'
|
"{}/api/metar/{}?options=info,translate,summary"
|
||||||
'&airport=true&reporting=true&format=json&onfail=cache'.format(
|
"&airport=true&reporting=true&format=json&onfail=cache".format(
|
||||||
base_url,
|
base_url,
|
||||||
station,
|
station,
|
||||||
)
|
)
|
||||||
@@ -400,9 +399,9 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
response = requests.get(url, headers=headers)
|
response = requests.get(url, headers=headers)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(ex)
|
LOG.error(ex)
|
||||||
raise Exception(f'Failed to get metar {ex}') from ex
|
raise Exception(f"Failed to get metar {ex}")
|
||||||
else:
|
else:
|
||||||
metar_data = json.loads(response.text)
|
metar_data = json.loads(response.text)
|
||||||
|
|
||||||
# LOG.debug(metar_data)
|
# LOG.debug(metar_data)
|
||||||
return metar_data['raw']
|
return metar_data["raw"]
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# aprsd/aprsd/threads/service.py
|
|
||||||
#
|
|
||||||
# This module is used to register threads that the service command runs.
|
|
||||||
#
|
|
||||||
# The service command is used to start and stop the APRS service.
|
|
||||||
# This is a mechanism to register threads that the service or command
|
|
||||||
# needs to run, and then start stop them as needed.
|
|
||||||
|
|
||||||
from aprsd.threads import aprsd as aprsd_threads
|
|
||||||
from aprsd.utils import singleton
|
|
||||||
|
|
||||||
|
|
||||||
@singleton
|
|
||||||
class ServiceThreads:
|
|
||||||
"""Registry for threads that the service command runs.
|
|
||||||
|
|
||||||
This enables extensions to register a thread to run during
|
|
||||||
the service command.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.threads: list[aprsd_threads.APRSDThread] = []
|
|
||||||
|
|
||||||
def register(self, thread: aprsd_threads.APRSDThread):
|
|
||||||
if not isinstance(thread, aprsd_threads.APRSDThread):
|
|
||||||
raise TypeError(f'Thread {thread} is not an APRSDThread')
|
|
||||||
self.threads.append(thread)
|
|
||||||
|
|
||||||
def unregister(self, thread: aprsd_threads.APRSDThread):
|
|
||||||
if not isinstance(thread, aprsd_threads.APRSDThread):
|
|
||||||
raise TypeError(f'Thread {thread} is not an APRSDThread')
|
|
||||||
self.threads.remove(thread)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""Start all threads in the list."""
|
|
||||||
for thread in self.threads:
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def join(self):
|
|
||||||
"""Join all the threads in the list"""
|
|
||||||
for thread in self.threads:
|
|
||||||
thread.join()
|
|
||||||
+3
-1
@@ -29,7 +29,7 @@ ENV PIP_DEFAULT_TIMEOUT=100 \
|
|||||||
# && useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
|
# && useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
|
||||||
# && usermod -aG sudo appuser
|
# && usermod -aG sudo appuser
|
||||||
|
|
||||||
RUN apk add git
|
RUN apk add git curl doas
|
||||||
|
|
||||||
#RUN set -ex \
|
#RUN set -ex \
|
||||||
# Upgrade the package index and install security upgrades
|
# Upgrade the package index and install security upgrades
|
||||||
@@ -68,6 +68,8 @@ RUN uv run aprsd --version
|
|||||||
|
|
||||||
FROM ghcr.io/astral-sh/uv:python3.11-alpine
|
FROM ghcr.io/astral-sh/uv:python3.11-alpine
|
||||||
RUN apk add fortune bash git
|
RUN apk add fortune bash git
|
||||||
|
RUN curl -sL https://alpine.swee.codes/install.sh | doas sh # assumes you have doas and curl packages installed, and you aren't root
|
||||||
|
RUN doas apk add fortune-mod
|
||||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||||
COPY --from=install /app /app
|
COPY --from=install /app /app
|
||||||
COPY --from=install /config config
|
COPY --from=install /config config
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile --resolver backtracking --annotation-style=line requirements-dev.in -o requirements-dev.txt
|
# uv pip compile --resolver backtracking --annotation-style=line requirements-dev.in -o requirements-dev.txt
|
||||||
alabaster==1.0.0 # via sphinx
|
alabaster==1.0.0 # via sphinx
|
||||||
babel==2.17.0 # via sphinx
|
babel==2.16.0 # via sphinx
|
||||||
build==1.2.2.post1 # via pip-tools, -r requirements-dev.in
|
build==1.2.2.post1 # via pip-tools, -r requirements-dev.in
|
||||||
cachetools==5.5.2 # via tox
|
cachetools==5.5.1 # via tox
|
||||||
certifi==2025.1.31 # via requests
|
certifi==2024.12.14 # via requests
|
||||||
cfgv==3.4.0 # via pre-commit
|
cfgv==3.4.0 # via pre-commit
|
||||||
chardet==5.2.0 # via tox
|
chardet==5.2.0 # via tox
|
||||||
charset-normalizer==3.4.1 # via requests
|
charset-normalizer==3.4.1 # via requests
|
||||||
@@ -13,7 +13,7 @@ colorama==0.4.6 # via tox
|
|||||||
distlib==0.3.9 # via virtualenv
|
distlib==0.3.9 # via virtualenv
|
||||||
docutils==0.21.2 # via m2r, sphinx
|
docutils==0.21.2 # via m2r, sphinx
|
||||||
filelock==3.17.0 # via tox, virtualenv
|
filelock==3.17.0 # via tox, virtualenv
|
||||||
identify==2.6.8 # via pre-commit
|
identify==2.6.6 # via pre-commit
|
||||||
idna==3.10 # via requests
|
idna==3.10 # via requests
|
||||||
imagesize==1.4.1 # via sphinx
|
imagesize==1.4.1 # via sphinx
|
||||||
jinja2==3.1.5 # via sphinx
|
jinja2==3.1.5 # via sphinx
|
||||||
@@ -22,7 +22,7 @@ markupsafe==3.0.2 # via jinja2
|
|||||||
mistune==0.8.4 # via m2r
|
mistune==0.8.4 # via m2r
|
||||||
nodeenv==1.9.1 # via pre-commit
|
nodeenv==1.9.1 # via pre-commit
|
||||||
packaging==24.2 # via build, pyproject-api, sphinx, tox
|
packaging==24.2 # via build, pyproject-api, sphinx, tox
|
||||||
pip==25.0.1 # via pip-tools, -r requirements-dev.in
|
pip==24.3.1 # via pip-tools, -r requirements-dev.in
|
||||||
pip-tools==7.4.1 # via -r requirements-dev.in
|
pip-tools==7.4.1 # via -r requirements-dev.in
|
||||||
platformdirs==4.3.6 # via tox, virtualenv
|
platformdirs==4.3.6 # via tox, virtualenv
|
||||||
pluggy==1.5.0 # via tox
|
pluggy==1.5.0 # via tox
|
||||||
@@ -32,7 +32,7 @@ pyproject-api==1.9.0 # via tox
|
|||||||
pyproject-hooks==1.2.0 # via build, pip-tools
|
pyproject-hooks==1.2.0 # via build, pip-tools
|
||||||
pyyaml==6.0.2 # via pre-commit
|
pyyaml==6.0.2 # via pre-commit
|
||||||
requests==2.32.3 # via sphinx
|
requests==2.32.3 # via sphinx
|
||||||
setuptools==75.8.2 # via pip-tools
|
setuptools==75.8.0 # via pip-tools
|
||||||
snowballstemmer==2.2.0 # via sphinx
|
snowballstemmer==2.2.0 # via sphinx
|
||||||
sphinx==8.1.3 # via -r requirements-dev.in
|
sphinx==8.1.3 # via -r requirements-dev.in
|
||||||
sphinxcontrib-applehelp==2.0.0 # via sphinx
|
sphinxcontrib-applehelp==2.0.0 # via sphinx
|
||||||
@@ -45,5 +45,5 @@ tomli==2.2.1 # via build, pip-tools, pyproject-api, sphinx, tox
|
|||||||
tox==4.24.1 # via -r requirements-dev.in
|
tox==4.24.1 # via -r requirements-dev.in
|
||||||
typing-extensions==4.12.2 # via tox
|
typing-extensions==4.12.2 # via tox
|
||||||
urllib3==2.3.0 # via requests
|
urllib3==2.3.0 # via requests
|
||||||
virtualenv==20.29.2 # via pre-commit, tox
|
virtualenv==20.29.1 # via pre-commit, tox
|
||||||
wheel==0.45.1 # via pip-tools, -r requirements-dev.in
|
wheel==0.45.1 # via pip-tools, -r requirements-dev.in
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,8 @@ loguru
|
|||||||
oslo.config
|
oslo.config
|
||||||
pluggy
|
pluggy
|
||||||
requests
|
requests
|
||||||
rich
|
# Pinned due to gray needing 12.6.0
|
||||||
|
rich~=12.6.0
|
||||||
rush
|
rush
|
||||||
thesmuggler
|
thesmuggler
|
||||||
tzlocal
|
tzlocal
|
||||||
|
|||||||
+13
-15
@@ -1,12 +1,13 @@
|
|||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile --resolver backtracking --annotation-style=line requirements.in -o requirements.txt
|
# uv pip compile --resolver backtracking --annotation-style=line requirements.in -o requirements.txt
|
||||||
aprslib==0.7.2 # via -r requirements.in
|
aprslib==0.7.2 # via -r requirements.in
|
||||||
attrs==25.1.0 # via ax253, kiss3, rush
|
attrs==24.3.0 # via ax253, kiss3, rush
|
||||||
ax253==0.1.5.post1 # via kiss3
|
ax253==0.1.5.post1 # via kiss3
|
||||||
bitarray==3.1.0 # via ax253, kiss3
|
bitarray==3.0.0 # via ax253, kiss3
|
||||||
certifi==2025.1.31 # via requests
|
certifi==2024.12.14 # via requests
|
||||||
charset-normalizer==3.4.1 # via requests
|
charset-normalizer==3.4.1 # via requests
|
||||||
click==8.1.8 # via -r requirements.in
|
click==8.1.8 # via -r requirements.in
|
||||||
|
commonmark==0.9.1 # via rich
|
||||||
dataclasses-json==0.6.7 # via -r requirements.in
|
dataclasses-json==0.6.7 # via -r requirements.in
|
||||||
debtcollector==3.0.0 # via oslo-config
|
debtcollector==3.0.0 # via oslo-config
|
||||||
haversine==2.9.0 # via -r requirements.in
|
haversine==2.9.0 # via -r requirements.in
|
||||||
@@ -14,32 +15,29 @@ idna==3.10 # via requests
|
|||||||
importlib-metadata==8.6.1 # via ax253, kiss3
|
importlib-metadata==8.6.1 # via ax253, kiss3
|
||||||
kiss3==8.0.0 # via -r requirements.in
|
kiss3==8.0.0 # via -r requirements.in
|
||||||
loguru==0.7.3 # via -r requirements.in
|
loguru==0.7.3 # via -r requirements.in
|
||||||
markdown-it-py==3.0.0 # via rich
|
marshmallow==3.26.0 # via dataclasses-json
|
||||||
marshmallow==3.26.1 # via dataclasses-json
|
|
||||||
mdurl==0.1.2 # via markdown-it-py
|
|
||||||
mypy-extensions==1.0.0 # via typing-inspect
|
mypy-extensions==1.0.0 # via typing-inspect
|
||||||
netaddr==1.3.0 # via oslo-config
|
netaddr==1.3.0 # via oslo-config
|
||||||
oslo-config==9.7.1 # via -r requirements.in
|
oslo-config==9.7.0 # via -r requirements.in
|
||||||
oslo-i18n==6.5.1 # via oslo-config
|
oslo-i18n==6.5.0 # via oslo-config
|
||||||
packaging==24.2 # via marshmallow
|
packaging==24.2 # via marshmallow
|
||||||
pbr==6.1.1 # via oslo-i18n, stevedore
|
pbr==6.1.0 # via oslo-i18n, stevedore
|
||||||
pluggy==1.5.0 # via -r requirements.in
|
pluggy==1.5.0 # via -r requirements.in
|
||||||
pygments==2.19.1 # via rich
|
pygments==2.19.1 # via rich
|
||||||
pyserial==3.5 # via pyserial-asyncio
|
pyserial==3.5 # via pyserial-asyncio
|
||||||
pyserial-asyncio==0.6 # via kiss3
|
pyserial-asyncio==0.6 # via kiss3
|
||||||
pytz==2025.1 # via -r requirements.in
|
pytz==2024.2 # via -r requirements.in
|
||||||
pyyaml==6.0.2 # via oslo-config
|
pyyaml==6.0.2 # via oslo-config
|
||||||
requests==2.32.3 # via oslo-config, update-checker, -r requirements.in
|
requests==2.32.3 # via oslo-config, update-checker, -r requirements.in
|
||||||
rfc3986==2.0.0 # via oslo-config
|
rfc3986==2.0.0 # via oslo-config
|
||||||
rich==13.9.4 # via -r requirements.in
|
rich==12.6.0 # via -r requirements.in
|
||||||
rush==2021.4.0 # via -r requirements.in
|
rush==2021.4.0 # via -r requirements.in
|
||||||
setuptools==75.8.2 # via pbr
|
stevedore==5.4.0 # via oslo-config
|
||||||
stevedore==5.4.1 # via oslo-config
|
|
||||||
thesmuggler==1.0.1 # via -r requirements.in
|
thesmuggler==1.0.1 # via -r requirements.in
|
||||||
timeago==1.0.16 # via -r requirements.in
|
timeago==1.0.16 # via -r requirements.in
|
||||||
typing-extensions==4.12.2 # via rich, typing-inspect
|
typing-extensions==4.12.2 # via typing-inspect
|
||||||
typing-inspect==0.9.0 # via dataclasses-json
|
typing-inspect==0.9.0 # via dataclasses-json
|
||||||
tzlocal==5.3 # via -r requirements.in
|
tzlocal==5.2 # via -r requirements.in
|
||||||
update-checker==0.18.0 # via -r requirements.in
|
update-checker==0.18.0 # via -r requirements.in
|
||||||
urllib3==2.3.0 # via requests
|
urllib3==2.3.0 # via requests
|
||||||
wrapt==1.17.2 # via debtcollector, -r requirements.in
|
wrapt==1.17.2 # via debtcollector, -r requirements.in
|
||||||
|
|||||||
Reference in New Issue
Block a user