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
@@ -1,18 +1,18 @@
1
- from collections import OrderedDict
2
1
  import logging
2
+ from collections import OrderedDict
3
3
 
4
4
  from oslo_config import cfg
5
5
 
6
6
  from aprsd.packets import core
7
7
  from aprsd.utils import objectstore
8
8
 
9
-
10
9
  CONF = cfg.CONF
11
10
  LOG = logging.getLogger("APRSD")
12
11
 
13
12
 
14
13
  class PacketList(objectstore.ObjectStoreMixin):
15
14
  """Class to keep track of the packets we tx/rx."""
15
+
16
16
  _instance = None
17
17
  _total_rx: int = 0
18
18
  _total_tx: int = 0
@@ -37,9 +37,11 @@ class PacketList(objectstore.ObjectStoreMixin):
37
37
  self._total_rx += 1
38
38
  self._add(packet)
39
39
  ptype = packet.__class__.__name__
40
- if ptype not in self.data["types"]:
41
- self.data["types"][ptype] = {"tx": 0, "rx": 0}
42
- self.data["types"][ptype]["rx"] += 1
40
+ type_stats = self.data["types"].setdefault(
41
+ ptype,
42
+ {"tx": 0, "rx": 0},
43
+ )
44
+ type_stats["rx"] += 1
43
45
 
44
46
  def tx(self, packet: type[core.Packet]):
45
47
  """Add a packet that was received."""
@@ -47,9 +49,11 @@ class PacketList(objectstore.ObjectStoreMixin):
47
49
  self._total_tx += 1
48
50
  self._add(packet)
49
51
  ptype = packet.__class__.__name__
50
- if ptype not in self.data["types"]:
51
- self.data["types"][ptype] = {"tx": 0, "rx": 0}
52
- self.data["types"][ptype]["tx"] += 1
52
+ type_stats = self.data["types"].setdefault(
53
+ ptype,
54
+ {"tx": 0, "rx": 0},
55
+ )
56
+ type_stats["tx"] += 1
53
57
 
54
58
  def add(self, packet):
55
59
  with self.lock:
@@ -81,28 +85,17 @@ class PacketList(objectstore.ObjectStoreMixin):
81
85
  return self._total_tx
82
86
 
83
87
  def stats(self, serializable=False) -> dict:
84
- # limit the number of packets to return to 50
85
88
  with self.lock:
86
- tmp = OrderedDict(
87
- reversed(
88
- list(
89
- self.data.get("packets", OrderedDict()).items(),
90
- ),
91
- ),
92
- )
93
- pkts = []
94
- count = 1
95
- for packet in tmp:
96
- pkts.append(tmp[packet])
97
- count += 1
98
- if count > CONF.packet_list_stats_maxlen:
99
- break
89
+ # Get last N packets directly using list slicing
90
+ packets_list = list(self.data.get("packets", {}).values())
91
+ pkts = packets_list[-CONF.packet_list_stats_maxlen :][::-1]
100
92
 
