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
@@ -4,12 +4,9 @@ import inspect
4
4
  import logging
5
5
  import os
6
6
  import pkgutil
7
- import re
8
7
  import sys
9
8
  from traceback import print_tb
10
- from urllib.parse import urljoin
11
9
 
12
- from bs4 import BeautifulSoup
13
10
  import click
14
11
  import requests
15
12
  from rich.console import Console
@@ -20,17 +17,14 @@ from thesmuggler import smuggle
20
17
  from aprsd import cli_helper
21
18
  from aprsd import plugin as aprsd_plugin
22
19
  from aprsd.main import cli
23
- from aprsd.plugins import (
24
- email, fortune, location, notify, ping, time, version, weather,
25
- )
20
+ from aprsd.plugins import fortune, notify, ping, time, version, weather
26
21
 
27
-
28
- LOG = logging.getLogger("APRSD")
29
- PYPI_URL = "https://pypi.org/search/"
22
+ LOG = logging.getLogger('APRSD')
23
+ PYPI_URL = 'https://pypi.org/search/'
30
24
 
31
25
 
32
26
  def onerror(name):
33
- print(f"Error importing module {name}")
27
+ print(f'Error importing module {name}')
34
28
  type, value, traceback = sys.exc_info()
35
29
  print_tb(traceback)
36
30
 
@@ -46,19 +40,19 @@ def is_plugin(obj):
46
40
  def plugin_type(obj):
47
41
  for c in inspect.getmro(obj):
48
42
  if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
49
- return "RegexCommand"
43
+ return 'RegexCommand'
50
44
  if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
51
- return "WatchList"
45
+ return 'WatchList'
52
46
  if issubclass(c, aprsd_plugin.APRSDPluginBase):
53
- return "APRSDPluginBase"
47
+ return 'APRSDPluginBase'
54
48
 
55
- return "Unknown"
49
+ return 'Unknown'
56
50
 
57
51
 
58
52
  def walk_package(package):
59
53
  return pkgutil.walk_packages(
60
54
  package.__path__,
61
- package.__name__ + ".",
55
+ package.__name__ + '.',
62
56
  onerror=onerror,
63
57
  )
64
58
 
@@ -68,22 +62,23 @@ def get_module_info(package_name, module_name, module_path):
68
62
  return None
69
63
 
70
64
  dir_path = os.path.realpath(module_path)
71
- pattern = "*.py"
65
+ pattern = '*.py'
72
66
 
73
67
  obj_list = []
74
68
 
75
69
  for path, _subdirs, files in os.walk(dir_path):
76
70
  for name in files:
77
71
  if fnmatch.fnmatch(name, pattern):
78
- module = smuggle(f"{path}/{name}")
72
+ module = smuggle(f'{path}/{name}')
79
73
  for mem_name, obj in inspect.getmembers(module):
80
74
  if inspect.isclass(obj) and is_plugin(obj):
81
75
  obj_list.append(
82
76
  {
83
- "package": package_name,
84
- "name": mem_name, "obj": obj,
85
- "version": obj.version,
86
- "path": f"{'.'.join([module_name, obj.__name__])}",
77
+ 'package': package_name,
78
+ 'name': mem_name,
79
+ 'obj': obj,
80
+ 'version': obj.version,
81
+ 'path': f'{".".join([module_name, obj.__name__])}',
87
82
  },
88
83
  )
89
84
 
@@ -94,18 +89,18 @@ def _get_installed_aprsd_items():
94
89
  # installed plugins
95
90
  plugins = {}
96
91
  extensions = {}
97
- for finder, name, ispkg in pkgutil.iter_modules():
98
- if name.startswith("aprsd_"):
99
- print(f"Found aprsd_ module: {name}")
100
- if ispkg:
101
- module = importlib.import_module(name)
102
- pkgs = walk_package(module)
103
- for pkg in pkgs:
104
- pkg_info = get_module_info(module.__name__, pkg.name, module.__path__[0])
105
- if "plugin" in name:
106
- plugins[name] = pkg_info
107
- elif "extension" in name:
108
- extensions[name] = pkg_info
92
+ for _finder, name, ispkg in pkgutil.iter_modules():
93
+ if ispkg and name.startswith('aprsd_'):
94
+ module = importlib.import_module(name)
95
+ pkgs = walk_package(module)
96
+ for pkg in pkgs:
97
+ pkg_info = get_module_info(
98
+ module.__name__, pkg.name, module.__path__[0]
99
+ )
100
+ if 'plugin' in name:
101
+ plugins[name] = pkg_info
102
+ elif 'extension' in name:
103
+ extensions[name] = pkg_info
109
104
  return plugins, extensions
