aprsd 1.0.0__py3-none-any.whl → 3.4.1__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.
Files changed (141) hide show
  1. aprsd/__init__.py +6 -4
  2. aprsd/cli_helper.py +151 -0
  3. aprsd/client/__init__.py +13 -0
  4. aprsd/client/aprsis.py +132 -0
  5. aprsd/client/base.py +105 -0
  6. aprsd/client/drivers/__init__.py +0 -0
  7. aprsd/client/drivers/aprsis.py +224 -0
  8. aprsd/client/drivers/fake.py +73 -0
  9. aprsd/client/drivers/kiss.py +119 -0
  10. aprsd/client/factory.py +88 -0
  11. aprsd/client/fake.py +48 -0
  12. aprsd/client/kiss.py +103 -0
  13. aprsd/client/stats.py +38 -0
  14. aprsd/cmds/__init__.py +0 -0
  15. aprsd/cmds/completion.py +22 -0
  16. aprsd/cmds/dev.py +162 -0
  17. aprsd/cmds/fetch_stats.py +156 -0
  18. aprsd/cmds/healthcheck.py +86 -0
  19. aprsd/cmds/list_plugins.py +319 -0
  20. aprsd/cmds/listen.py +230 -0
  21. aprsd/cmds/send_message.py +174 -0
  22. aprsd/cmds/server.py +142 -0
  23. aprsd/cmds/webchat.py +681 -0
  24. aprsd/conf/__init__.py +56 -0
  25. aprsd/conf/client.py +131 -0
  26. aprsd/conf/common.py +302 -0
  27. aprsd/conf/log.py +65 -0
  28. aprsd/conf/opts.py +80 -0
  29. aprsd/conf/plugin_common.py +191 -0
  30. aprsd/conf/plugin_email.py +105 -0
  31. aprsd/exception.py +13 -0
  32. aprsd/log/__init__.py +0 -0
  33. aprsd/log/log.py +138 -0
  34. aprsd/main.py +104 -867
  35. aprsd/messaging.py +4 -0
  36. aprsd/packets/__init__.py +12 -0
  37. aprsd/packets/collector.py +56 -0
  38. aprsd/packets/core.py +823 -0
  39. aprsd/packets/log.py +143 -0
  40. aprsd/packets/packet_list.py +116 -0
  41. aprsd/packets/seen_list.py +54 -0
  42. aprsd/packets/tracker.py +109 -0
  43. aprsd/packets/watch_list.py +122 -0
  44. aprsd/plugin.py +475 -284
  45. aprsd/plugin_utils.py +86 -0
  46. aprsd/plugins/__init__.py +0 -0
  47. aprsd/plugins/email.py +709 -0
  48. aprsd/plugins/fortune.py +61 -0
  49. aprsd/plugins/location.py +179 -0
  50. aprsd/plugins/notify.py +61 -0
  51. aprsd/plugins/ping.py +31 -0
  52. aprsd/plugins/time.py +115 -0
  53. aprsd/plugins/version.py +31 -0
  54. aprsd/plugins/weather.py +405 -0
  55. aprsd/stats/__init__.py +20 -0
  56. aprsd/stats/app.py +49 -0
  57. aprsd/stats/collector.py +38 -0
  58. aprsd/threads/__init__.py +11 -0
  59. aprsd/threads/aprsd.py +119 -0
  60. aprsd/threads/keep_alive.py +124 -0
  61. aprsd/threads/log_monitor.py +121 -0
  62. aprsd/threads/registry.py +56 -0
  63. aprsd/threads/rx.py +354 -0
  64. aprsd/threads/stats.py +44 -0
  65. aprsd/threads/tx.py +255 -0
  66. aprsd/utils/__init__.py +163 -0
  67. aprsd/utils/counter.py +51 -0
  68. aprsd/utils/json.py +80 -0
  69. aprsd/utils/objectstore.py +123 -0
  70. aprsd/utils/ring_buffer.py +40 -0
  71. aprsd/utils/trace.py +180 -0
  72. aprsd/web/__init__.py +0 -0
  73. aprsd/web/admin/__init__.py +0 -0
  74. aprsd/web/admin/static/css/index.css +84 -0
  75. aprsd/web/admin/static/css/prism.css +4 -0
  76. aprsd/web/admin/static/css/tabs.css +35 -0
  77. aprsd/web/admin/static/images/Untitled.png +0 -0
  78. aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
  79. aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
  80. aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
  81. aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
  82. aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
  83. aprsd/web/admin/static/js/charts.js +235 -0
  84. aprsd/web/admin/static/js/echarts.js +465 -0
  85. aprsd/web/admin/static/js/logs.js +26 -0
  86. aprsd/web/admin/static/js/main.js +231 -0
  87. aprsd/web/admin/static/js/prism.js +12 -0
  88. aprsd/web/admin/static/js/send-message.js +114 -0
  89. aprsd/web/admin/static/js/tabs.js +28 -0
  90. aprsd/web/admin/templates/index.html +196 -0
  91. aprsd/web/chat/static/css/chat.css +115 -0
  92. aprsd/web/chat/static/css/index.css +66 -0
  93. aprsd/web/chat/static/css/style.css.map +1 -0
  94. aprsd/web/chat/static/css/tabs.css +41 -0
  95. aprsd/web/chat/static/css/upstream/bootstrap.min.css +6 -0
  96. aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
  97. aprsd/web/chat/static/css/upstream/google-fonts.css +23 -0
  98. aprsd/web/chat/static/css/upstream/jquery-ui.css +1311 -0
  99. aprsd/web/chat/static/css/upstream/jquery.toast.css +28 -0
  100. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
  101. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
  102. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
  103. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
  104. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
  105. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
  106. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
  107. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
  108. aprsd/web/chat/static/images/Untitled.png +0 -0
  109. aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
  110. aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
  111. aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
  112. aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
  113. aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
  114. aprsd/web/chat/static/images/globe.svg +3 -0
  115. aprsd/web/chat/static/js/gps.js +84 -0
  116. aprsd/web/chat/static/js/main.js +45 -0
  117. aprsd/web/chat/static/js/send-message.js +585 -0
  118. aprsd/web/chat/static/js/tabs.js +28 -0
  119. aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +7 -0
  120. aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +2 -0
  121. aprsd/web/chat/static/js/upstream/jquery-ui.min.js +13 -0
  122. aprsd/web/chat/static/js/upstream/jquery.toast.js +374 -0
  123. aprsd/web/chat/static/js/upstream/semantic.min.js +11 -0
  124. aprsd/web/chat/static/js/upstream/socket.io.min.js +7 -0
  125. aprsd/web/chat/templates/index.html +139 -0
  126. aprsd/wsgi.py +315 -0
  127. aprsd-3.4.1.dist-info/AUTHORS +13 -0
  128. aprsd-3.4.1.dist-info/LICENSE +175 -0
  129. aprsd-3.4.1.dist-info/METADATA +799 -0
  130. aprsd-3.4.1.dist-info/RECORD +134 -0
  131. {aprsd-1.0.0.dist-info → aprsd-3.4.1.dist-info}/WHEEL +1 -1
  132. aprsd-3.4.1.dist-info/entry_points.txt +8 -0
  133. aprsd/fake_aprs.py +0 -83
  134. aprsd/utils.py +0 -166
  135. aprsd-1.0.0.dist-info/AUTHORS +0 -6
  136. aprsd-1.0.0.dist-info/METADATA +0 -181
  137. aprsd-1.0.0.dist-info/RECORD +0 -13
  138. aprsd-1.0.0.dist-info/entry_points.txt +0 -4
  139. aprsd-1.0.0.dist-info/pbr.json +0 -1
  140. /aprsd/{fuzzyclock.py → utils/fuzzyclock.py} +0 -0
  141. {aprsd-1.0.0.dist-info → aprsd-3.4.1.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)
@@ -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
@@ -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()