aprsd 3.4.3__py3-none-any.whl → 4.0.0__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/cli_helper.py +12 -5
- aprsd/client/aprsis.py +68 -17
- aprsd/client/base.py +60 -12
- aprsd/client/drivers/aprsis.py +11 -5
- aprsd/client/drivers/fake.py +15 -20
- aprsd/client/factory.py +6 -3
- aprsd/client/fake.py +5 -4
- aprsd/client/kiss.py +43 -7
- aprsd/client/stats.py +2 -22
- aprsd/cmds/completion.py +7 -4
- aprsd/cmds/dev.py +39 -43
- aprsd/cmds/fetch_stats.py +221 -69
- aprsd/cmds/healthcheck.py +7 -5
- aprsd/cmds/list_plugins.py +140 -134
- aprsd/cmds/listen.py +102 -11
- aprsd/cmds/server.py +71 -37
- aprsd/conf/__init__.py +1 -2
- aprsd/conf/client.py +3 -4
- aprsd/conf/common.py +29 -92
- aprsd/conf/log.py +2 -4
- aprsd/conf/opts.py +5 -4
- aprsd/conf/plugin_common.py +11 -121
- aprsd/exception.py +2 -0
- aprsd/log/log.py +7 -46
- aprsd/main.py +19 -9
- aprsd/packets/__init__.py +14 -4
- aprsd/packets/core.py +82 -91
- aprsd/packets/log.py +8 -8
- aprsd/packets/packet_list.py +18 -25
- aprsd/plugin.py +33 -15
- aprsd/plugins/fortune.py +2 -2
- aprsd/plugins/notify.py +1 -4
- aprsd/plugins/weather.py +10 -8
- aprsd/stats/__init__.py +0 -2
- aprsd/stats/collector.py +11 -3
- aprsd/threads/__init__.py +3 -2
- aprsd/threads/aprsd.py +57 -10
- aprsd/threads/{keep_alive.py → keepalive.py} +14 -37
- aprsd/threads/registry.py +3 -3
- aprsd/threads/rx.py +48 -17
- aprsd/threads/stats.py +2 -2
- aprsd/threads/tx.py +34 -10
- aprsd/utils/__init__.py +62 -15
- aprsd/utils/counter.py +15 -12
- aprsd/utils/json.py +9 -4
- aprsd/utils/keepalive_collector.py +55 -0
- aprsd/utils/trace.py +4 -4
- aprsd-4.0.0.dist-info/AUTHORS +1 -0
- aprsd-4.0.0.dist-info/METADATA +293 -0
- aprsd-4.0.0.dist-info/RECORD +74 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/WHEEL +1 -1
- aprsd/cmds/webchat.py +0 -674
- aprsd/conf/plugin_email.py +0 -105
- aprsd/plugins/email.py +0 -709
- aprsd/plugins/location.py +0 -179
- aprsd/threads/log_monitor.py +0 -121
- aprsd/web/__init__.py +0 -0
- aprsd/web/admin/__init__.py +0 -0
- aprsd/web/admin/static/css/index.css +0 -84
- aprsd/web/admin/static/css/prism.css +0 -4
- aprsd/web/admin/static/css/tabs.css +0 -35
- 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 +0 -235
- aprsd/web/admin/static/js/echarts.js +0 -465
- aprsd/web/admin/static/js/logs.js +0 -26
- aprsd/web/admin/static/js/main.js +0 -231
- aprsd/web/admin/static/js/prism.js +0 -12
- aprsd/web/admin/static/js/send-message.js +0 -114
- aprsd/web/admin/static/js/tabs.js +0 -28
- aprsd/web/admin/templates/index.html +0 -196
- aprsd/web/chat/static/css/chat.css +0 -115
- aprsd/web/chat/static/css/index.css +0 -66
- aprsd/web/chat/static/css/style.css.map +0 -1
- aprsd/web/chat/static/css/tabs.css +0 -41
- aprsd/web/chat/static/css/upstream/bootstrap.min.css +0 -6
- aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/google-fonts.css +0 -23
- aprsd/web/chat/static/css/upstream/jquery-ui.css +0 -1311
- aprsd/web/chat/static/css/upstream/jquery.toast.css +0 -28
- 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 +0 -3
- aprsd/web/chat/static/js/gps.js +0 -84
- aprsd/web/chat/static/js/main.js +0 -45
- aprsd/web/chat/static/js/send-message.js +0 -585
- aprsd/web/chat/static/js/tabs.js +0 -28
- aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +0 -7
- aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +0 -2
- aprsd/web/chat/static/js/upstream/jquery-ui.min.js +0 -13
- aprsd/web/chat/static/js/upstream/jquery.toast.js +0 -374
- aprsd/web/chat/static/js/upstream/semantic.min.js +0 -11
- aprsd/web/chat/static/js/upstream/socket.io.min.js +0 -7
- aprsd/web/chat/templates/index.html +0 -139
- aprsd/wsgi.py +0 -315
- aprsd-3.4.3.dist-info/AUTHORS +0 -13
- aprsd-3.4.3.dist-info/METADATA +0 -793
- aprsd-3.4.3.dist-info/RECORD +0 -133
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/LICENSE +0 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/entry_points.txt +0 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/top_level.txt +0 -0
aprsd/threads/rx.py
CHANGED
@@ -11,17 +11,18 @@ from aprsd.client import client_factory
|
|
11
11
|
from aprsd.packets import collector
|
12
12
|
from aprsd.packets import log as packet_log
|
13
13
|
from aprsd.threads import APRSDThread, tx
|
14
|
-
|
14
|
+
from aprsd.utils import trace
|
15
15
|
|
16
16
|
CONF = cfg.CONF
|
17
17
|
LOG = logging.getLogger("APRSD")
|
18
18
|
|
19
19
|
|
20
20
|
class APRSDRXThread(APRSDThread):
|
21
|
+
_client = None
|
22
|
+
|
21
23
|
def __init__(self, packet_queue):
|
22
24
|
super().__init__("RX_PKT")
|
23
25
|
self.packet_queue = packet_queue
|
24
|
-
self._client = client_factory.create()
|
25
26
|
|
26
27
|
def stop(self):
|
27
28
|
self.thread_stop = True
|
@@ -33,6 +34,12 @@ class APRSDRXThread(APRSDThread):
|
|
33
34
|
self._client = client_factory.create()
|
34
35
|
time.sleep(1)
|
35
36
|
return True
|
37
|
+
|
38
|
+
if not self._client.is_connected:
|
39
|
+
self._client = client_factory.create()
|
40
|
+
time.sleep(1)
|
41
|
+
return True
|
42
|
+
|
36
43
|
# setup the consumer of messages and block until a messages
|
37
44
|
try:
|
38
45
|
# This will register a packet consumer with aprslib
|
@@ -45,7 +52,9 @@ class APRSDRXThread(APRSDThread):
|
|
45
52
|
# kwargs. :(
|
46
53
|
# https://github.com/rossengeorgiev/aprs-python/pull/56
|
47
54
|
self._client.consumer(
|
48
|
-
self._process_packet,
|
55
|
+
self._process_packet,
|
56
|
+
raw=False,
|
57
|
+
blocking=False,
|
49
58
|
)
|
50
59
|
except (
|
51
60
|
aprslib.exceptions.ConnectionDrop,
|
@@ -63,6 +72,7 @@ class APRSDRXThread(APRSDThread):
|
|
63
72
|
self._client.reset()
|
64
73
|
time.sleep(5)
|
65
74
|
# Continue to loop
|
75
|
+
time.sleep(1)
|
66
76
|
return True
|
67
77
|
|
68
78
|
def _process_packet(self, *args, **kwargs):
|
@@ -84,6 +94,7 @@ class APRSDDupeRXThread(APRSDRXThread):
|
|
84
94
|
to be processed.
|
85
95
|
"""
|
86
96
|
|
97
|
+
@trace.trace
|
87
98
|
def process_packet(self, *args, **kwargs):
|
88
99
|
"""This handles the processing of an inbound packet.
|
89
100
|
|
@@ -96,7 +107,6 @@ class APRSDDupeRXThread(APRSDRXThread):
|
|
96
107
|
PluginProcessPacketThread for processing.
|
97
108
|
"""
|
98
109
|
packet = self._client.decode_packet(*args, **kwargs)
|
99
|
-
# LOG.debug(raw)
|
100
110
|
packet_log.log(packet)
|
101
111
|
pkt_list = packets.PacketList()
|
102
112
|
|
@@ -113,6 +123,12 @@ class APRSDDupeRXThread(APRSDRXThread):
|
|
113
123
|
# Find the packet in the list of already seen packets
|
114
124
|
# Based on the packet.key
|
115
125
|
found = pkt_list.find(packet)
|
126
|
+
if not packet.msgNo:
|
127
|
+
# If the packet doesn't have a message id
|
128
|
+
# then there is no reliable way to detect
|
129
|
+
# if it's a dupe, so we just pass it on.
|
130
|
+
# it shouldn't get acked either.
|
131
|
+
found = False
|
116
132
|
except KeyError:
|
117
133
|
found = False
|
118
134
|
|
@@ -123,7 +139,9 @@ class APRSDDupeRXThread(APRSDRXThread):
|
|
123
139
|
elif packet.timestamp - found.timestamp < CONF.packet_dupe_timeout:
|
124
140
|
# If the packet came in within N seconds of the
|
125
141
|
# Last time seeing the packet, then we drop it as a dupe.
|
126
|
-
LOG.warning(
|
142
|
+
LOG.warning(
|
143
|
+
f"Packet {packet.from_call}:{packet.msgNo} already tracked, dropping."
|
144
|
+
)
|
127
145
|
else:
|
128
146
|
LOG.warning(
|
129
147
|
f"Packet {packet.from_call}:{packet.msgNo} already tracked "
|
@@ -134,7 +152,7 @@ class APRSDDupeRXThread(APRSDRXThread):
|
|
134
152
|
|
135
153
|
|
136
154
|
class APRSDPluginRXThread(APRSDDupeRXThread):
|
137
|
-
""""Process received packets.
|
155
|
+
""" "Process received packets.
|
138
156
|
|
139
157
|
For backwards compatibility, we keep the APRSDPluginRXThread.
|
140
158
|
"""
|
@@ -151,6 +169,11 @@ class APRSDProcessPacketThread(APRSDThread):
|
|
151
169
|
def __init__(self, packet_queue):
|
152
170
|
self.packet_queue = packet_queue
|
153
171
|
super().__init__("ProcessPKT")
|
172
|
+
if not CONF.enable_sending_ack_packets:
|
173
|
+
LOG.warning(
|
174
|
+
"Sending ack packets is disabled, messages "
|
175
|
+
"will not be acknowledged.",
|
176
|
+
)
|
154
177
|
|
155
178
|
def process_ack_packet(self, packet):
|
156
179
|
"""We got an ack for a message, no need to resend it."""
|
@@ -214,13 +237,14 @@ class APRSDProcessPacketThread(APRSDThread):
|
|
214
237
|
# It's a MessagePacket and it's for us!
|
215
238
|
# let any threads do their thing, then ack
|
216
239
|
# send an ack last
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
240
|
+
if msg_id:
|
241
|
+
tx.send(
|
242
|
+
packets.AckPacket(
|
243
|
+
from_call=CONF.callsign,
|
244
|
+
to_call=from_call,
|
245
|
+
msgNo=msg_id,
|
246
|
+
),
|
247
|
+
)
|
224
248
|
|
225
249
|
self.process_our_message_packet(packet)
|
226
250
|
else:
|
@@ -228,7 +252,8 @@ class APRSDProcessPacketThread(APRSDThread):
|
|
228
252
|
self.process_other_packet(packet, for_us=False)
|
229
253
|
else:
|
230
254
|
self.process_other_packet(
|
231
|
-
packet,
|
255
|
+
packet,
|
256
|
+
for_us=(to_call.lower() == our_call),
|
232
257
|
)
|
233
258
|
LOG.debug(f"Packet processing complete for pkt '{packet.key}'")
|
234
259
|
return False
|
@@ -240,7 +265,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|
240
265
|
def process_other_packet(self, packet, for_us=False):
|
241
266
|
"""Process an APRS Packet that isn't a message or ack"""
|
242
267
|
if not for_us:
|
243
|
-
LOG.info("Got a packet
|
268
|
+
LOG.info("Got a packet meant for someone else '{packet.to_call}'")
|
244
269
|
else:
|
245
270
|
LOG.info("Got a non AckPacket/MessagePacket")
|
246
271
|
|
@@ -328,8 +353,14 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
|
328
353
|
# If the message was for us and we didn't have a
|
329
354
|
# response, then we send a usage statement.
|
330
355
|
if to_call == CONF.callsign and not replied:
|
331
|
-
|
332
|
-
|
356
|
+
# Tailor the messages accordingly
|
357
|
+
if CONF.load_help_plugin:
|
358
|
+
LOG.warning("Sending help!")
|
359
|
+
message_text = "Unknown command! Send 'help' message for help"
|
360
|
+
else:
|
361
|
+
LOG.warning("Unknown command!")
|
362
|
+
message_text = "Unknown command!"
|
363
|
+
|
333
364
|
tx.send(
|
334
365
|
packets.MessagePacket(
|
335
366
|
from_call=CONF.callsign,
|
aprsd/threads/stats.py
CHANGED
@@ -2,20 +2,20 @@ import logging
|
|
2
2
|
import threading
|
3
3
|
import time
|
4
4
|
|
5
|
-
from oslo_config import cfg
|
6
5
|
import wrapt
|
6
|
+
from oslo_config import cfg
|
7
7
|
|
8
8
|
from aprsd.stats import collector
|
9
9
|
from aprsd.threads import APRSDThread
|
10
10
|
from aprsd.utils import objectstore
|
11
11
|
|
12
|
-
|
13
12
|
CONF = cfg.CONF
|
14
13
|
LOG = logging.getLogger("APRSD")
|
15
14
|
|
16
15
|
|
17
16
|
class StatsStore(objectstore.ObjectStoreMixin):
|
18
17
|
"""Container to save the stats from the collector."""
|
18
|
+
|
19
19
|
lock = threading.Lock()
|
20
20
|
data = {}
|
21
21
|
|
aprsd/threads/tx.py
CHANGED
@@ -2,20 +2,18 @@ import logging
|
|
2
2
|
import threading
|
3
3
|
import time
|
4
4
|
|
5
|
+
import wrapt
|
5
6
|
from oslo_config import cfg
|
6
7
|
from rush import quota, throttle
|
7
8
|
from rush.contrib import decorator
|
8
9
|
from rush.limiters import periodic
|
9
10
|
from rush.stores import dictionary
|
10
|
-
import wrapt
|
11
11
|
|
12
12
|
from aprsd import conf # noqa
|
13
13
|
from aprsd import threads as aprsd_threads
|
14
14
|
from aprsd.client import client_factory
|
15
|
-
from aprsd.packets import collector, core
|
15
|
+
from aprsd.packets import collector, core, tracker
|
16
16
|
from aprsd.packets import log as packet_log
|
17
|
-
from aprsd.packets import tracker
|
18
|
-
|
19
17
|
|
20
18
|
CONF = cfg.CONF
|
21
19
|
LOG = logging.getLogger("APRSD")
|
@@ -48,12 +46,15 @@ def send(packet: core.Packet, direct=False, aprs_client=None):
|
|
48
46
|
"""Send a packet either in a thread or directly to the client."""
|
49
47
|
# prepare the packet for sending.
|
50
48
|
# This constructs the packet.raw
|
51
|
-
packet.prepare()
|
49
|
+
packet.prepare(create_msg_number=True)
|
52
50
|
# Have to call the collector to track the packet
|
53
51
|
# After prepare, as prepare assigns the msgNo
|
54
52
|
collector.PacketCollector().tx(packet)
|
55
53
|
if isinstance(packet, core.AckPacket):
|
56
|
-
|
54
|
+
if CONF.enable_sending_ack_packets:
|
55
|
+
_send_ack(packet, direct=direct, aprs_client=aprs_client)
|
56
|
+
else:
|
57
|
+
LOG.info("Sending ack packets is disabled. Not sending AckPacket.")
|
57
58
|
else:
|
58
59
|
_send_packet(packet, direct=direct, aprs_client=aprs_client)
|
59
60
|
|
@@ -89,6 +90,9 @@ def _send_direct(packet, aprs_client=None):
|
|
89
90
|
except Exception as e:
|
90
91
|
LOG.error(f"Failed to send packet: {packet}")
|
91
92
|
LOG.error(e)
|
93
|
+
return False
|
94
|
+
else:
|
95
|
+
return True
|
92
96
|
|
93
97
|
|
94
98
|
class SendPacketThread(aprsd_threads.APRSDThread):
|
@@ -150,8 +154,17 @@ class SendPacketThread(aprsd_threads.APRSDThread):
|
|
150
154
|
# no attempt time, so lets send it, and start
|
151
155
|
# tracking the time.
|
152
156
|
packet.last_send_time = int(round(time.time()))
|
153
|
-
|
154
|
-
|
157
|
+
sent = False
|
158
|
+
try:
|
159
|
+
sent = _send_direct(packet)
|
160
|
+
except Exception:
|
161
|
+
LOG.error(f"Failed to send packet: {packet}")
|
162
|
+
else:
|
163
|
+
# If an exception happens while sending
|
164
|
+
# we don't want this attempt to count
|
165
|
+
# against the packet
|
166
|
+
if sent:
|
167
|
+
packet.send_count += 1
|
155
168
|
|
156
169
|
time.sleep(1)
|
157
170
|
# Make sure we get called again.
|
@@ -199,8 +212,18 @@ class SendAckThread(aprsd_threads.APRSDThread):
|
|
199
212
|
send_now = True
|
200
213
|
|
201
214
|
if send_now:
|
202
|
-
|
203
|
-
|
215
|
+
sent = False
|
216
|
+
try:
|
217
|
+
sent = _send_direct(self.packet)
|
218
|
+
except Exception:
|
219
|
+
LOG.error(f"Failed to send packet: {self.packet}")
|
220
|
+
else:
|
221
|
+
# If an exception happens while sending
|
222
|
+
# we don't want this attempt to count
|
223
|
+
# against the packet
|
224
|
+
if sent:
|
225
|
+
self.packet.send_count += 1
|
226
|
+
|
204
227
|
self.packet.last_send_time = int(round(time.time()))
|
205
228
|
|
206
229
|
time.sleep(1)
|
@@ -213,6 +236,7 @@ class BeaconSendThread(aprsd_threads.APRSDThread):
|
|
213
236
|
|
214
237
|
Settings are in the [DEFAULT] section of the config file.
|
215
238
|
"""
|
239
|
+
|
216
240
|
_loop_cnt: int = 1
|
217
241
|
|
218
242
|
def __init__(self):
|
aprsd/utils/__init__.py
CHANGED
@@ -13,11 +13,11 @@ import update_checker
|
|
13
13
|
import aprsd
|
14
14
|
|
15
15
|
from .fuzzyclock import fuzzy # noqa: F401
|
16
|
+
|
16
17
|
# Make these available by anyone importing
|
17
18
|
# aprsd.utils
|
18
19
|
from .ring_buffer import RingBuffer # noqa: F401
|
19
20
|
|
20
|
-
|
21
21
|
if sys.version_info.major == 3 and sys.version_info.minor >= 3:
|
22
22
|
from collections.abc import MutableMapping
|
23
23
|
else:
|
@@ -26,11 +26,13 @@ else:
|
|
26
26
|
|
27
27
|
def singleton(cls):
|
28
28
|
"""Make a class a Singleton class (only one instance)"""
|
29
|
+
|
29
30
|
@functools.wraps(cls)
|
30
31
|
def wrapper_singleton(*args, **kwargs):
|
31
32
|
if wrapper_singleton.instance is None:
|
32
33
|
wrapper_singleton.instance = cls(*args, **kwargs)
|
33
34
|
return wrapper_singleton.instance
|
35
|
+
|
34
36
|
wrapper_singleton.instance = None
|
35
37
|
return wrapper_singleton
|
36
38
|
|
@@ -170,23 +172,40 @@ def load_entry_points(group):
|
|
170
172
|
try:
|
171
173
|
ep.load()
|
172
174
|
except Exception as e:
|
173
|
-
print(
|
175
|
+
print(
|
176
|
+
f"Extension {ep.name} of group {group} failed to load with {e}",
|
177
|
+
file=sys.stderr,
|
178
|
+
)
|
174
179
|
print(traceback.format_exc(), file=sys.stderr)
|
175
180
|
|
176
181
|
|
177
|
-
def calculate_initial_compass_bearing(
|
178
|
-
|
182
|
+
def calculate_initial_compass_bearing(point_a, point_b):
|
183
|
+
"""
|
184
|
+
Calculates the bearing between two points.
|
185
|
+
The formulae used is the following:
|
186
|
+
θ = atan2(sin(Δlong).cos(lat2),
|
187
|
+
cos(lat1).sin(lat2) − sin(lat1).cos(lat2).cos(Δlong))
|
188
|
+
:Parameters:
|
189
|
+
- `pointA: The tuple representing the latitude/longitude for the
|
190
|
+
first point. Latitude and longitude must be in decimal degrees
|
191
|
+
- `pointB: The tuple representing the latitude/longitude for the
|
192
|
+
second point. Latitude and longitude must be in decimal degrees
|
193
|
+
:Returns:
|
194
|
+
The bearing in degrees
|
195
|
+
:Returns Type:
|
196
|
+
float
|
197
|
+
"""
|
198
|
+
if (type(point_a) != tuple) or (type(point_b) != tuple): # noqa: E721
|
179
199
|
raise TypeError("Only tuples are supported as arguments")
|
180
200
|
|
181
|
-
lat1 = math.radians(float(
|
182
|
-
lat2 = math.radians(float(
|
201
|
+
lat1 = math.radians(float(point_a[0]))
|
202
|
+
lat2 = math.radians(float(point_b[0]))
|
183
203
|
|
184
|
-
diff_long = math.radians(float(
|
204
|
+
diff_long = math.radians(float(point_b[1]) - float(point_a[1]))
|
185
205
|
|
186
206
|
x = math.sin(diff_long) * math.cos(lat2)
|
187
207
|
y = math.cos(lat1) * math.sin(lat2) - (
|
188
|
-
math.sin(lat1)
|
189
|
-
* math.cos(lat2) * math.cos(diff_long)
|
208
|
+
math.sin(lat1) * math.cos(lat2) * math.cos(diff_long)
|
190
209
|
)
|
191
210
|
|
192
211
|
initial_bearing = math.atan2(x, y)
|
@@ -203,15 +222,43 @@ def calculate_initial_compass_bearing(start, end):
|
|
203
222
|
def degrees_to_cardinal(bearing, full_string=False):
|
204
223
|
if full_string:
|
205
224
|
directions = [
|
206
|
-
"North",
|
207
|
-
"
|
208
|
-
"
|
225
|
+
"North",
|
226
|
+
"North-Northeast",
|
227
|
+
"Northeast",
|
228
|
+
"East-Northeast",
|
229
|
+
"East",
|
230
|
+
"East-Southeast",
|
231
|
+
"Southeast",
|
232
|
+
"South-Southeast",
|
233
|
+
"South",
|
234
|
+
"South-Southwest",
|
235
|
+
"Southwest",
|
236
|
+
"West-Southwest",
|
237
|
+
"West",
|
238
|
+
"West-Northwest",
|
239
|
+
"Northwest",
|
240
|
+
"North-Northwest",
|
241
|
+
"North",
|
209
242
|
]
|
210
243
|
else:
|
211
244
|
directions = [
|
212
|
-
"N",
|
213
|
-
"
|
214
|
-
"
|
245
|
+
"N",
|
246
|
+
"NNE",
|
247
|
+
"NE",
|
248
|
+
"ENE",
|
249
|
+
"E",
|
250
|
+
"ESE",
|
251
|
+
"SE",
|
252
|
+
"SSE",
|
253
|
+
"S",
|
254
|
+
"SSW",
|
255
|
+
"SW",
|
256
|
+
"WSW",
|
257
|
+
"W",
|
258
|
+
"WNW",
|
259
|
+
"NW",
|
260
|
+
"NNW",
|
261
|
+
"N",
|
215
262
|
]
|
216
263
|
|
217
264
|
cardinal = directions[round(bearing / 22.5)]
|
aprsd/utils/counter.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
from multiprocessing import RawValue
|
2
1
|
import random
|
3
2
|
import threading
|
4
3
|
|
@@ -10,12 +9,12 @@ MAX_PACKET_ID = 9999
|
|
10
9
|
|
11
10
|
class PacketCounter:
|
12
11
|
"""
|
13
|
-
Global Packet
|
12
|
+
Global Packet ID counter class.
|
14
13
|
|
15
|
-
This is a singleton
|
14
|
+
This is a singleton-based class that keeps
|
16
15
|
an incrementing counter for all packets to
|
17
|
-
be sent.
|
18
|
-
message
|
16
|
+
be sent. All new Packet objects get a new
|
17
|
+
message ID, which is the next number available
|
19
18
|
from the PacketCounter.
|
20
19
|
|
21
20
|
"""
|
@@ -27,25 +26,29 @@ class PacketCounter:
|
|
27
26
|
"""Make this a singleton class."""
|
28
27
|
if cls._instance is None:
|
29
28
|
cls._instance = super().__new__(cls, *args, **kwargs)
|
30
|
-
cls._instance.
|
29
|
+
cls._instance._val = random.randint(1, MAX_PACKET_ID) # Initialize counter
|
31
30
|
return cls._instance
|
32
31
|
|
33
32
|
@wrapt.synchronized(lock)
|
34
33
|
def increment(self):
|
35
|
-
if
|
36
|
-
|
34
|
+
"""Increment the counter, reset if it exceeds MAX_PACKET_ID."""
|
35
|
+
if self._val == MAX_PACKET_ID:
|
36
|
+
self._val = 1
|
37
37
|
else:
|
38
|
-
self.
|
38
|
+
self._val += 1
|
39
39
|
|
40
40
|
@property
|
41
41
|
@wrapt.synchronized(lock)
|
42
42
|
def value(self):
|
43
|
-
|
43
|
+
"""Get the current value as a string."""
|
44
|
+
return str(self._val)
|
44
45
|
|
45
46
|
@wrapt.synchronized(lock)
|
46
47
|
def __repr__(self):
|
47
|
-
|
48
|
+
"""String representation of the current value."""
|
49
|
+
return str(self._val)
|
48
50
|
|
49
51
|
@wrapt.synchronized(lock)
|
50
52
|
def __str__(self):
|
51
|
-
|
53
|
+
"""String representation of the current value."""
|
54
|
+
return str(self._val)
|
aprsd/utils/json.py
CHANGED
@@ -10,8 +10,13 @@ class EnhancedJSONEncoder(json.JSONEncoder):
|
|
10
10
|
def default(self, obj):
|
11
11
|
if isinstance(obj, datetime.datetime):
|
12
12
|
args = (
|
13
|
-
"year",
|
14
|
-
"
|
13
|
+
"year",
|
14
|
+
"month",
|
15
|
+
"day",
|
16
|
+
"hour",
|
17
|
+
"minute",
|
18
|
+
"second",
|
19
|
+
"microsecond",
|
15
20
|
)
|
16
21
|
return {
|
17
22
|
"__type__": "datetime.datetime",
|
@@ -63,10 +68,10 @@ class SimpleJSONEncoder(json.JSONEncoder):
|
|
63
68
|
|
64
69
|
|
65
70
|
class EnhancedJSONDecoder(json.JSONDecoder):
|
66
|
-
|
67
71
|
def __init__(self, *args, **kwargs):
|
68
72
|
super().__init__(
|
69
|
-
*args,
|
73
|
+
*args,
|
74
|
+
object_hook=self.object_hook,
|
70
75
|
**kwargs,
|
71
76
|
)
|
72
77
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Callable, Protocol, runtime_checkable
|
3
|
+
|
4
|
+
from aprsd.utils import singleton
|
5
|
+
|
6
|
+
LOG = logging.getLogger("APRSD")
|
7
|
+
|
8
|
+
|
9
|
+
@runtime_checkable
|
10
|
+
class KeepAliveProducer(Protocol):
|
11
|
+
"""The KeepAliveProducer protocol is used to define the interface for running Keepalive checks."""
|
12
|
+
|
13
|
+
def keepalive_check(self) -> dict:
|
14
|
+
"""Check for keepalive."""
|
15
|
+
...
|
16
|
+
|
17
|
+
def keepalive_log(self):
|
18
|
+
"""Log any keepalive information."""
|
19
|
+
...
|
20
|
+
|
21
|
+
|
22
|
+
@singleton
|
23
|
+
class KeepAliveCollector:
|
24
|
+
"""The Collector class is used to collect stats from multiple StatsProducer instances."""
|
25
|
+
|
26
|
+
def __init__(self):
|
27
|
+
self.producers: list[Callable] = []
|
28
|
+
|
29
|
+
def check(self) -> None:
|
30
|
+
"""Do any keepalive checks."""
|
31
|
+
for name in self.producers:
|
32
|
+
cls = name()
|
33
|
+
try:
|
34
|
+
cls.keepalive_check()
|
35
|
+
except Exception as e:
|
36
|
+
LOG.error(f"Error in producer {name} (check): {e}")
|
37
|
+
|
38
|
+
def log(self) -> None:
|
39
|
+
"""Log any relevant information during a KeepAlive check"""
|
40
|
+
for name in self.producers:
|
41
|
+
cls = name()
|
42
|
+
try:
|
43
|
+
cls.keepalive_log()
|
44
|
+
except Exception as e:
|
45
|
+
LOG.error(f"Error in producer {name} (check): {e}")
|
46
|
+
|
47
|
+
def register(self, producer_name: Callable):
|
48
|
+
if not isinstance(producer_name, KeepAliveProducer):
|
49
|
+
raise TypeError(f"Producer {producer_name} is not a KeepAliveProducer")
|
50
|
+
self.producers.append(producer_name)
|
51
|
+
|
52
|
+
def unregister(self, producer_name: Callable):
|
53
|
+
if not isinstance(producer_name, KeepAliveProducer):
|
54
|
+
raise TypeError(f"Producer {producer_name} is not a KeepAliveProducer")
|
55
|
+
self.producers.remove(producer_name)
|
aprsd/utils/trace.py
CHANGED
@@ -5,7 +5,6 @@ import logging
|
|
5
5
|
import time
|
6
6
|
import types
|
7
7
|
|
8
|
-
|
9
8
|
VALID_TRACE_FLAGS = {"method", "api"}
|
10
9
|
TRACE_API = False
|
11
10
|
TRACE_METHOD = False
|
@@ -27,8 +26,8 @@ def trace(*dec_args, **dec_kwargs):
|
|
27
26
|
"""
|
28
27
|
|
29
28
|
def _decorator(f):
|
30
|
-
|
31
|
-
|
29
|
+
func_name = f.__qualname__
|
30
|
+
func_file = "/".join(f.__code__.co_filename.split("/")[-4:])
|
32
31
|
|
33
32
|
@functools.wraps(f)
|
34
33
|
def trace_logging_wrapper(*args, **kwargs):
|
@@ -46,10 +45,11 @@ def trace(*dec_args, **dec_kwargs):
|
|
46
45
|
|
47
46
|
if pass_filter:
|
48
47
|
logger.debug(
|
49
|
-
"==> %(func)s: call %(all_args)r",
|
48
|
+
"==> %(func)s: call %(all_args)r file: %(file)s",
|
50
49
|
{
|
51
50
|
"func": func_name,
|
52
51
|
"all_args": str(all_args),
|
52
|
+
"file": func_file,
|
53
53
|
},
|
54
54
|
)
|
55
55
|
|
@@ -0,0 +1 @@
|
|
1
|
+
waboring@hemna.com : 1
|