110
105
 
111
106
 
@@ -122,7 +117,7 @@ def get_installed_extensions():
122
117
 
123
118
 
124
119
  def show_built_in_plugins(console):
125
- modules = [email, fortune, location, notify, ping, time, version, weather]
120
+ modules = [fortune, notify, ping, time, version, weather]
126
121
  plugins = []
127
122
 
128
123
  for module in modules:
@@ -131,132 +126,141 @@ def show_built_in_plugins(console):
131
126
  cls = entry[1]
132
127
  if issubclass(cls, aprsd_plugin.APRSDPluginBase):
133
128
  info = {
134
- "name": cls.__qualname__,
135
- "path": f"{cls.__module__}.{cls.__qualname__}",
136
- "version": cls.version,
137
- "docstring": cls.__doc__,
138
- "short_desc": cls.short_description,
129
+ 'name': cls.__qualname__,
130
+ 'path': f'{cls.__module__}.{cls.__qualname__}',
131
+ 'version': cls.version,
132
+ 'docstring': cls.__doc__,
133
+ 'short_desc': cls.short_description,
139
134
  }
140
135
 
141
136
  if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
142
- info["command_regex"] = cls.command_regex
143
- info["type"] = "RegexCommand"
137
+ info['command_regex'] = cls.command_regex
138
+ info['type'] = 'RegexCommand'
144
139
 
145
140
  if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
146
- info["type"] = "WatchList"
141
+ info['type'] = 'WatchList'
147
142
 
148
143
  plugins.append(info)
149
144
 
150
- plugins = sorted(plugins, key=lambda i: i["name"])
145
+ plugins = sorted(plugins, key=lambda i: i['name'])
151
146
 
152
147
  table = Table(
153
- title="[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]",
148
+ title='[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]',
154
149
  )
155
- table.add_column("Plugin Name", style="cyan", no_wrap=True)
156
- table.add_column("Info", style="bold yellow")
157
- table.add_column("Type", style="bold green")
158
- table.add_column("Plugin Path", style="bold blue")
150
+ table.add_column('Plugin Name', style='cyan', no_wrap=True)
151
+ table.add_column('Info', style='bold yellow')
152
+ table.add_column('Type', style='bold green')
153
+ table.add_column('Plugin Path', style='bold blue')
159
154
  for entry in plugins:
160
- table.add_row(entry["name"], entry["short_desc"], entry["type"], entry["path"])
155
+ table.add_row(entry['name'], entry['short_desc'], entry['type'], entry['path'])
161
156
 
162
157
  console.print(table)
163
158
 
164
159
 
165
160
  def _get_pypi_packages():
166
- query = "aprsd"
167
- snippets = []
168
- s = requests.Session()
169
- for page in range(1, 3):
170
- params = {"q": query, "page": page}
171
- r = s.get(PYPI_URL, params=params)
172
- soup = BeautifulSoup(r.text, "html.parser")
173
- snippets += soup.select('a[class*="snippet"]')
174
- if not hasattr(s, "start_url"):
175
- s.start_url = r.url.rsplit("&page", maxsplit=1).pop(0)
176
-
177
- return snippets
161
+ if simple_r := requests.get(
162
+ 'https://pypi.org/simple',
163
+ headers={'Accept': 'application/vnd.pypi.simple.v1+json'},
164
+ ):
165
+ simple_response = simple_r.json()
166
+ else:
167
+ simple_response = {}
168
+
169
+ key = 'aprsd'
170
+ matches = [
171
+ p['name'] for p in simple_response['projects'] if p['name'].startswith(key)
172
+ ]
173
+
174
+ packages = []
175
+ for pkg in matches:
176
+ # Get info for first match
177
+ if r := requests.get(
178
+ f'https://pypi.org/pypi/{pkg}/json',
179
+ headers={'Accept': 'application/json'},
180
+ ):
181
+ packages.append(r.json())
182
+
183
+ return packages
178
184
 
179
185
 
180
186
  def show_pypi_plugins(installed_plugins, console):
181
- snippets = _get_pypi_packages()
187
+ packages = _get_pypi_packages()
182
188
 
