aprsd 1.0.0__py3-none-any.whl → 3.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aprsd/__init__.py +6 -4
- aprsd/cli_helper.py +151 -0
- aprsd/client/__init__.py +13 -0
- aprsd/client/aprsis.py +132 -0
- aprsd/client/base.py +105 -0
- aprsd/client/drivers/__init__.py +0 -0
- aprsd/client/drivers/aprsis.py +228 -0
- aprsd/client/drivers/fake.py +73 -0
- aprsd/client/drivers/kiss.py +119 -0
- aprsd/client/factory.py +88 -0
- aprsd/client/fake.py +48 -0
- aprsd/client/kiss.py +103 -0
- aprsd/client/stats.py +38 -0
- aprsd/cmds/__init__.py +0 -0
- aprsd/cmds/completion.py +22 -0
- aprsd/cmds/dev.py +162 -0
- aprsd/cmds/fetch_stats.py +156 -0
- aprsd/cmds/healthcheck.py +86 -0
- aprsd/cmds/list_plugins.py +319 -0
- aprsd/cmds/listen.py +231 -0
- aprsd/cmds/send_message.py +171 -0
- aprsd/cmds/server.py +137 -0
- aprsd/cmds/webchat.py +674 -0
- aprsd/conf/__init__.py +56 -0
- aprsd/conf/client.py +131 -0
- aprsd/conf/common.py +301 -0
- aprsd/conf/log.py +65 -0
- aprsd/conf/opts.py +80 -0
- aprsd/conf/plugin_common.py +182 -0
- aprsd/conf/plugin_email.py +105 -0
- aprsd/exception.py +13 -0
- aprsd/log/__init__.py +0 -0
- aprsd/log/log.py +138 -0
- aprsd/main.py +104 -867
- aprsd/packets/__init__.py +20 -0
- aprsd/packets/collector.py +79 -0
- aprsd/packets/core.py +823 -0
- aprsd/packets/log.py +161 -0
- aprsd/packets/packet_list.py +110 -0
- aprsd/packets/seen_list.py +49 -0
- aprsd/packets/tracker.py +103 -0
- aprsd/packets/watch_list.py +119 -0
- aprsd/plugin.py +474 -284
- aprsd/plugin_utils.py +86 -0
- aprsd/plugins/__init__.py +0 -0
- aprsd/plugins/email.py +709 -0
- aprsd/plugins/fortune.py +61 -0
- aprsd/plugins/location.py +179 -0
- aprsd/plugins/notify.py +61 -0
- aprsd/plugins/ping.py +31 -0
- aprsd/plugins/time.py +115 -0
- aprsd/plugins/version.py +31 -0
- aprsd/plugins/weather.py +405 -0
- aprsd/stats/__init__.py +20 -0
- aprsd/stats/app.py +49 -0
- aprsd/stats/collector.py +37 -0
- aprsd/threads/__init__.py +11 -0
- aprsd/threads/aprsd.py +119 -0
- aprsd/threads/keep_alive.py +131 -0
- aprsd/threads/log_monitor.py +121 -0
- aprsd/threads/registry.py +56 -0
- aprsd/threads/rx.py +354 -0
- aprsd/threads/stats.py +44 -0
- aprsd/threads/tx.py +255 -0
- aprsd/utils/__init__.py +218 -0
- aprsd/utils/counter.py +51 -0
- aprsd/utils/json.py +80 -0
- aprsd/utils/objectstore.py +123 -0
- aprsd/utils/ring_buffer.py +40 -0
- aprsd/utils/trace.py +180 -0
- aprsd/web/__init__.py +0 -0
- aprsd/web/admin/__init__.py +0 -0
- aprsd/web/admin/static/css/index.css +84 -0
- aprsd/web/admin/static/css/prism.css +4 -0
- aprsd/web/admin/static/css/tabs.css +35 -0
- aprsd/web/admin/static/images/Untitled.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/admin/static/js/charts.js +235 -0
- aprsd/web/admin/static/js/echarts.js +465 -0
- aprsd/web/admin/static/js/logs.js +26 -0
- aprsd/web/admin/static/js/main.js +231 -0
- aprsd/web/admin/static/js/prism.js +12 -0
- aprsd/web/admin/static/js/send-message.js +114 -0
- aprsd/web/admin/static/js/tabs.js +28 -0
- aprsd/web/admin/templates/index.html +196 -0
- aprsd/web/chat/static/css/chat.css +115 -0
- aprsd/web/chat/static/css/index.css +66 -0
- aprsd/web/chat/static/css/style.css.map +1 -0
- aprsd/web/chat/static/css/tabs.css +41 -0
- aprsd/web/chat/static/css/upstream/bootstrap.min.css +6 -0
- aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/google-fonts.css +23 -0
- aprsd/web/chat/static/css/upstream/jquery-ui.css +1311 -0
- aprsd/web/chat/static/css/upstream/jquery.toast.css +28 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
- aprsd/web/chat/static/images/Untitled.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/chat/static/images/globe.svg +3 -0
- aprsd/web/chat/static/js/gps.js +84 -0
- aprsd/web/chat/static/js/main.js +45 -0
- aprsd/web/chat/static/js/send-message.js +585 -0
- aprsd/web/chat/static/js/tabs.js +28 -0
- aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +7 -0
- aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +2 -0
- aprsd/web/chat/static/js/upstream/jquery-ui.min.js +13 -0
- aprsd/web/chat/static/js/upstream/jquery.toast.js +374 -0
- aprsd/web/chat/static/js/upstream/semantic.min.js +11 -0
- aprsd/web/chat/static/js/upstream/socket.io.min.js +7 -0
- aprsd/web/chat/templates/index.html +139 -0
- aprsd/wsgi.py +315 -0
- aprsd-3.4.2.dist-info/AUTHORS +13 -0
- aprsd-3.4.2.dist-info/LICENSE +175 -0
- aprsd-3.4.2.dist-info/METADATA +793 -0
- aprsd-3.4.2.dist-info/RECORD +133 -0
- {aprsd-1.0.0.dist-info → aprsd-3.4.2.dist-info}/WHEEL +1 -1
- aprsd-3.4.2.dist-info/entry_points.txt +8 -0
- aprsd/fake_aprs.py +0 -83
- aprsd/utils.py +0 -166
- aprsd-1.0.0.dist-info/AUTHORS +0 -6
- aprsd-1.0.0.dist-info/METADATA +0 -181
- aprsd-1.0.0.dist-info/RECORD +0 -13
- aprsd-1.0.0.dist-info/entry_points.txt +0 -4
- aprsd-1.0.0.dist-info/pbr.json +0 -1
- /aprsd/{fuzzyclock.py → utils/fuzzyclock.py} +0 -0
- {aprsd-1.0.0.dist-info → aprsd-3.4.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
import logging
|
2
|
+
import threading
|
3
|
+
import time
|
4
|
+
|
5
|
+
import aprslib
|
6
|
+
from oslo_config import cfg
|
7
|
+
import wrapt
|
8
|
+
|
9
|
+
from aprsd import conf # noqa
|
10
|
+
from aprsd.packets import core
|
11
|
+
from aprsd.utils import trace
|
12
|
+
|
13
|
+
|
14
|
+
CONF = cfg.CONF
|
15
|
+
LOG = logging.getLogger("APRSD")
|
16
|
+
|
17
|
+
|
18
|
+
class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
19
|
+
'''Fake client for testing.'''
|
20
|
+
|
21
|
+
# flag to tell us to stop
|
22
|
+
thread_stop = False
|
23
|
+
|
24
|
+
lock = threading.Lock()
|
25
|
+
path = []
|
26
|
+
|
27
|
+
def __init__(self):
|
28
|
+
LOG.info("Starting APRSDFakeClient client.")
|
29
|
+
self.path = ["WIDE1-1", "WIDE2-1"]
|
30
|
+
|
31
|
+
def stop(self):
|
32
|
+
self.thread_stop = True
|
33
|
+
LOG.info("Shutdown APRSDFakeClient client.")
|
34
|
+
|
35
|
+
def is_alive(self):
|
36
|
+
"""If the connection is alive or not."""
|
37
|
+
return not self.thread_stop
|
38
|
+
|
39
|
+
@wrapt.synchronized(lock)
|
40
|
+
def send(self, packet: core.Packet):
|
41
|
+
"""Send an APRS Message object."""
|
42
|
+
LOG.info(f"Sending packet: {packet}")
|
43
|
+
payload = None
|
44
|
+
if isinstance(packet, core.Packet):
|
45
|
+
packet.prepare()
|
46
|
+
payload = packet.payload.encode("US-ASCII")
|
47
|
+
if packet.path:
|
48
|
+
packet.path
|
49
|
+
else:
|
50
|
+
self.path
|
51
|
+
else:
|
52
|
+
msg_payload = f"{packet.raw}{{{str(packet.msgNo)}"
|
53
|
+
payload = (
|
54
|
+
":{:<9}:{}".format(
|
55
|
+
packet.to_call,
|
56
|
+
msg_payload,
|
57
|
+
)
|
58
|
+
).encode("US-ASCII")
|
59
|
+
|
60
|
+
LOG.debug(
|
61
|
+
f"FAKE::Send '{payload}' TO '{packet.to_call}' From "
|
62
|
+
f"'{packet.from_call}' with PATH \"{self.path}\"",
|
63
|
+
)
|
64
|
+
|
65
|
+
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
66
|
+
LOG.debug("Start non blocking FAKE consumer")
|
67
|
+
# Generate packets here?
|
68
|
+
raw = "GTOWN>APDW16,WIDE1-1,WIDE2-1:}KM6LYW-9>APZ100,TCPIP,GTOWN*::KM6LYW :KM6LYW: 19 Miles SW"
|
69
|
+
pkt_raw = aprslib.parse(raw)
|
70
|
+
pkt = core.factory(pkt_raw)
|
71
|
+
callback(packet=pkt)
|
72
|
+
LOG.debug(f"END blocking FAKE consumer {self}")
|
73
|
+
time.sleep(8)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from ax253 import Frame
|
4
|
+
import kiss
|
5
|
+
from oslo_config import cfg
|
6
|
+
|
7
|
+
from aprsd import conf # noqa
|
8
|
+
from aprsd.packets import core
|
9
|
+
from aprsd.utils import trace
|
10
|
+
|
11
|
+
|
12
|
+
CONF = cfg.CONF
|
13
|
+
LOG = logging.getLogger("APRSD")
|
14
|
+
|
15
|
+
|
16
|
+
class KISS3Client:
|
17
|
+
path = []
|
18
|
+
|
19
|
+
def __init__(self):
|
20
|
+
self.setup()
|
21
|
+
|
22
|
+
def is_alive(self):
|
23
|
+
return True
|
24
|
+
|
25
|
+
def setup(self):
|
26
|
+
# we can be TCP kiss or Serial kiss
|
27
|
+
if CONF.kiss_serial.enabled:
|
28
|
+
LOG.debug(
|
29
|
+
"KISS({}) Serial connection to {}".format(
|
30
|
+
kiss.__version__,
|
31
|
+
CONF.kiss_serial.device,
|
32
|
+
),
|
33
|
+
)
|
34
|
+
self.kiss = kiss.SerialKISS(
|
35
|
+
port=CONF.kiss_serial.device,
|
36
|
+
speed=CONF.kiss_serial.baudrate,
|
37
|
+
strip_df_start=True,
|
38
|
+
)
|
39
|
+
self.path = CONF.kiss_serial.path
|
40
|
+
elif CONF.kiss_tcp.enabled:
|
41
|
+
LOG.debug(
|
42
|
+
"KISS({}) TCP Connection to {}:{}".format(
|
43
|
+
kiss.__version__,
|
44
|
+
CONF.kiss_tcp.host,
|
45
|
+
CONF.kiss_tcp.port,
|
46
|
+
),
|
47
|
+
)
|
48
|
+
self.kiss = kiss.TCPKISS(
|
49
|
+
host=CONF.kiss_tcp.host,
|
50
|
+
port=CONF.kiss_tcp.port,
|
51
|
+
strip_df_start=True,
|
52
|
+
)
|
53
|
+
self.path = CONF.kiss_tcp.path
|
54
|
+
|
55
|
+
LOG.debug("Starting KISS interface connection")
|
56
|
+
self.kiss.start()
|
57
|
+
|
58
|
+
@trace.trace
|
59
|
+
def stop(self):
|
60
|
+
try:
|
61
|
+
self.kiss.stop()
|
62
|
+
self.kiss.loop.call_soon_threadsafe(
|
63
|
+
self.kiss.protocol.transport.close,
|
64
|
+
)
|
65
|
+
except Exception as ex:
|
66
|
+
LOG.exception(ex)
|
67
|
+
|
68
|
+
def set_filter(self, filter):
|
69
|
+
# This does nothing right now.
|
70
|
+
pass
|
71
|
+
|
72
|
+
def parse_frame(self, frame_bytes):
|
73
|
+
try:
|
74
|
+
frame = Frame.from_bytes(frame_bytes)
|
75
|
+
# Now parse it with aprslib
|
76
|
+
kwargs = {
|
77
|
+
"frame": frame,
|
78
|
+
}
|
79
|
+
self._parse_callback(**kwargs)
|
80
|
+
except Exception as ex:
|
81
|
+
LOG.error("Failed to parse bytes received from KISS interface.")
|
82
|
+
LOG.exception(ex)
|
83
|
+
|
84
|
+
def consumer(self, callback):
|
85
|
+
LOG.debug("Start blocking KISS consumer")
|
86
|
+
self._parse_callback = callback
|
87
|
+
self.kiss.read(callback=self.parse_frame, min_frames=None)
|
88
|
+
LOG.debug(f"END blocking KISS consumer {self.kiss}")
|
89
|
+
|
90
|
+
def send(self, packet):
|
91
|
+
"""Send an APRS Message object."""
|
92
|
+
|
93
|
+
payload = None
|
94
|
+
path = self.path
|
95
|
+
if isinstance(packet, core.Packet):
|
96
|
+
packet.prepare()
|
97
|
+
payload = packet.payload.encode("US-ASCII")
|
98
|
+
if packet.path:
|
99
|
+
path = packet.path
|
100
|
+
else:
|
101
|
+
msg_payload = f"{packet.raw}{{{str(packet.msgNo)}"
|
102
|
+
payload = (
|
103
|
+
":{:<9}:{}".format(
|
104
|
+
packet.to_call,
|
105
|
+
msg_payload,
|
106
|
+
)
|
107
|
+
).encode("US-ASCII")
|
108
|
+
|
109
|
+
LOG.debug(
|
110
|
+
f"KISS Send '{payload}' TO '{packet.to_call}' From "
|
111
|
+
f"'{packet.from_call}' with PATH '{path}'",
|
112
|
+
)
|
113
|
+
frame = Frame.ui(
|
114
|
+
destination="APZ100",
|
115
|
+
source=packet.from_call,
|
116
|
+
path=path,
|
117
|
+
info=payload,
|
118
|
+
)
|
119
|
+
self.kiss.write(frame)
|
aprsd/client/factory.py
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Callable, Protocol, runtime_checkable
|
3
|
+
|
4
|
+
from aprsd import exception
|
5
|
+
from aprsd.packets import core
|
6
|
+
|
7
|
+
|
8
|
+
LOG = logging.getLogger("APRSD")
|
9
|
+
|
10
|
+
|
11
|
+
@runtime_checkable
|
12
|
+
class Client(Protocol):
|
13
|
+
|
14
|
+
def __init__(self):
|
15
|
+
pass
|
16
|
+
|
17
|
+
def connect(self) -> bool:
|
18
|
+
pass
|
19
|
+
|
20
|
+
def disconnect(self) -> bool:
|
21
|
+
pass
|
22
|
+
|
23
|
+
def decode_packet(self, *args, **kwargs) -> type[core.Packet]:
|
24
|
+
pass
|
25
|
+
|
26
|
+
def is_enabled(self) -> bool:
|
27
|
+
pass
|
28
|
+
|
29
|
+
def is_configured(self) -> bool:
|
30
|
+
pass
|
31
|
+
|
32
|
+
def transport(self) -> str:
|
33
|
+
pass
|
34
|
+
|
35
|
+
def send(self, message: str) -> bool:
|
36
|
+
pass
|
37
|
+
|
38
|
+
def setup_connection(self) -> None:
|
39
|
+
pass
|
40
|
+
|
41
|
+
|
42
|
+
class ClientFactory:
|
43
|
+
_instance = None
|
44
|
+
clients = []
|
45
|
+
|
46
|
+
def __new__(cls, *args, **kwargs):
|
47
|
+
"""This magic turns this into a singleton."""
|
48
|
+
if cls._instance is None:
|
49
|
+
cls._instance = super().__new__(cls)
|
50
|
+
# Put any initialization here.
|
51
|
+
return cls._instance
|
52
|
+
|
53
|
+
def __init__(self):
|
54
|
+
self.clients: list[Callable] = []
|
55
|
+
|
56
|
+
def register(self, aprsd_client: Callable):
|
57
|
+
if isinstance(aprsd_client, Client):
|
58
|
+
raise ValueError("Client must be a subclass of Client protocol")
|
59
|
+
|
60
|
+
self.clients.append(aprsd_client)
|
61
|
+
|
62
|
+
def create(self, key=None):
|
63
|
+
for client in self.clients:
|
64
|
+
if client.is_enabled():
|
65
|
+
return client()
|
66
|
+
raise Exception("No client is configured!!")
|
67
|
+
|
68
|
+
def is_client_enabled(self):
|
69
|
+
"""Make sure at least one client is enabled."""
|
70
|
+
enabled = False
|
71
|
+
for client in self.clients:
|
72
|
+
if client.is_enabled():
|
73
|
+
enabled = True
|
74
|
+
return enabled
|
75
|
+
|
76
|
+
def is_client_configured(self):
|
77
|
+
enabled = False
|
78
|
+
for client in self.clients:
|
79
|
+
try:
|
80
|
+
if client.is_configured():
|
81
|
+
enabled = True
|
82
|
+
except exception.MissingConfigOptionException as ex:
|
83
|
+
LOG.error(ex.message)
|
84
|
+
return False
|
85
|
+
except exception.ConfigOptionBogusDefaultException as ex:
|
86
|
+
LOG.error(ex.message)
|
87
|
+
return False
|
88
|
+
return enabled
|
aprsd/client/fake.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from oslo_config import cfg
|
4
|
+
|
5
|
+
from aprsd import client
|
6
|
+
from aprsd.client import base
|
7
|
+
from aprsd.client.drivers import fake as fake_driver
|
8
|
+
from aprsd.utils import trace
|
9
|
+
|
10
|
+
|
11
|
+
CONF = cfg.CONF
|
12
|
+
LOG = logging.getLogger("APRSD")
|
13
|
+
|
14
|
+
|
15
|
+
class APRSDFakeClient(base.APRSClient, metaclass=trace.TraceWrapperMetaclass):
|
16
|
+
|
17
|
+
def stats(self) -> dict:
|
18
|
+
return {}
|
19
|
+
|
20
|
+
@staticmethod
|
21
|
+
def is_enabled():
|
22
|
+
if CONF.fake_client.enabled:
|
23
|
+
return True
|
24
|
+
return False
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def is_configured():
|
28
|
+
return APRSDFakeClient.is_enabled()
|
29
|
+
|
30
|
+
def is_alive(self):
|
31
|
+
return True
|
32
|
+
|
33
|
+
def close(self):
|
34
|
+
pass
|
35
|
+
|
36
|
+
def setup_connection(self):
|
37
|
+
self.connected = True
|
38
|
+
return fake_driver.APRSDFakeClient()
|
39
|
+
|
40
|
+
@staticmethod
|
41
|
+
def transport():
|
42
|
+
return client.TRANSPORT_FAKE
|
43
|
+
|
44
|
+
def decode_packet(self, *args, **kwargs):
|
45
|
+
LOG.debug(f"kwargs {kwargs}")
|
46
|
+
pkt = kwargs["packet"]
|
47
|
+
LOG.debug(f"Got an APRS Fake Packet '{pkt}'")
|
48
|
+
return pkt
|
aprsd/client/kiss.py
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
import aprslib
|
4
|
+
from oslo_config import cfg
|
5
|
+
|
6
|
+
from aprsd import client, exception
|
7
|
+
from aprsd.client import base
|
8
|
+
from aprsd.client.drivers import kiss
|
9
|
+
from aprsd.packets import core
|
10
|
+
|
11
|
+
|
12
|
+
CONF = cfg.CONF
|
13
|
+
LOG = logging.getLogger("APRSD")
|
14
|
+
|
15
|
+
|
16
|
+
class KISSClient(base.APRSClient):
|
17
|
+
|
18
|
+
_client = None
|
19
|
+
|
20
|
+
def stats(self) -> dict:
|
21
|
+
stats = {}
|
22
|
+
if self.is_configured():
|
23
|
+
return {
|
24
|
+
"transport": self.transport(),
|
25
|
+
}
|
26
|
+
return stats
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
def is_enabled():
|
30
|
+
"""Return if tcp or serial KISS is enabled."""
|
31
|
+
if CONF.kiss_serial.enabled:
|
32
|
+
return True
|
33
|
+
|
34
|
+
if CONF.kiss_tcp.enabled:
|
35
|
+
return True
|
36
|
+
|
37
|
+
return False
|
38
|
+
|
39
|
+
@staticmethod
|
40
|
+
def is_configured():
|
41
|
+
# Ensure that the config vars are correctly set
|
42
|
+
if KISSClient.is_enabled():
|
43
|
+
transport = KISSClient.transport()
|
44
|
+
if transport == client.TRANSPORT_SERIALKISS:
|
45
|
+
if not CONF.kiss_serial.device:
|
46
|
+
LOG.error("KISS serial enabled, but no device is set.")
|
47
|
+
raise exception.MissingConfigOptionException(
|
48
|
+
"kiss_serial.device is not set.",
|
49
|
+
)
|
50
|
+
elif transport == client.TRANSPORT_TCPKISS:
|
51
|
+
if not CONF.kiss_tcp.host:
|
52
|
+
LOG.error("KISS TCP enabled, but no host is set.")
|
53
|
+
raise exception.MissingConfigOptionException(
|
54
|
+
"kiss_tcp.host is not set.",
|
55
|
+
)
|
56
|
+
|
57
|
+
return True
|
58
|
+
return False
|
59
|
+
|
60
|
+
def is_alive(self):
|
61
|
+
if self._client:
|
62
|
+
return self._client.is_alive()
|
63
|
+
else:
|
64
|
+
return False
|
65
|
+
|
66
|
+
def close(self):
|
67
|
+
if self._client:
|
68
|
+
self._client.stop()
|
69
|
+
|
70
|
+
@staticmethod
|
71
|
+
def transport():
|
72
|
+
if CONF.kiss_serial.enabled:
|
73
|
+
return client.TRANSPORT_SERIALKISS
|
74
|
+
|
75
|
+
if CONF.kiss_tcp.enabled:
|
76
|
+
return client.TRANSPORT_TCPKISS
|
77
|
+
|
78
|
+
def decode_packet(self, *args, **kwargs):
|
79
|
+
"""We get a frame, which has to be decoded."""
|
80
|
+
LOG.debug(f"kwargs {kwargs}")
|
81
|
+
frame = kwargs["frame"]
|
82
|
+
LOG.debug(f"Got an APRS Frame '{frame}'")
|
83
|
+
# try and nuke the * from the fromcall sign.
|
84
|
+
# frame.header._source._ch = False
|
85
|
+
# payload = str(frame.payload.decode())
|
86
|
+
# msg = f"{str(frame.header)}:{payload}"
|
87
|
+
# msg = frame.tnc2
|
88
|
+
# LOG.debug(f"Decoding {msg}")
|
89
|
+
|
90
|
+
raw = aprslib.parse(str(frame))
|
91
|
+
packet = core.factory(raw)
|
92
|
+
if isinstance(packet, core.ThirdPartyPacket):
|
93
|
+
return packet.subpacket
|
94
|
+
else:
|
95
|
+
return packet
|
96
|
+
|
97
|
+
def setup_connection(self):
|
98
|
+
self._client = kiss.KISS3Client()
|
99
|
+
self.connected = True
|
100
|
+
return self._client
|
101
|
+
|
102
|
+
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
103
|
+
self._client.consumer(callback)
|
aprsd/client/stats.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
import threading
|
2
|
+
|
3
|
+
from oslo_config import cfg
|
4
|
+
import wrapt
|
5
|
+
|
6
|
+
from aprsd import client
|
7
|
+
from aprsd.utils import singleton
|
8
|
+
|
9
|
+
|
10
|
+
CONF = cfg.CONF
|
11
|
+
|
12
|
+
|
13
|
+
@singleton
|
14
|
+
class APRSClientStats:
|
15
|
+
|
16
|
+
lock = threading.Lock()
|
17
|
+
|
18
|
+
@wrapt.synchronized(lock)
|
19
|
+
def stats(self, serializable=False):
|
20
|
+
cl = client.client_factory.create()
|
21
|
+
stats = {
|
22
|
+
"transport": cl.transport(),
|
23
|
+
"filter": cl.filter,
|
24
|
+
"connected": cl.connected,
|
25
|
+
}
|
26
|
+
|
27
|
+
if cl.transport() == client.TRANSPORT_APRSIS:
|
28
|
+
stats["server_string"] = cl.client.server_string
|
29
|
+
keepalive = cl.client.aprsd_keepalive
|
30
|
+
if serializable:
|
31
|
+
keepalive = keepalive.isoformat()
|
32
|
+
stats["server_keepalive"] = keepalive
|
33
|
+
elif cl.transport() == client.TRANSPORT_TCPKISS:
|
34
|
+
stats["host"] = CONF.kiss_tcp.host
|
35
|
+
stats["port"] = CONF.kiss_tcp.port
|
36
|
+
elif cl.transport() == client.TRANSPORT_SERIALKISS:
|
37
|
+
stats["device"] = CONF.kiss_serial.device
|
38
|
+
return stats
|
aprsd/cmds/__init__.py
ADDED
File without changes
|
aprsd/cmds/completion.py
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
import click
|
2
|
+
import click.shell_completion
|
3
|
+
|
4
|
+
from aprsd.main import cli
|
5
|
+
|
6
|
+
|
7
|
+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
8
|
+
|
9
|
+
|
10
|
+
@cli.command()
|
11
|
+
@click.argument("shell", type=click.Choice(list(click.shell_completion._available_shells)))
|
12
|
+
def completion(shell):
|
13
|
+
"""Show the shell completion code"""
|
14
|
+
from click.utils import _detect_program_name
|
15
|
+
|
16
|
+
cls = click.shell_completion.get_completion_class(shell)
|
17
|
+
prog_name = _detect_program_name()
|
18
|
+
complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper()
|
19
|
+
print(cls(cli, {}, prog_name, complete_var).source())
|
20
|
+
print("# Add the following line to your shell configuration file to have aprsd command line completion")
|
21
|
+
print("# but remove the leading '#' character.")
|
22
|
+
print(f"# eval \"$(aprsd completion {shell})\"")
|
aprsd/cmds/dev.py
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
#
|
2
|
+
# Dev.py is used to help develop plugins
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# python included libs
|
6
|
+
import logging
|
7
|
+
|
8
|
+
import click
|
9
|
+
from oslo_config import cfg
|
10
|
+
|
11
|
+
from aprsd import cli_helper, conf, packets, plugin
|
12
|
+
# local imports here
|
13
|
+
from aprsd.client import base
|
14
|
+
from aprsd.main import cli
|
15
|
+
from aprsd.utils import trace
|
16
|
+
|
17
|
+
|
18
|
+
CONF = cfg.CONF
|
19
|
+
LOG = logging.getLogger("APRSD")
|
20
|
+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
21
|
+
|
22
|
+
|
23
|
+
@cli.group(help="Development type subcommands", context_settings=CONTEXT_SETTINGS)
|
24
|
+
@click.pass_context
|
25
|
+
def dev(ctx):
|
26
|
+
pass
|
27
|
+
|
28
|
+
|
29
|
+
@dev.command()
|
30
|
+
@cli_helper.add_options(cli_helper.common_options)
|
31
|
+
@click.option(
|
32
|
+
"--aprs-login",
|
33
|
+
envvar="APRS_LOGIN",
|
34
|
+
show_envvar=True,
|
35
|
+
help="What callsign to send the message from.",
|
36
|
+
)
|
37
|
+
@click.option(
|
38
|
+
"-p",
|
39
|
+
"--plugin",
|
40
|
+
"plugin_path",
|
41
|
+
show_default=True,
|
42
|
+
default=None,
|
43
|
+
help="The plugin to run. Ex: aprsd.plugins.ping.PingPlugin",
|
44
|
+
)
|
45
|
+
@click.option(
|
46
|
+
"-a",
|
47
|
+
"--all",
|
48
|
+
"load_all",
|
49
|
+
show_default=True,
|
50
|
+
is_flag=True,
|
51
|
+
default=False,
|
52
|
+
help="Load all the plugins in config?",
|
53
|
+
)
|
54
|
+
@click.option(
|
55
|
+
"-n",
|
56
|
+
"--num",
|
57
|
+
"number",
|
58
|
+
show_default=True,
|
59
|
+
default=1,
|
60
|
+
help="Number of times to call the plugin",
|
61
|
+
)
|
62
|
+
@click.argument("message", nargs=-1, required=True)
|
63
|
+
@click.pass_context
|
64
|
+
@cli_helper.process_standard_options
|
65
|
+
def test_plugin(
|
66
|
+
ctx,
|
67
|
+
aprs_login,
|
68
|
+
plugin_path,
|
69
|
+
load_all,
|
70
|
+
number,
|
71
|
+
message,
|
72
|
+
):
|
73
|
+
"""Test an individual APRSD plugin given a python path."""
|
74
|
+
|
75
|
+
CONF.log_opt_values(LOG, logging.DEBUG)
|
76
|
+
|
77
|
+
if not aprs_login:
|
78
|
+
if CONF.aprs_network.login == conf.client.DEFAULT_LOGIN:
|
79
|
+
click.echo("Must set --aprs_login or APRS_LOGIN")
|
80
|
+
ctx.exit(-1)
|
81
|
+
return
|
82
|
+
else:
|
83
|
+
fromcall = CONF.aprs_network.login
|
84
|
+
else:
|
85
|
+
fromcall = aprs_login
|
86
|
+
|
87
|
+
if not plugin_path:
|
88
|
+
click.echo(ctx.get_help())
|
89
|
+
click.echo("")
|
90
|
+
click.echo("Failed to provide -p option to test a plugin")
|
91
|
+
ctx.exit(-1)
|
92
|
+
return
|
93
|
+
|
94
|
+
if type(message) is tuple:
|
95
|
+
message = " ".join(message)
|
96
|
+
|
97
|
+
if CONF.trace_enabled:
|
98
|
+
trace.setup_tracing(["method", "api"])
|
99
|
+
|
100
|
+
base.APRSClient()
|
101
|
+
|
102
|
+
pm = plugin.PluginManager()
|
103
|
+
if load_all:
|
104
|
+
pm.setup_plugins()
|
105
|
+
obj = pm._create_class(plugin_path, plugin.APRSDPluginBase)
|
106
|
+
if not obj:
|
107
|
+
click.echo(ctx.get_help())
|
108
|
+
click.echo("")
|
109
|
+
ctx.fail(f"Failed to create object from plugin path '{plugin_path}'")
|
110
|
+
ctx.exit()
|
111
|
+
|
112
|
+
# Register the plugin they wanted tested.
|
113
|
+
LOG.info(
|
114
|
+
"Testing plugin {} Version {}".format(
|
115
|
+
obj.__class__, obj.version,
|
116
|
+
),
|
117
|
+
)
|
118
|
+
pm.register_msg(obj)
|
119
|
+
|
120
|
+
packet = packets.MessagePacket(
|
121
|
+
from_call=fromcall,
|
122
|
+
to_call=CONF.callsign,
|
123
|
+
msgNo=1,
|
124
|
+
message_text=message,
|
125
|
+
)
|
126
|
+
LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'")
|
127
|
+
|
128
|
+
for x in range(number):
|
129
|
+
replies = pm.run(packet)
|
130
|
+
# Plugin might have threads, so lets stop them so we can exit.
|
131
|
+
# obj.stop_threads()
|
132
|
+
for reply in replies:
|
133
|
+
if isinstance(reply, list):
|
134
|
+
# one of the plugins wants to send multiple messages
|
135
|
+
for subreply in reply:
|
136
|
+
if isinstance(subreply, packets.Packet):
|
137
|
+
LOG.info(subreply)
|
138
|
+
else:
|
139
|
+
LOG.info(
|
140
|
+
packets.MessagePacket(
|
141
|
+
from_call=CONF.callsign,
|
142
|
+
to_call=fromcall,
|
143
|
+
message_text=subreply,
|
144
|
+
),
|
145
|
+
)
|
146
|
+
elif isinstance(reply, packets.Packet):
|
147
|
+
# We have a message based object.
|
148
|
+
LOG.info(reply)
|
149
|
+
else:
|
150
|
+
# A plugin can return a null message flag which signals
|
151
|
+
# us that they processed the message correctly, but have
|
152
|
+
# nothing to reply with, so we avoid replying with a
|
153
|
+
# usage string
|
154
|
+
if reply is not packets.NULL_MESSAGE:
|
155
|
+
LOG.info(
|
156
|
+
packets.MessagePacket(
|
157
|
+
from_call=CONF.callsign,
|
158
|
+
to_call=fromcall,
|
159
|
+
message_text=reply,
|
160
|
+
),
|
161
|
+
)
|
162
|
+
pm.stop()
|