101
93
  stats = {
102
- "total_tracked": self._total_rx + self._total_rx,
94
+ "total_tracked": self._total_rx
95
+ + self._total_tx, # Fixed typo: was rx + rx
103
96
  "rx": self._total_rx,
104
97
  "tx": self._total_tx,
105
- "types": self.data.get("types", []),
98
+ "types": self.data.get("types", {}), # Changed default from [] to {}
106
99
  "packet_count": len(self.data.get("packets", [])),
107
100
  "maxlen": self.maxlen,
108
101
  "packets": pkts,
aprsd/plugin.py CHANGED
@@ -8,14 +8,13 @@ import re
8
8
  import textwrap
9
9
  import threading
10
10
 
11
- from oslo_config import cfg
12
11
  import pluggy
12
+ from oslo_config import cfg
13
13
 
14
14
  import aprsd
15
15
  from aprsd import client, packets, threads
16
16
  from aprsd.packets import watch_list
17
17
 
18
-
19
18
  # setup the global logger
20
19
  CONF = cfg.CONF
21
20
  LOG = logging.getLogger("APRSD")
@@ -166,7 +165,8 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
166
165
  except Exception as ex:
167
166
  LOG.error(
168
167
  "Plugin {} failed to process packet {}".format(
169
- self.__class__, ex,
168
+ self.__class__,
169
+ ex,
170
170
  ),
171
171
  )
172
172
  if result:
@@ -214,7 +214,9 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
214
214
  return result
215
215
 
216
216
  if not isinstance(packet, packets.MessagePacket):
217
- LOG.warning(f"{self.__class__.__name__} Got a {packet.__class__.__name__} ignoring")
217
+ LOG.warning(
218
+ f"{self.__class__.__name__} Got a {packet.__class__.__name__} ignoring"
219
+ )
218
220
  return packets.NULL_MESSAGE
219
221
 
220
222
  result = None
@@ -236,7 +238,8 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
236
238
  except Exception as ex:
237
239
  LOG.error(
238
240
  "Plugin {} failed to process packet {}".format(
239
- self.__class__, ex,
241
+ self.__class__,
242
+ ex,
240
243
  ),
241
244
  )
242
245
  LOG.exception(ex)
@@ -286,7 +289,8 @@ class HelpPlugin(APRSDRegexCommandPluginBase):
286
289
  reply = None
287
290
  for p in pm.get_plugins():
288
291
  if (
289
- p.enabled and isinstance(p, APRSDRegexCommandPluginBase)
292
+ p.enabled
293
+ and isinstance(p, APRSDRegexCommandPluginBase)
290
294
  and p.command_name.lower() == command_name
291
295
  ):
292
296
  reply = p.help()
@@ -345,6 +349,7 @@ class PluginManager:
345
349
 
346
350
  def stats(self, serializable=False) -> dict:
347
351
  """Collect and return stats for all plugins."""
352
+
348
353
  def full_name_with_qualname(obj):
349
354
  return "{}.{}".format(
350
355
  obj.__class__.__module__,
@@ -354,7 +359,6 @@ class PluginManager:
354
359
  plugin_stats = {}
355
360
  plugins = self.get_plugins()
356
361
  if plugins:
357
-
358
362
  for p in plugins:
359
363
  plugin_stats[full_name_with_qualname(p)] = {
360
364
  "enabled": p.enabled,
@@ -439,7 +443,9 @@ class PluginManager:
439
443
  )
440
444
  self._watchlist_pm.register(plugin_obj)
441
445
  else:
442
- LOG.warning(f"Plugin {plugin_obj.__class__.__name__} is disabled")
446
+ LOG.warning(
447
+ f"Plugin {plugin_obj.__class__.__name__} is disabled"
448
+ )
443
449
  elif isinstance(plugin_obj, APRSDRegexCommandPluginBase):
444
450
  if plugin_obj.enabled:
445
451
  LOG.info(
@@ -451,7 +457,9 @@ class PluginManager:
451
457
  )
452
458
  self._pluggy_pm.register(plugin_obj)
453
459
  else:
454
- LOG.warning(f"Plugin {plugin_obj.__class__.__name__} is disabled")
460
+ LOG.warning(
461
+ f"Plugin {plugin_obj.__class__.__name__} is disabled"
462
+ )
455
463
  elif isinstance(plugin_obj, APRSDPluginBase):
456
464
  if plugin_obj.enabled:
457
465
  LOG.info(
@@ -462,7 +470,9 @@ class PluginManager:
462
470
  )
463
471
  self._pluggy_pm.register(plugin_obj)
464
472
  else:
465
- LOG.warning(f"Plugin {plugin_obj.__class__.__name__} is disabled")
473
+ LOG.warning(
474
+ f"Plugin {plugin_obj.__class__.__name__} is disabled"
475
+ )
466
476
  except Exception as ex:
467
477
  LOG.error(f"Couldn't load plugin '{plugin_name}'")
468
478
  LOG.exception(ex)
@@ -470,9 +480,13 @@ class PluginManager:
470
480
  def reload_plugins(self):
471
481
  with self.lock:
472
482
  del self._pluggy_pm
473
- self.setup_plugins()
483
+ self.setup_plugins(load_help_plugin=CONF.load_help_plugin)
474
484
 
475
- def setup_plugins(self, load_help_plugin=True):
485
+ def setup_plugins(
486
+ self,
487
+ load_help_plugin=True,
488
+ plugin_list=[],
489
+ ):
476
490
  """Create the plugin manager and register plugins."""
477
491
 
478
492
  LOG.info("Loading APRSD Plugins")
@@ -481,9 +495,13 @@ class PluginManager:
481
495
  _help = HelpPlugin()
482
496
  self._pluggy_pm.register(_help)
483
497
 
484
- enabled_plugins = CONF.enabled_plugins
485
- if enabled_plugins:
486
- for p_name in enabled_plugins:
498
+ # if plugins_list is passed in, only load
499
+ # those plugins.
500
+ if plugin_list:
501
+ for plugin_name in plugin_list:
502
+ self._load_plugin(plugin_name)
503
+ elif CONF.enabled_plugins:
504
+ for p_name in CONF.enabled_plugins:
487
505
  self._load_plugin(p_name)
488
506
  else:
489
507
  # Enabled plugins isn't set, so we default to loading all of
aprsd/plugins/fortune.py CHANGED
@@ -8,7 +8,7 @@ from aprsd.utils import trace
8
8
 
9
9
  LOG = logging.getLogger("APRSD")
10
10
 
11
- DEFAULT_FORTUNE_PATH = '/usr/games/fortune'
11
+ DEFAULT_FORTUNE_PATH = "/usr/games/fortune"
12
12
 
13
13
 
14
14
  class FortunePlugin(plugin.APRSDRegexCommandPluginBase):
@@ -45,7 +45,7 @@ class FortunePlugin(plugin.APRSDRegexCommandPluginBase):
45
45
  command,
46
46
  shell=True,
47
47
  timeout=3,
48
- universal_newlines=True,
48
+ text=True,
49
49
  )
50
50
  output = (
51
51
  output.replace("\r", "")
aprsd/plugins/notify.py CHANGED
@@ -4,7 +4,6 @@ from oslo_config import cfg
4
4
 
5
5
  from aprsd import packets, plugin
6
6
 
7
-
8
7
  CONF = cfg.CONF
9
8
  LOG = logging.getLogger("APRSD")
10
9
 
@@ -43,9 +42,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
43
42
  pkt = packets.MessagePacket(
44
43
  from_call=CONF.callsign,
45
44
  to_call=notify_callsign,
46
- message_text=(
47
- f"{fromcall} was just seen by type:'{packet_type}'"
48
- ),
45
+ message_text=(f"{fromcall} was just seen by type:'{packet_type}'"),
49
46
  allow_delay=False,
50
47
  )
51
48
  pkt.allow_delay = False
aprsd/plugins/weather.py CHANGED
@@ -2,13 +2,12 @@ import json
2
2
  import logging
3
3
  import re
4
4
 
5
- from oslo_config import cfg
6
5
  import requests
6
+ from oslo_config import cfg
7
7
 
8
8
  from aprsd import plugin, plugin_utils
9
9
  from aprsd.utils import trace
10
10
 
11
-
12
11
  CONF = cfg.CONF
13
12
  LOG = logging.getLogger("APRSD")
14
13
 
@@ -205,8 +204,9 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
205
204
 
206
205
  def help(self):
207
206
  _help = [
208
- "openweathermap: Send {} to get weather "
209
- "from your location".format(self.command_regex),
207
+ "openweathermap: Send {} to get weather " "from your location".format(
208
+ self.command_regex
209
+ ),
210
210
  "openweathermap: Send {} <callsign> to get "
211
211
  "weather from <callsign>".format(self.command_regex),
212
212
  ]
@@ -327,10 +327,12 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
327
327
 
328
328
  def help(self):
329
329
  _help = [
330
- "avwxweather: Send {} to get weather "
331
- "from your location".format(self.command_regex),
332
- "avwxweather: Send {} <callsign> to get "
333
- "weather from <callsign>".format(self.command_regex),
330
+ "avwxweather: Send {} to get weather " "from your location".format(
331
+ self.command_regex
332
+ ),
333
+ "avwxweather: Send {} <callsign> to get " "weather from <callsign>".format(
334
+ self.command_regex
335
+ ),
334
336
  ]
335
337
  return _help
336
338
 
aprsd/stats/__init__.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from aprsd import plugin
2
2
  from aprsd.client import stats as client_stats
3
3
  from aprsd.packets import packet_list, seen_list, tracker, watch_list
4
- from aprsd.plugins import email
5
4
  from aprsd.stats import app, collector
6
5
  from aprsd.threads import aprsd
7
6
 
@@ -15,6 +14,5 @@ stats_collector.register_producer(watch_list.WatchList)
15
14
  stats_collector.register_producer(tracker.PacketTrack)
16
15
  stats_collector.register_producer(plugin.PluginManager)
17
16
  stats_collector.register_producer(aprsd.APRSDThreadList)
18
- stats_collector.register_producer(email.EmailStats)
19
17
  stats_collector.register_producer(client_stats.APRSClientStats)
20
18
  stats_collector.register_producer(seen_list.SeenList)
aprsd/stats/collector.py CHANGED
@@ -3,14 +3,14 @@ from typing import Callable, Protocol, runtime_checkable
3
3
 
4
4
  from aprsd.utils import singleton
5
5
 
6
-
7
6
  LOG = logging.getLogger("APRSD")
8
7
 
9
8
 
10
9
  @runtime_checkable
11
10
  class StatsProducer(Protocol):
12
11
  """The StatsProducer protocol is used to define the interface for collecting stats."""
13
- def stats(self, serializeable=False) -> dict:
12
+
13
+ def stats(self, serializable=False) -> dict:
14
14
  """provide stats in a dictionary format."""
15
15
  ...
16
16
 
@@ -18,6 +18,7 @@ class StatsProducer(Protocol):
18
18
  @singleton
19
19
  class Collector:
20
20
  """The Collector class is used to collect stats from multiple StatsProducer instances."""
21
+
21
22
  def __init__(self):
22
23
  self.producers: list[Callable] = []
23
24
 
@@ -26,7 +27,9 @@ class Collector:
26
27
  for name in self.producers:
27
28
  cls = name()
28
29
  try:
29
- stats[cls.__class__.__name__] = cls.stats(serializable=serializable).copy()
30
+ stats[cls.__class__.__name__] = cls.stats(
31
+ serializable=serializable
32
+ ).copy()
30
33
  except Exception as e:
31
34
  LOG.error(f"Error in producer {name} (stats): {e}")
32
35
  return stats
@@ -35,3 +38,8 @@ class Collector:
35
38
  if not isinstance(producer_name, StatsProducer):
36
39
  raise TypeError(f"Producer {producer_name} is not a StatsProducer")
37
40
  self.producers.append(producer_name)
41
+
42
+ def unregister_producer(self, producer_name: Callable):
43
+ if not isinstance(producer_name, StatsProducer):
44
+ raise TypeError(f"Producer {producer_name} is not a StatsProducer")
45
+ self.producers.remove(producer_name)
aprsd/threads/__init__.py CHANGED
@@ -4,8 +4,9 @@ import queue
4
4
  # aprsd.threads
5
5
  from .aprsd import APRSDThread, APRSDThreadList # noqa: F401
6
6
  from .rx import ( # noqa: F401
7
- APRSDDupeRXThread, APRSDProcessPacketThread, APRSDRXThread,
7
+ APRSDDupeRXThread,
8
+ APRSDProcessPacketThread,
9
+ APRSDRXThread,
8
10
  )
9
11
 
10
-
11
12
  packet_queue = queue.Queue(maxsize=20)
aprsd/threads/aprsd.py CHANGED
@@ -2,11 +2,11 @@ import abc
2
2
  import datetime
3
3
  import logging
4
4
  import threading
5
+ import time
5
6
  from typing import List
6
7
 
7
8
  import wrapt
8
9
 
9
-
10
10
  LOG = logging.getLogger("APRSD")
11
11
 
12
12
 
@@ -14,6 +14,8 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
14
14
  """Base class for all threads in APRSD."""
15
15
 
16
16
  loop_count = 1
17
+ _pause = False
18
+ thread_stop = False
17
19
 
18
20
  def __init__(self, name):
19
21
  super().__init__(name=name)
@@ -22,11 +24,22 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
22
24
  self._last_loop = datetime.datetime.now()
23
25
 
24
26
  def _should_quit(self):
25
- """ see if we have a quit message from the global queue."""
27
+ """see if we have a quit message from the global queue."""
26
28
  if self.thread_stop:
27
29
  return True
28
30
 
31
+ def pause(self):
32
+ """Logically pause the processing of the main loop."""
33
+ LOG.debug(f"Pausing thread '{self.name}' loop_count {self.loop_count}")
34
+ self._pause = True
35
+
36
+ def unpause(self):
37
+ """Logically resume processing of the main loop."""
38
+ LOG.debug(f"Resuming thread '{self.name}' loop_count {self.loop_count}")
39
+ self._pause = False
40
+
29
41
  def stop(self):
42
+ LOG.debug(f"Stopping thread '{self.name}'")
30
43
  self.thread_stop = True
31
44
 
32
45
  @abc.abstractmethod
@@ -37,7 +50,9 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
37
50
  """Add code to subclass to do any cleanup"""
38
51
 
39
52
  def __str__(self):
40
- out = f"Thread <{self.__class__.__name__}({self.name}) Alive? {self.is_alive()}>"
53
+ out = (
54
+ f"Thread <{self.__class__.__name__}({self.name}) Alive? {self.is_alive()}>"
55
+ )
41
56
  return out
42
57
 
43
58
  def loop_age(self):
@@ -47,11 +62,14 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
47
62
  def run(self):
48
63
  LOG.debug("Starting")
49
64
  while not self._should_quit():
50
- self.loop_count += 1
51
- can_loop = self.loop()
52
- self._last_loop = datetime.datetime.now()
53
- if not can_loop:
54
- self.stop()
65
+ if self._pause:
66
+ time.sleep(1)
67
+ else:
68
+ self.loop_count += 1
69
+ can_loop = self.loop()
70
+ self._last_loop = datetime.datetime.now()
71
+ if not can_loop:
72
+ self.stop()
55
73
  self._cleanup()
56
74
  APRSDThreadList().remove(self)
57
75
  LOG.debug("Exiting")
@@ -71,6 +89,13 @@ class APRSDThreadList:
71
89
  cls.threads_list = []
72
90
  return cls._instance
73
91
 
92
+ def __contains__(self, name):
93
+ """See if we have a thread in our list"""
94
+ for t in self.threads_list:
95
+ if t.name == name:
96
+ return True
97
+ return False
98
+
74
99
  def stats(self, serializable=False) -> dict:
75
100
  stats = {}
76
101
  for th in self.threads_list:
@@ -100,9 +125,27 @@ class APRSDThreadList:
100
125
  for th in self.threads_list:
101
126
  LOG.info(f"Stopping Thread {th.name}")
102
127
  if hasattr(th, "packet"):
103
- LOG.info(F"{th.name} packet {th.packet}")
128
+ LOG.info(f"{th.name} packet {th.packet}")
104
129
  th.stop()
105
130
 
131
+ @wrapt.synchronized
132
+ def pause_all(self):
133
+ """Iterate over all threads and pause them."""
134
+ for th in self.threads_list:
135
+ LOG.info(f"Pausing Thread {th.name}")
136
+ if hasattr(th, "packet"):
137
+ LOG.info(f"{th.name} packet {th.packet}")
138
+ th.pause()
139
+
140
+ @wrapt.synchronized
141
+ def unpause_all(self):
142
+ """Iterate over all threads and resume them."""
143
+ for th in self.threads_list:
144
+ LOG.info(f"Resuming Thread {th.name}")
145
+ if hasattr(th, "packet"):
146
+ LOG.info(f"{th.name} packet {th.packet}")
147
+ th.unpause()
148
+
106
149
  @wrapt.synchronized(lock)
107
150
  def info(self):
108
151
  """Go through all the threads and collect info about each."""
@@ -111,7 +154,11 @@ class APRSDThreadList:
111
154
  alive = thread.is_alive()
112
155
  age = thread.loop_age()
113
156
  key = thread.__class__.__name__
114
- info[key] = {"alive": True if alive else False, "age": age, "name": thread.name}
157
+ info[key] = {
158
+ "alive": True if alive else False,
159
+ "age": age,
160
+ "name": thread.name,
161
+ }
115
162
  return info
116
163
 
117
164
  @wrapt.synchronized(lock)
@@ -7,11 +7,10 @@ from loguru import logger
7
7
  from oslo_config import cfg
8
8
 
9
9
  from aprsd import packets, utils
10
- from aprsd.client import client_factory
11
10
  from aprsd.log import log as aprsd_log
12
11
  from aprsd.stats import collector
13
12
  from aprsd.threads import APRSDThread, APRSDThreadList
14
-
13
+ from aprsd.utils import keepalive_collector
15
14
 
16
15
  CONF = cfg.CONF
17
16
  LOG = logging.getLogger("APRSD")
@@ -35,18 +34,14 @@ class KeepAliveThread(APRSDThread):
35
34
  thread_list = APRSDThreadList()
36
35
  now = datetime.datetime.now()
37
36
 
38
- if "EmailStats" in stats_json:
39
- email_stats = stats_json["EmailStats"]
40
- if email_stats.get("last_check_time"):
41
- email_thread_time = utils.strfdelta(now - email_stats["last_check_time"])
42
- else:
43
- email_thread_time = "N/A"
44
- else:
45
- email_thread_time = "N/A"
46
-
47
- if "APRSClientStats" in stats_json and stats_json["APRSClientStats"].get("transport") == "aprsis":
37
+ if (
38
+ "APRSClientStats" in stats_json
39
+ and stats_json["APRSClientStats"].get("transport") == "aprsis"
40
+ ):
48
41
  if stats_json["APRSClientStats"].get("server_keepalive"):
49
- last_msg_time = utils.strfdelta(now - stats_json["APRSClientStats"]["server_keepalive"])
42
+ last_msg_time = utils.strfdelta(
43
+ now - stats_json["APRSClientStats"]["server_keepalive"]
44
+ )
50
45
  else:
51
46
  last_msg_time = "N/A"
52
47
  else:
@@ -63,7 +58,7 @@ class KeepAliveThread(APRSDThread):
63
58
 
64
59
  keepalive = (
65
60
  "{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
66
- "Last:{} Email: {} - RAM Current:{} Peak:{} Threads:{} LoggingQueue:{}"
61
+ "Last:{} - RAM Current:{} Peak:{} Threads:{} LoggingQueue:{}"
67
62
  ).format(
68
63
  stats_json["APRSDStats"]["callsign"],
69
64
  stats_json["APRSDStats"]["uptime"],
@@ -73,7 +68,6 @@ class KeepAliveThread(APRSDThread):
73
68
  tx_msg,
74
69
  rx_msg,
75
70
  last_msg_time,
76
- email_thread_time,
77
71
  stats_json["APRSDStats"]["memory_current_str"],
78
72
  stats_json["APRSDStats"]["memory_peak_str"],
79
73
  len(thread_list),
@@ -96,28 +90,11 @@ class KeepAliveThread(APRSDThread):
96
90
  LOGU.opt(colors=True).info(thread_msg)
97
91
  # LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}")
98
92
 
99
- # check the APRS connection
100
- cl = client_factory.create()
101
- # Reset the connection if it's dead and this isn't our
102
- # First time through the loop.
103
- # The first time through the loop can happen at startup where
104
- # The keepalive thread starts before the client has a chance
105
- # to make it's connection the first time.
106
- if not cl.is_alive() and self.cntr > 0:
107
- LOG.error(f"{cl.__class__.__name__} is not alive!!! Resetting")
108
- client_factory.create().reset()
109
- # else:
110
- # # See if we should reset the aprs-is client
111
- # # Due to losing a keepalive from them
112
- # delta_dict = utils.parse_delta_str(last_msg_time)
113
- # delta = datetime.timedelta(**delta_dict)
114
- #
115
- # if delta > self.max_delta:
116
- # # We haven't gotten a keepalive from aprs-is in a while
117
- # # reset the connection.a
118
- # if not client.KISSClient.is_enabled():
119
- # LOG.warning(f"Resetting connection to APRS-IS {delta}")
120
- # client.factory.create().reset()
93
+ # Go through the registered keepalive collectors
94
+ # and check them as well as call log.
95
+ collect = keepalive_collector.KeepAliveCollector()
96
+ collect.check()
97
+ collect.log()
121
98
 
122
99
  # Check version every day
123
100
  delta = now - self.checker_time
aprsd/threads/registry.py CHANGED
@@ -1,19 +1,19 @@
1
1
  import logging
2
2
  import time
3
3
 
4
- from oslo_config import cfg
5
4
  import requests
5
+ from oslo_config import cfg
6
6
 
7
7
  import aprsd
8
8
  from aprsd import threads as aprsd_threads
9
9
 
10
-
11
10
  CONF = cfg.CONF
12
11
  LOG = logging.getLogger("APRSD")
13
12
 
14
13
 
15
14
  class APRSRegistryThread(aprsd_threads.APRSDThread):
16
15
  """This sends service information to the configured APRS Registry."""
16
+
17
17
  _loop_cnt: int = 1
18
18
 
19
19
  def __init__(self):
@@ -41,7 +41,7 @@ class APRSRegistryThread(aprsd_threads.APRSDThread):
41
41
  "description": CONF.aprs_registry.description,
42
42
  "service_website": CONF.aprs_registry.service_website,
43
43
  "software": f"APRSD version {aprsd.__version__} "
44
- "https://github.com/craigerl/aprsd",
44
+ "https://github.com/craigerl/aprsd",
45
45
  }
46
46
  try:
47
47
  requests.post(