183
189
  title = Text.assemble(
184
- ("Pypi.org APRSD Installable Plugin Packages\n\n", "bold magenta"),
185
- ("Install any of the following plugins with\n", "bold yellow"),
186
- ("'pip install ", "bold white"),
187
- ("<Plugin Package Name>'", "cyan"),
190
+ ('Pypi.org APRSD Installable Plugin Packages\n\n', 'bold magenta'),
191
+ ('Install any of the following plugins with\n', 'bold yellow'),
192
+ ("'pip install ", 'bold white'),
193
+ ("<Plugin Package Name>'", 'cyan'),
188
194
  )
189
195
 
190
196
  table = Table(title=title)
191
- table.add_column("Plugin Package Name", style="cyan", no_wrap=True)
192
- table.add_column("Description", style="yellow")
193
- table.add_column("Version", style="yellow", justify="center")
194
- table.add_column("Released", style="bold green", justify="center")
195
- table.add_column("Installed?", style="red", justify="center")
196
- for snippet in snippets:
197
- link = urljoin(PYPI_URL, snippet.get("href"))
198
- package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
199
- version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
200
- created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
201
- description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
202
- emoji = ":open_file_folder:"
203
-
204
- if "aprsd-" not in package or "-plugin" not in package:
197
+ table.add_column('Plugin Package Name', style='cyan', no_wrap=True)
198
+ table.add_column('Description', style='yellow')
199
+ table.add_column('Version', style='yellow', justify='center')
200
+ table.add_column('Released', style='bold green', justify='center')
201
+ table.add_column('Installed?', style='red', justify='center')
202
+ emoji = ':open_file_folder:'
203
+ for package in packages:
204
+ link = package['info']['package_url']
205
+ version = package['info']['version']
206
+ package_name = package['info']['name']
207
+ description = package['info']['summary']
208
+ created = package['releases'][version][0]['upload_time']
209
+
210
+ if 'aprsd-' not in package_name or '-plugin' not in package_name:
205
211
  continue
206
212
 
207
- under = package.replace("-", "_")
208
- if under in installed_plugins:
209
- installed = "Yes"
210
- else:
211
- installed = "No"
212
-
213
+ under = package_name.replace('-', '_')
214
+ installed = 'Yes' if under in installed_plugins else 'No'
213
215
  table.add_row(
214
- f"[link={link}]{emoji}[/link] {package}",
215
- description, version, created, installed,
216
+ f'[link={link}]{emoji}[/link] {package_name}',
217
+ description,
218
+ version,
219
+ created,
220
+ installed,
216
221
  )
217
222
 
218
- console.print("\n")
223
+ console.print('\n')
219
224
  console.print(table)
220
225
 
221
226
 
222
227
  def show_pypi_extensions(installed_extensions, console):
223
- snippets = _get_pypi_packages()
228
+ packages = _get_pypi_packages()
224
229
 
225
230
  title = Text.assemble(
226
- ("Pypi.org APRSD Installable Extension Packages\n\n", "bold magenta"),
227
- ("Install any of the following extensions by running\n", "bold yellow"),
228
- ("'pip install ", "bold white"),
229
- ("<Plugin Package Name>'", "cyan"),
231
+ ('Pypi.org APRSD Installable Extension Packages\n\n', 'bold magenta'),
232
+ ('Install any of the following extensions by running\n', 'bold yellow'),
233
+ ("'pip install ", 'bold white'),
234
+ ("<Plugin Package Name>'", 'cyan'),
230
235
  )
231
236
  table = Table(title=title)
232
- table.add_column("Extension Package Name", style="cyan", no_wrap=True)
233
- table.add_column("Description", style="yellow")
234
- table.add_column("Version", style="yellow", justify="center")
235
- table.add_column("Released", style="bold green", justify="center")
236
- table.add_column("Installed?", style="red", justify="center")
237
- for snippet in snippets:
238
- link = urljoin(PYPI_URL, snippet.get("href"))
239
- package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
240
- version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
241
- created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
242
- description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
243
- emoji = ":open_file_folder:"
244
-
245
- if "aprsd-" not in package or "-extension" not in package:
237
+ table.add_column('Extension Package Name', style='cyan', no_wrap=True)
238
+ table.add_column('Description', style='yellow')
239
+ table.add_column('Version', style='yellow', justify='center')
240
+ table.add_column('Released', style='bold green', justify='center')
241
+ table.add_column('Installed?', style='red', justify='center')
242
+ emoji = ':open_file_folder:'
243
+
244
+ for package in packages:
245
+ link = package['info']['package_url']
246
+ version = package['info']['version']
247
+ package_name = package['info']['name']
248
+ description = package['info']['summary']
249
+ created = package['releases'][version][0]['upload_time']
250
+ if 'aprsd-' not in package_name or '-extension' not in package_name:
246
251
  continue
