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.
Files changed (117) hide show
  1. aprsd/cli_helper.py +12 -5
  2. aprsd/client/aprsis.py +68 -17
  3. aprsd/client/base.py +60 -12
  4. aprsd/client/drivers/aprsis.py +11 -5
  5. aprsd/client/drivers/fake.py +15 -20
  6. aprsd/client/factory.py +6 -3
  7. aprsd/client/fake.py +5 -4
  8. aprsd/client/kiss.py +43 -7
  9. aprsd/client/stats.py +2 -22
  10. aprsd/cmds/completion.py +7 -4
  11. aprsd/cmds/dev.py +39 -43
  12. aprsd/cmds/fetch_stats.py +221 -69
  13. aprsd/cmds/healthcheck.py +7 -5
  14. aprsd/cmds/list_plugins.py +140 -134
  15. aprsd/cmds/listen.py +102 -11
  16. aprsd/cmds/server.py +71 -37
  17. aprsd/conf/__init__.py +1 -2
  18. aprsd/conf/client.py +3 -4
  19. aprsd/conf/common.py +29 -92
  20. aprsd/conf/log.py +2 -4
  21. aprsd/conf/opts.py +5 -4
  22. aprsd/conf/plugin_common.py +11 -121
  23. aprsd/exception.py +2 -0
  24. aprsd/log/log.py +7 -46
  25. aprsd/main.py +19 -9
  26. aprsd/packets/__init__.py +14 -4
  27. aprsd/packets/core.py +82 -91
  28. aprsd/packets/log.py +8 -8
  29. aprsd/packets/packet_list.py +18 -25
  30. aprsd/plugin.py +33 -15
  31. aprsd/plugins/fortune.py +2 -2
  32. aprsd/plugins/notify.py +1 -4
  33. aprsd/plugins/weather.py +10 -8
  34. aprsd/stats/__init__.py +0 -2
  35. aprsd/stats/collector.py +11 -3
  36. aprsd/threads/__init__.py +3 -2
  37. aprsd/threads/aprsd.py +57 -10
  38. aprsd/threads/{keep_alive.py → keepalive.py} +14 -37
  39. aprsd/threads/registry.py +3 -3
  40. aprsd/threads/rx.py +48 -17
  41. aprsd/threads/stats.py +2 -2
  42. aprsd/threads/tx.py +34 -10
  43. aprsd/utils/__init__.py +62 -15
  44. aprsd/utils/counter.py +15 -12
  45. aprsd/utils/json.py +9 -4
  46. aprsd/utils/keepalive_collector.py +55 -0
  47. aprsd/utils/trace.py +4 -4
  48. aprsd-4.0.0.dist-info/AUTHORS +1 -0
  49. aprsd-4.0.0.dist-info/METADATA +293 -0
  50. aprsd-4.0.0.dist-info/RECORD +74 -0
  51. {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/WHEEL +1 -1
  52. aprsd/cmds/webchat.py +0 -674
  53. aprsd/conf/plugin_email.py +0 -105
  54. aprsd/plugins/email.py +0 -709
  55. aprsd/plugins/location.py +0 -179
  56. aprsd/threads/log_monitor.py +0 -121
  57. aprsd/web/__init__.py +0 -0
  58. aprsd/web/admin/__init__.py +0 -0
  59. aprsd/web/admin/static/css/index.css +0 -84
  60. aprsd/web/admin/static/css/prism.css +0 -4
  61. aprsd/web/admin/static/css/tabs.css +0 -35
  62. aprsd/web/admin/static/images/Untitled.png +0 -0
  63. aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
  64. aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
  65. aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
  66. aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
  67. aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
  68. aprsd/web/admin/static/js/charts.js +0 -235
  69. aprsd/web/admin/static/js/echarts.js +0 -465
  70. aprsd/web/admin/static/js/logs.js +0 -26
  71. aprsd/web/admin/static/js/main.js +0 -231
  72. aprsd/web/admin/static/js/prism.js +0 -12
  73. aprsd/web/admin/static/js/send-message.js +0 -114
  74. aprsd/web/admin/static/js/tabs.js +0 -28
  75. aprsd/web/admin/templates/index.html +0 -196
  76. aprsd/web/chat/static/css/chat.css +0 -115
  77. aprsd/web/chat/static/css/index.css +0 -66
  78. aprsd/web/chat/static/css/style.css.map +0 -1
  79. aprsd/web/chat/static/css/tabs.css +0 -41
  80. aprsd/web/chat/static/css/upstream/bootstrap.min.css +0 -6
  81. aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
  82. aprsd/web/chat/static/css/upstream/google-fonts.css +0 -23
  83. aprsd/web/chat/static/css/upstream/jquery-ui.css +0 -1311
  84. aprsd/web/chat/static/css/upstream/jquery.toast.css +0 -28
  85. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
  86. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
  87. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
  88. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
  89. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
  90. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
  91. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
  92. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
  93. aprsd/web/chat/static/images/Untitled.png +0 -0
  94. aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
  95. aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
  96. aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
  97. aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
  98. aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
  99. aprsd/web/chat/static/images/globe.svg +0 -3
  100. aprsd/web/chat/static/js/gps.js +0 -84
  101. aprsd/web/chat/static/js/main.js +0 -45
  102. aprsd/web/chat/static/js/send-message.js +0 -585
  103. aprsd/web/chat/static/js/tabs.js +0 -28
  104. aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +0 -7
  105. aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +0 -2
  106. aprsd/web/chat/static/js/upstream/jquery-ui.min.js +0 -13
  107. aprsd/web/chat/static/js/upstream/jquery.toast.js +0 -374
  108. aprsd/web/chat/static/js/upstream/semantic.min.js +0 -11
  109. aprsd/web/chat/static/js/upstream/socket.io.min.js +0 -7
  110. aprsd/web/chat/templates/index.html +0 -139
  111. aprsd/wsgi.py +0 -315
  112. aprsd-3.4.3.dist-info/AUTHORS +0 -13
  113. aprsd-3.4.3.dist-info/METADATA +0 -793
  114. aprsd-3.4.3.dist-info/RECORD +0 -133
  115. {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/LICENSE +0 -0
  116. {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/entry_points.txt +0 -0
  117. {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, raw=False, blocking=False,
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(f"Packet {packet.from_call}:{packet.msgNo} already tracked, dropping.")
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
- tx.send(
218
- packets.AckPacket(
219
- from_call=CONF.callsign,
220
- to_call=from_call,
221
- msgNo=msg_id,
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, for_us=(to_call.lower() == our_call),
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 not meant for us.")
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
- LOG.warning("Sending help!")
332
- message_text = "Unknown command! Send 'help' message for help"
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
- _send_ack(packet, direct=direct, aprs_client=aprs_client)
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
- _send_direct(packet)
154
- packet.send_count += 1
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
- _send_direct(self.packet)
203
- self.packet.send_count += 1
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(f"Extension {ep.name} of group {group} failed to load with {e}", file=sys.stderr)
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(start, end):
178
- if (type(start) != tuple) or (type(end) != tuple): # noqa: E721
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(start[0]))
182
- lat2 = math.radians(float(end[0]))
201
+ lat1 = math.radians(float(point_a[0]))
202
+ lat2 = math.radians(float(point_b[0]))
183
203
 
184
- diff_long = math.radians(float(end[1]) - float(start[1]))
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", "North-Northeast", "Northeast", "East-Northeast", "East", "East-Southeast",
207
- "Southeast", "South-Southeast", "South", "South-Southwest", "Southwest", "West-Southwest",
208
- "West", "West-Northwest", "Northwest", "North-Northwest", "North",
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", "NNE", "NE", "ENE", "E", "ESE",
213
- "SE", "SSE", "S", "SSW", "SW", "WSW",
214
- "W", "WNW", "NW", "NNW", "N",
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 id counter class.
12
+ Global Packet ID counter class.
14
13
 
15
- This is a singleton based class that keeps
14
+ This is a singleton-based class that keeps
16
15
  an incrementing counter for all packets to
17
- be sent. All new Packet objects gets a new
18
- message id, which is the next number available
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.val = RawValue("i", random.randint(1, MAX_PACKET_ID))
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 self.val.value == MAX_PACKET_ID:
36
- self.val.value = 1
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.val.value += 1
38
+ self._val += 1
39
39
 
40
40
  @property
41
41
  @wrapt.synchronized(lock)
42
42
  def value(self):
43
- return str(self.val.value)
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
- return str(self.val.value)
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
- return str(self.val.value)
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", "month", "day", "hour", "minute",
14
- "second", "microsecond",
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, object_hook=self.object_hook,
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
- func_name = f.__name__
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