247
252
 
248
- under = package.replace("-", "_")
249
- if under in installed_extensions:
250
- installed = "Yes"
251
- else:
252
- installed = "No"
253
-
253
+ under = package_name.replace('-', '_')
254
+ installed = 'Yes' if under in installed_extensions else 'No'
254
255
  table.add_row(
255
- f"[link={link}]{emoji}[/link] {package}",
256
- description, version, created, installed,
256
+ f'[link={link}]{emoji}[/link] {package_name}',
257
+ description,
258
+ version,
259
+ created,
260
+ installed,
257
261
  )
258
262
 
259
- console.print("\n")
263
+ console.print('\n')
260
264
  console.print(table)
261
265
 
262
266
 
@@ -265,24 +269,24 @@ def show_installed_plugins(installed_plugins, console):
265
269
  return
266
270
 
267
271
  table = Table(
268
- title="[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]",
272
+ title='[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]',
269
273
  )
270
- table.add_column("Package Name", style=" bold white", no_wrap=True)
271
- table.add_column("Plugin Name", style="cyan", no_wrap=True)
272
- table.add_column("Version", style="yellow", justify="center")
273
- table.add_column("Type", style="bold green")
274
- table.add_column("Plugin Path", style="bold blue")
274
+ table.add_column('Package Name', style=' bold white', no_wrap=True)
275
+ table.add_column('Plugin Name', style='cyan', no_wrap=True)
276
+ table.add_column('Version', style='yellow', justify='center')
277
+ table.add_column('Type', style='bold green')
278
+ table.add_column('Plugin Path', style='bold blue')
275
279
  for name in installed_plugins:
276
280
  for plugin in installed_plugins[name]:
277
281
  table.add_row(
278
- name.replace("_", "-"),
279
- plugin["name"],
280
- plugin["version"],
281
- plugin_type(plugin["obj"]),
282
- plugin["path"],
282
+ name.replace('_', '-'),
283
+ plugin['name'],
284
+ plugin['version'],
285
+ plugin_type(plugin['obj']),
286
+ plugin['path'],
283
287
  )
284
288
 
285
- console.print("\n")
289
+ console.print('\n')
286
290
  console.print(table)
287
291
 
288
292
 
@@ -294,14 +298,14 @@ def list_plugins(ctx):
294
298
  """List the built in plugins available to APRSD."""
295
299
  console = Console()
296
300
 
297
- with console.status("Show Built-in Plugins") as status:
301
+ with console.status('Show Built-in Plugins') as status:
298
302
  show_built_in_plugins(console)
299
303
 
300
- status.update("Fetching pypi.org plugins")
304
+ status.update('Fetching pypi.org plugins')
301
305
  installed_plugins = get_installed_plugins()
302
306
  show_pypi_plugins(installed_plugins, console)
303
307
 
304
- status.update("Looking for installed APRSD plugins")
308
+ status.update('Looking for installed APRSD plugins')
305
309
  show_installed_plugins(installed_plugins, console)
306
310
 
307
311
 
@@ -313,7 +317,9 @@ def list_extensions(ctx):
313
317
  """List the built in plugins available to APRSD."""
314
318
  console = Console()
315
319
 
316
- with console.status("Show APRSD Extensions") as status:
317
- status.update("Fetching pypi.org APRSD Extensions")
320
+ with console.status('Show APRSD Extensions') as status:
321
+ status.update('Fetching pypi.org APRSD Extensions')
322
+
323
+ status.update('Looking for installed APRSD Extensions')
318
324
  installed_extensions = get_installed_extensions()
319
325
  show_pypi_extensions(installed_extensions, console)
aprsd/cmds/listen.py CHANGED
@@ -10,26 +10,28 @@ import sys
10
10
  import time
11
11
 
12
12
  import click
13
+ from loguru import logger
13
14
  from oslo_config import cfg
14
15
  from rich.console import Console
15
16
 
16
17
  # local imports here
17
18
  import aprsd
18
- from aprsd import cli_helper, packets, plugin, threads
19
+ from aprsd import cli_helper, packets, plugin, threads, utils
19
20
  from aprsd.client import client_factory
20
21
  from aprsd.main import cli
21
22
  from aprsd.packets import collector as packet_collector
22
23
  from aprsd.packets import log as packet_log
23
24
  from aprsd.packets import seen_list
24
25
  from aprsd.stats import collector
25
- from aprsd.threads import keep_alive, rx
26
+ from aprsd.threads import keepalive, rx
26
27
  from aprsd.threads import stats as stats_thread
27
-
28
+ from aprsd.threads.aprsd import APRSDThread
28
29
 
29
30
  # setup the global logger
30
31
  # log.basicConfig(level=log.DEBUG) # level=10
31
32
  LOG = logging.getLogger("APRSD")
32
33
  CONF = cfg.CONF
34
+ LOGU = logger
33
35
  console = Console()
34
36
 
35
37
 
@@ -47,12 +49,20 @@ def signal_handler(sig, frame):
47
49
 
48
50
 
49
51
  class APRSDListenThread(rx.APRSDRXThread):
50
- def __init__(self, packet_queue, packet_filter=None, plugin_manager=None):
52
+ def __init__(
53
+ self,
54
+ packet_queue,
55
+ packet_filter=None,
56
+ plugin_manager=None,
57
+ enabled_plugins=[],
58
+ log_packets=False,
59
+ ):
51
60
  super().__init__(packet_queue)
52
61
  self.packet_filter = packet_filter
53
62
  self.plugin_manager = plugin_manager
54
63
  if self.plugin_manager:
55
64
  LOG.info(f"Plugins {self.plugin_manager.get_message_plugins()}")
65
+ self.log_packets = log_packets
56
66
 
57
67
  def process_packet(self, *args, **kwargs):
58
68
  packet = self._client.decode_packet(*args, **kwargs)
@@ -73,13 +83,15 @@ class APRSDListenThread(rx.APRSDRXThread):
73
83
  if self.packet_filter:
74
84
  filter_class = filters[self.packet_filter]
75
85
  if isinstance(packet, filter_class):
76
- packet_log.log(packet)
86
+ if self.log_packets:
87
+ packet_log.log(packet)
77
88
  if self.plugin_manager:
78
89
  # Don't do anything with the reply
79
90
  # This is the listen only command.
80
91
  self.plugin_manager.run(packet)
81
92
  else:
82
- packet_log.log(packet)
93
+ if self.log_packets:
94
+ packet_log.log(packet)
83
95
  if self.plugin_manager:
84
96
  # Don't do anything with the reply.
85
97
  # This is the listen only command.
@@ -88,6 +100,44 @@ class APRSDListenThread(rx.APRSDRXThread):
88
100
  packet_collector.PacketCollector().rx(packet)
89
101
 
90
102
 
103
+ class ListenStatsThread(APRSDThread):
104
+ """Log the stats from the PacketList."""
105
+
106
+ def __init__(self):
107
+ super().__init__("PacketStatsLog")
108
+ self._last_total_rx = 0
109
+
110
+ def loop(self):
111
+ if self.loop_count % 10 == 0:
112
+ # log the stats every 10 seconds
113
+ stats_json = collector.Collector().collect()
114
+ stats = stats_json["PacketList"]
115
+ total_rx = stats["rx"]
116
+ packet_count = len(stats["packets"])
117
+ rx_delta = total_rx - self._last_total_rx
118
+ rate = rx_delta / 10
119
+
120
+ # Log summary stats
121
+ LOGU.opt(colors=True).info(
122
+ f"<green>RX Rate: {rate} pps</green> "
123
+ f"<yellow>Total RX: {total_rx}</yellow> "
124
+ f"<red>RX Last 10 secs: {rx_delta}</red> "
125
+ f"<white>Packets in PacketList: {packet_count}</white>",
126
+ )
127
+ self._last_total_rx = total_rx
128
+
129
+ # Log individual type stats
130
+ for k, v in stats["types"].items():
131
+ thread_hex = f"fg {utils.hex_from_name(k)}"
132
+ LOGU.opt(colors=True).info(
133
+ f"<{thread_hex}>{k:<15}</{thread_hex}> "
134
+ f"<blue>RX: {v['rx']}</blue> <red>TX: {v['tx']}</red>",
135
+ )
136
+
137
+ time.sleep(1)
138
+ return True
139
+
140
+
91
141
  @cli.command()
92
142
  @cli_helper.add_options(cli_helper.common_options)
93
143
  @click.option(
@@ -122,6 +172,11 @@ class APRSDListenThread(rx.APRSDRXThread):
122
172
  ),
123
173
  help="Filter by packet type",
124
174
  )
175
+ @click.option(
176
+ "--enable-plugin",
177
+ multiple=True,
178
+ help="Enable a plugin. This is the name of the file in the plugins directory.",
179
+ )
125
180
  @click.option(
126
181
  "--load-plugins",
127
182
  default=False,
@@ -133,6 +188,18 @@ class APRSDListenThread(rx.APRSDRXThread):
133
188
  nargs=-1,
134
189
  required=True,
135
190
  )
191
+ @click.option(
192
+ "--log-packets",
193
+ default=False,
194
+ is_flag=True,
195
+ help="Log incoming packets.",
196
+ )
197
+ @click.option(
198
+ "--enable-packet-stats",
199
+ default=False,
200
+ is_flag=True,
201
+ help="Enable packet stats periodic logging.",
202
+ )
136
203
  @click.pass_context
137
204
  @cli_helper.process_standard_options
138
205
  def listen(
@@ -140,8 +207,11 @@ def listen(
140
207
  aprs_login,
141
208
  aprs_password,
142
209
  packet_filter,
210
+ enable_plugin,
143
211
  load_plugins,
144
212
  filter,
213
+ log_packets,
214
+ enable_packet_stats,
145
215
  ):
146
216
  """Listen to packets on the APRS-IS Network based on FILTER.
147
217
 
@@ -190,27 +260,43 @@ def listen(
190
260
  LOG.info("Creating client connection")
191
261
  aprs_client = client_factory.create()
192
262
  LOG.info(aprs_client)
263
+ if not aprs_client.login_success:
264
+ # We failed to login, will just quit!
265
+ msg = f"Login Failure: {aprs_client.login_failure}"
266
+ LOG.error(msg)
267
+ print(msg)
268
+ sys.exit(-1)
193
269
 
194
270
  LOG.debug(f"Filter by '{filter}'")
195
271
  aprs_client.set_filter(filter)
196
272
 
197
- keepalive = keep_alive.KeepAliveThread()
198
- # keepalive.start()
273
+ keepalive_thread = keepalive.KeepAliveThread()
199
274
 
200
275
  if not CONF.enable_seen_list:
201
276
  # just deregister the class from the packet collector
202
277
  packet_collector.PacketCollector().unregister(seen_list.SeenList)
203
278
 
204
279
  pm = None
205
- pm = plugin.PluginManager()
206
280
  if load_plugins:
281
+ pm = plugin.PluginManager()
207
282
  LOG.info("Loading plugins")
208
283
  pm.setup_plugins(load_help_plugin=False)
284
+ elif enable_plugin:
285
+ pm = plugin.PluginManager()
286
+ pm.setup_plugins(
287
+ load_help_plugin=False,
288
+ plugin_list=enable_plugin,
289
+ )
209
290
  else:
210
291
  LOG.warning(
211
292
  "Not Loading any plugins use --load-plugins to load what's "
212
293
  "defined in the config file.",
213
294
  )
295
+
296
+ if pm:
297
+ for p in pm.get_plugins():
298
+ LOG.info("Loaded plugin %s", p.__class__.__name__)
299
+
214
300
  stats = stats_thread.APRSDStatsStoreThread()
215
301
  stats.start()
216
302
 
@@ -219,13 +305,18 @@ def listen(
219
305
  packet_queue=threads.packet_queue,
220
306
  packet_filter=packet_filter,
221
307
  plugin_manager=pm,
308
+ enabled_plugins=enable_plugin,
309
+ log_packets=log_packets,
222
310
  )
223
311
  LOG.debug("Start APRSDListenThread")
224
312
  listen_thread.start()
313
+ if enable_packet_stats:
314
+ listen_stats = ListenStatsThread()
315
+ listen_stats.start()
225
316
 
226
- keepalive.start()
317
+ keepalive_thread.start()
227
318
  LOG.debug("keepalive Join")
228
- keepalive.join()
319
+ keepalive_thread.join()
229
320
  LOG.debug("listen_thread Join")
230
321
  listen_thread.join()
231
322
  stats.join()