aprsd 3.4.2__py3-none-any.whl → 3.4.4__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 (41) hide show
  1. aprsd/client/aprsis.py +39 -10
  2. aprsd/client/base.py +47 -10
  3. aprsd/client/drivers/aprsis.py +5 -2
  4. aprsd/client/factory.py +6 -1
  5. aprsd/client/fake.py +5 -2
  6. aprsd/client/kiss.py +26 -5
  7. aprsd/client/stats.py +1 -19
  8. aprsd/cmds/admin.py +57 -0
  9. aprsd/cmds/dev.py +1 -1
  10. aprsd/cmds/fetch_stats.py +155 -0
  11. aprsd/cmds/healthcheck.py +2 -2
  12. aprsd/cmds/listen.py +93 -6
  13. aprsd/cmds/send_message.py +2 -1
  14. aprsd/cmds/server.py +18 -10
  15. aprsd/cmds/webchat.py +41 -53
  16. aprsd/conf/common.py +11 -0
  17. aprsd/main.py +10 -6
  18. aprsd/packets/core.py +28 -27
  19. aprsd/packets/packet_list.py +13 -23
  20. aprsd/plugin.py +12 -5
  21. aprsd/plugins/email.py +6 -0
  22. aprsd/plugins/fortune.py +2 -2
  23. aprsd/plugins/location.py +6 -4
  24. aprsd/stats/__init__.py +0 -2
  25. aprsd/stats/collector.py +6 -1
  26. aprsd/threads/aprsd.py +47 -5
  27. aprsd/threads/keep_alive.py +8 -0
  28. aprsd/threads/rx.py +40 -12
  29. aprsd/threads/tx.py +31 -6
  30. aprsd/utils/__init__.py +20 -5
  31. aprsd/utils/counter.py +15 -12
  32. aprsd/utils/trace.py +4 -2
  33. aprsd/web/chat/static/js/send-message.js +48 -21
  34. aprsd/wsgi.py +8 -1
  35. {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/METADATA +89 -33
  36. {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/RECORD +41 -40
  37. {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/WHEEL +1 -1
  38. {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/AUTHORS +0 -0
  39. {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/LICENSE +0 -0
  40. {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/entry_points.txt +0 -0
  41. {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/top_level.txt +0 -0
aprsd/cmds/listen.py CHANGED
@@ -10,12 +10,13 @@ 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
@@ -24,12 +25,14 @@ from aprsd.packets import seen_list
24
25
  from aprsd.stats import collector
25
26
  from aprsd.threads import keep_alive, rx
26
27
  from aprsd.threads import stats as stats_thread
28
+ from aprsd.threads.aprsd import APRSDThread
27
29
 
28
30
 
29
31
  # setup the global logger
30
32
  # log.basicConfig(level=log.DEBUG) # level=10
31
33
  LOG = logging.getLogger("APRSD")
32
34
  CONF = cfg.CONF
35
+ LOGU = logger
33
36
  console = Console()
34
37
 
35
38
 
@@ -47,12 +50,16 @@ def signal_handler(sig, frame):
47
50
 
48
51
 
49
52
  class APRSDListenThread(rx.APRSDRXThread):
50
- def __init__(self, packet_queue, packet_filter=None, plugin_manager=None):
53
+ def __init__(
54
+ self, packet_queue, packet_filter=None, plugin_manager=None,
55
+ enabled_plugins=[], log_packets=False,
56
+ ):
51
57
  super().__init__(packet_queue)
52
58
  self.packet_filter = packet_filter
53
59
  self.plugin_manager = plugin_manager
54
60
  if self.plugin_manager:
55
61
  LOG.info(f"Plugins {self.plugin_manager.get_message_plugins()}")
62
+ self.log_packets = log_packets
56
63
 
57
64
  def process_packet(self, *args, **kwargs):
58
65
  packet = self._client.decode_packet(*args, **kwargs)
@@ -73,13 +80,16 @@ class APRSDListenThread(rx.APRSDRXThread):
73
80
  if self.packet_filter:
74
81
  filter_class = filters[self.packet_filter]
75
82
  if isinstance(packet, filter_class):
76
- packet_log.log(packet)
83
+ if self.log_packets:
84
+ packet_log.log(packet)
77
85
  if self.plugin_manager:
78
86
  # Don't do anything with the reply
79
87
  # This is the listen only command.
80
88
  self.plugin_manager.run(packet)
81
89
  else:
82
- packet_log.log(packet)
90
+ if self.log_packets:
91
+ LOG.error("PISS")
92
+ packet_log.log(packet)
83
93
  if self.plugin_manager:
84
94
  # Don't do anything with the reply.
85
95
  # This is the listen only command.
@@ -88,6 +98,42 @@ class APRSDListenThread(rx.APRSDRXThread):
88
98
  packet_collector.PacketCollector().rx(packet)
89
99
 
90
100
 
101
+ class ListenStatsThread(APRSDThread):
102
+ """Log the stats from the PacketList."""
103
+
104
+ def __init__(self):
105
+ super().__init__("PacketStatsLog")
106
+ self._last_total_rx = 0
107
+
108
+ def loop(self):
109
+ if self.loop_count % 10 == 0:
110
+ # log the stats every 10 seconds
111
+ stats_json = collector.Collector().collect()
112
+ stats = stats_json["PacketList"]
113
+ total_rx = stats["rx"]
114
+ rx_delta = total_rx - self._last_total_rx
115
+ rate = rx_delta / 10
116
+
117
+ # Log summary stats
118
+ LOGU.opt(colors=True).info(
119
+ f"<green>RX Rate: {rate} pps</green> "
120
+ f"<yellow>Total RX: {total_rx}</yellow> "
121
+ f"<red>RX Last 10 secs: {rx_delta}</red>",
122
+ )
123
+ self._last_total_rx = total_rx
124
+
125
+ # Log individual type stats
126
+ for k, v in stats["types"].items():
127
+ thread_hex = f"fg {utils.hex_from_name(k)}"
128
+ LOGU.opt(colors=True).info(
129
+ f"<{thread_hex}>{k:<15}</{thread_hex}> "
130
+ f"<blue>RX: {v['rx']}</blue> <red>TX: {v['tx']}</red>",
131
+ )
132
+
133
+ time.sleep(1)
134
+ return True
135
+
136
+
91
137
  @cli.command()
92
138
  @cli_helper.add_options(cli_helper.common_options)
93
139
  @click.option(
@@ -122,6 +168,11 @@ class APRSDListenThread(rx.APRSDRXThread):
122
168
  ),
123
169
  help="Filter by packet type",
124
170
  )
171
+ @click.option(
172
+ "--enable-plugin",
173
+ multiple=True,
174
+ help="Enable a plugin. This is the name of the file in the plugins directory.",
175
+ )
125
176
  @click.option(
126
177
  "--load-plugins",
127
178
  default=False,
@@ -133,6 +184,18 @@ class APRSDListenThread(rx.APRSDRXThread):
133
184
  nargs=-1,
134
185
  required=True,
135
186
  )
187
+ @click.option(
188
+ "--log-packets",
189
+ default=False,
190
+ is_flag=True,
191
+ help="Log incoming packets.",
192
+ )
193
+ @click.option(
194
+ "--enable-packet-stats",
195
+ default=False,
196
+ is_flag=True,
197
+ help="Enable packet stats periodic logging.",
198
+ )
136
199
  @click.pass_context
137
200
  @cli_helper.process_standard_options
138
201
  def listen(
@@ -140,8 +203,11 @@ def listen(
140
203
  aprs_login,
141
204
  aprs_password,
142
205
  packet_filter,
206
+ enable_plugin,
143
207
  load_plugins,
144
208
  filter,
209
+ log_packets,
210
+ enable_packet_stats,
145
211
  ):
146
212
  """Listen to packets on the APRS-IS Network based on FILTER.
147
213
 
@@ -190,27 +256,43 @@ def listen(
190
256
  LOG.info("Creating client connection")
191
257
  aprs_client = client_factory.create()
192
258
  LOG.info(aprs_client)
259
+ if not aprs_client.login_success:
260
+ # We failed to login, will just quit!
261
+ msg = f"Login Failure: {aprs_client.login_failure}"
262
+ LOG.error(msg)
263
+ print(msg)
264
+ sys.exit(-1)
193
265
 
194
266
  LOG.debug(f"Filter by '{filter}'")
195
267
  aprs_client.set_filter(filter)
196
268
 
197
269
  keepalive = keep_alive.KeepAliveThread()
198
- # keepalive.start()
199
270
 
200
271
  if not CONF.enable_seen_list:
201
272
  # just deregister the class from the packet collector
202
273
  packet_collector.PacketCollector().unregister(seen_list.SeenList)
203
274
 
204
275
  pm = None
205
- pm = plugin.PluginManager()
206
276
  if load_plugins:
277
+ pm = plugin.PluginManager()
207
278
  LOG.info("Loading plugins")
208
279
  pm.setup_plugins(load_help_plugin=False)
280
+ elif enable_plugin:
281
+ pm = plugin.PluginManager()
282
+ pm.setup_plugins(
283
+ load_help_plugin=False,
284
+ plugin_list=enable_plugin,
285
+ )
209
286
  else:
210
287
  LOG.warning(
211
288
  "Not Loading any plugins use --load-plugins to load what's "
212
289
  "defined in the config file.",
213
290
  )
291
+
292
+ if pm:
293
+ for p in pm.get_plugins():
294
+ LOG.info("Loaded plugin %s", p.__class__.__name__)
295
+
214
296
  stats = stats_thread.APRSDStatsStoreThread()
215
297
  stats.start()
216
298
 
@@ -219,9 +301,14 @@ def listen(
219
301
  packet_queue=threads.packet_queue,
220
302
  packet_filter=packet_filter,
221
303
  plugin_manager=pm,
304
+ enabled_plugins=enable_plugin,
305
+ log_packets=log_packets,
222
306
  )
223
307
  LOG.debug("Start APRSDListenThread")
224
308
  listen_thread.start()
309
+ if enable_packet_stats:
310
+ listen_stats = ListenStatsThread()
311
+ listen_stats.start()
225
312
 
226
313
  keepalive.start()
227
314
  LOG.debug("keepalive Join")
@@ -14,6 +14,7 @@ from aprsd.client import client_factory
14
14
  from aprsd.main import cli
15
15
  import aprsd.packets # noqa : F401
16
16
  from aprsd.packets import collector
17
+ from aprsd.packets import log as packet_log
17
18
  from aprsd.threads import tx
18
19
 
19
20
 
@@ -103,7 +104,7 @@ def send_message(
103
104
  cl = client_factory.create()
104
105
  packet = cl.decode_packet(packet)
105
106
  collector.PacketCollector().rx(packet)
106
- packet.log("RX")
107
+ packet_log.log(packet, tx=False)
107
108
  # LOG.debug("Got packet back {}".format(packet))
108
109
  if isinstance(packet, packets.AckPacket):
109
110
  got_ack = True
aprsd/cmds/server.py CHANGED
@@ -54,10 +54,27 @@ def server(ctx, flush):
54
54
  LOG.error("No Clients are enabled in config.")
55
55
  sys.exit(-1)
56
56
 
57
+ # Make sure we have 1 client transport enabled
58
+ if not client_factory.is_client_enabled():
59
+ LOG.error("No Clients are enabled in config.")
60
+ sys.exit(-1)
61
+
62
+ if not client_factory.is_client_configured():
63
+ LOG.error("APRS client is not properly configured in config file.")
64
+ sys.exit(-1)
65
+
57
66
  # Creates the client object
58
67
  LOG.info("Creating client connection")
59
68
  aprs_client = client_factory.create()
60
69
  LOG.info(aprs_client)
70
+ if not aprs_client.login_success:
71
+ # We failed to login, will just quit!
72
+ msg = f"Login Failure: {aprs_client.login_failure}"
73
+ LOG.error(msg)
74
+ print(msg)
75
+ sys.exit(-1)
76
+
77
+ # Check to make sure the login worked.
61
78
 
62
79
  # Create the initial PM singleton and Register plugins
63
80
  # We register plugins first here so we can register each
@@ -65,7 +82,7 @@ def server(ctx, flush):
65
82
  # log file output.
66
83
  LOG.info("Loading Plugin Manager and registering plugins")
67
84
  plugin_manager = plugin.PluginManager()
68
- plugin_manager.setup_plugins()
85
+ plugin_manager.setup_plugins(load_help_plugin=CONF.load_help_plugin)
69
86
 
70
87
  # Dump all the config options now.
71
88
  CONF.log_opt_values(LOG, logging.DEBUG)
@@ -78,15 +95,6 @@ def server(ctx, flush):
78
95
  for p in watchlist_plugins:
79
96
  LOG.info(p)
80
97
 
81
- # Make sure we have 1 client transport enabled
82
- if not client_factory.is_client_enabled():
83
- LOG.error("No Clients are enabled in config.")
84
- sys.exit(-1)
85
-
86
- if not client_factory.is_client_configured():
87
- LOG.error("APRS client is not properly configured in config file.")
88
- sys.exit(-1)
89
-
90
98
  if not CONF.enable_seen_list:
91
99
  # just deregister the class from the packet collector
92
100
  packet_collector.PacketCollector().unregister(seen_list.SeenList)
aprsd/cmds/webchat.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import datetime
2
2
  import json
3
3
  import logging
4
- import math
5
4
  import signal
6
5
  import sys
7
6
  import threading
@@ -14,17 +13,20 @@ from flask_httpauth import HTTPBasicAuth
14
13
  from flask_socketio import Namespace, SocketIO
15
14
  from geopy.distance import geodesic
16
15
  from oslo_config import cfg
16
+ import timeago
17
17
  from werkzeug.security import check_password_hash, generate_password_hash
18
18
  import wrapt
19
19
 
20
20
  import aprsd
21
- from aprsd import (
22
- cli_helper, client, packets, plugin_utils, stats, threads, utils,
23
- )
21
+ from aprsd import cli_helper, client, packets, plugin_utils, stats, threads
22
+ from aprsd import utils
23
+ from aprsd import utils as aprsd_utils
24
24
  from aprsd.client import client_factory, kiss
25
25
  from aprsd.main import cli
26
26
  from aprsd.threads import aprsd as aprsd_threads
27
- from aprsd.threads import keep_alive, rx, tx
27
+ from aprsd.threads import keep_alive, rx
28
+ from aprsd.threads import stats as stats_thread
29
+ from aprsd.threads import tx
28
30
  from aprsd.utils import trace
29
31
 
30
32
 
@@ -36,7 +38,7 @@ socketio = None
36
38
 
37
39
  # List of callsigns that we don't want to track/fetch their location
38
40
  callsign_no_track = [
39
- "REPEAT", "WB4BOR-11", "APDW16", "WXNOW", "WXBOT", "BLN0", "BLN1", "BLN2",
41
+ "APDW16", "BLN0", "BLN1", "BLN2",
40
42
  "BLN3", "BLN4", "BLN5", "BLN6", "BLN7", "BLN8", "BLN9",
41
43
  ]
42
44
 
@@ -131,47 +133,6 @@ def verify_password(username, password):
131
133
  return username
132
134
 
133
135
 
134
- def calculate_initial_compass_bearing(point_a, point_b):
135
- """
136
- Calculates the bearing between two points.
137
- The formulae used is the following:
138
- θ = atan2(sin(Δlong).cos(lat2),
139
- cos(lat1).sin(lat2) − sin(lat1).cos(lat2).cos(Δlong))
140
- :Parameters:
141
- - `pointA: The tuple representing the latitude/longitude for the
142
- first point. Latitude and longitude must be in decimal degrees
143
- - `pointB: The tuple representing the latitude/longitude for the
144
- second point. Latitude and longitude must be in decimal degrees
145
- :Returns:
146
- The bearing in degrees
147
- :Returns Type:
148
- float
149
- """
150
- if (type(point_a) is not tuple) or (type(point_b) is not tuple):
151
- raise TypeError("Only tuples are supported as arguments")
152
-
153
- lat1 = math.radians(point_a[0])
154
- lat2 = math.radians(point_b[0])
155
-
156
- diff_long = math.radians(point_b[1] - point_a[1])
157
-
158
- x = math.sin(diff_long) * math.cos(lat2)
159
- y = math.cos(lat1) * math.sin(lat2) - (
160
- math.sin(lat1)
161
- * math.cos(lat2) * math.cos(diff_long)
162
- )
163
-
164
- initial_bearing = math.atan2(x, y)
165
-
166
- # Now we have the initial bearing but math.atan2 return values
167
- # from -180° to + 180° which is not what we want for a compass bearing
168
- # The solution is to normalize the initial bearing as shown below
169
- initial_bearing = math.degrees(initial_bearing)
170
- compass_bearing = (initial_bearing + 360) % 360
171
-
172
- return compass_bearing
173
-
174
-
175
136
  def _build_location_from_repeat(message):
176
137
  # This is a location message Format is
177
138
  # ^ld^callsign:latitude,longitude,altitude,course,speed,timestamp
@@ -188,16 +149,19 @@ def _build_location_from_repeat(message):
188
149
  course = float(b[3])
189
150
  speed = float(b[4])
190
151
  time = int(b[5])
152
+ compass_bearing = aprsd_utils.degrees_to_cardinal(course)
191
153
  data = {
192
154
  "callsign": callsign,
193
155
  "lat": lat,
194
156
  "lon": lon,
195
157
  "altitude": alt,
196
158
  "course": course,
159
+ "compass_bearing": compass_bearing,
197
160
  "speed": speed,
198
161
  "lasttime": time,
162
+ "timeago": timeago.format(time),
199
163
  }
200
- LOG.warning(f"Location data from REPEAT {data}")
164
+ LOG.debug(f"Location data from REPEAT {data}")
201
165
  return data
202
166
 
203
167
 
@@ -208,25 +172,32 @@ def _calculate_location_data(location_data):
208
172
  alt = location_data["altitude"]
209
173
  speed = location_data["speed"]
210
174
  lasttime = location_data["lasttime"]
175
+ timeago_str = location_data.get(
176
+ "timeago",
177
+ timeago.format(lasttime),
178
+ )
211
179
  # now calculate distance from our own location
212
180
  distance = 0
213
181
  if CONF.webchat.latitude and CONF.webchat.longitude:
214
182
  our_lat = float(CONF.webchat.latitude)
215
183
  our_lon = float(CONF.webchat.longitude)
216
184
  distance = geodesic((our_lat, our_lon), (lat, lon)).kilometers
217
- bearing = calculate_initial_compass_bearing(
185
+ bearing = aprsd_utils.calculate_initial_compass_bearing(
218
186
  (our_lat, our_lon),
219
187
  (lat, lon),
220
188
  )
189
+ compass_bearing = aprsd_utils.degrees_to_cardinal(bearing)
221
190
  return {
222
191
  "callsign": location_data["callsign"],
223
192
  "lat": lat,
224
193
  "lon": lon,
225
194
  "altitude": alt,
226
195
  "course": f"{bearing:0.1f}",
196
+ "compass_bearing": compass_bearing,
227
197
  "speed": speed,
228
198
  "lasttime": lasttime,
229
- "distance": f"{distance:0.3f}",
199
+ "timeago": timeago_str,
200
+ "distance": f"{distance:0.1f}",
230
201
  }
231
202
 
232
203
 
@@ -348,6 +319,7 @@ class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
348
319
  elif (
349
320
  from_call not in callsign_locations
350
321
  and from_call not in callsign_no_track
322
+ and client_factory.create().transport() in [client.TRANSPORT_APRSIS, client.TRANSPORT_FAKE]
351
323
  ):
352
324
  # We have to ask aprs for the location for the callsign
353
325
  # We send a message packet to wb4bor-11 asking for location.
@@ -406,7 +378,8 @@ def _get_transport(stats):
406
378
  @flask_app.route("/location/<callsign>", methods=["POST"])
407
379
  def location(callsign):
408
380
  LOG.debug(f"Fetch location for callsign {callsign}")
409
- populate_callsign_location(callsign)
381
+ if not callsign in callsign_no_track:
382
+ populate_callsign_location(callsign)
410
383
 
411
384
 
412
385
  @auth.login_required
@@ -527,8 +500,8 @@ class SendMessageNamespace(Namespace):
527
500
  pkt.prepare()
528
501
  self.msg = pkt
529
502
  msgs = SentMessages()
530
- msgs.add(pkt)
531
503
  tx.send(pkt)
504
+ msgs.add(pkt)
532
505
  msgs.set_status(pkt.msgNo, "Sending")
533
506
  obj = msgs.get(pkt.msgNo)
534
507
  socketio.emit(
@@ -571,7 +544,8 @@ class SendMessageNamespace(Namespace):
571
544
 
572
545
  def on_get_callsign_location(self, data):
573
546
  LOG.debug(f"on_callsign_location {data}")
574
- populate_callsign_location(data["callsign"])
547
+ if data["callsign"] not in callsign_no_track:
548
+ populate_callsign_location(data["callsign"])
575
549
 
576
550
 
577
551
  @trace.trace
@@ -645,10 +619,24 @@ def webchat(ctx, flush, port):
645
619
  LOG.error("APRS client is not properly configured in config file.")
646
620
  sys.exit(-1)
647
621
 
622
+ # Creates the client object
623
+ LOG.info("Creating client connection")
624
+ aprs_client = client_factory.create()
625
+ LOG.info(aprs_client)
626
+ if not aprs_client.login_success:
627
+ # We failed to login, will just quit!
628
+ msg = f"Login Failure: {aprs_client.login_failure}"
629
+ LOG.error(msg)
630
+ print(msg)
631
+ sys.exit(-1)
632
+
648
633
  keepalive = keep_alive.KeepAliveThread()
649
634
  LOG.info("Start KeepAliveThread")
650
635
  keepalive.start()
651
636
 
637
+ stats_store_thread = stats_thread.APRSDStatsStoreThread()
638
+ stats_store_thread.start()
639
+
652
640
  socketio = init_flask(loglevel, quiet)
653
641
  rx_thread = rx.APRSDPluginRXThread(
654
642
  packet_queue=threads.packet_queue,
aprsd/conf/common.py CHANGED
@@ -136,6 +136,17 @@ aprsd_opts = [
136
136
  default=True,
137
137
  help="Set this to False, to disable logging of packets to the log file.",
138
138
  ),
139
+ cfg.BoolOpt(
140
+ "load_help_plugin",
141
+ default=True,
142
+ help="Set this to False to disable the help plugin.",
143
+ ),
144
+ cfg.BoolOpt(
145
+ "enable_sending_ack_packets",
146
+ default=True,
147
+ help="Set this to False, to disable sending of ack packets. This will entirely stop"
148
+ "APRSD from sending ack packets.",
149
+ ),
139
150
  ]
140
151
 
141
152
  watch_list_opts = [
aprsd/main.py CHANGED
@@ -54,7 +54,7 @@ def cli(ctx):
54
54
 
55
55
  def load_commands():
56
56
  from .cmds import ( # noqa
57
- completion, dev, fetch_stats, healthcheck, list_plugins, listen,
57
+ admin, completion, dev, fetch_stats, healthcheck, list_plugins, listen,
58
58
  send_message, server, webchat,
59
59
  )
60
60
 
@@ -79,11 +79,15 @@ def signal_handler(sig, frame):
79
79
  ),
80
80
  )
81
81
  time.sleep(1.5)
82
- packets.PacketTrack().save()
83
- packets.WatchList().save()
84
- packets.SeenList().save()
85
- packets.PacketList().save()
86
- collector.Collector().collect()
82
+ try:
83
+ packets.PacketTrack().save()
84
+ packets.WatchList().save()
85
+ packets.SeenList().save()
86
+ packets.PacketList().save()
87
+ collector.Collector().collect()
88
+ except Exception as e:
89
+ LOG.error(f"Failed to save data: {e}")
90
+ sys.exit(0)
87
91
  # signal.signal(signal.SIGTERM, sys.exit(0))
88
92
  # sys.exit(0)
89
93
 
aprsd/packets/core.py CHANGED
@@ -12,7 +12,7 @@ from dataclasses_json import (
12
12
  )
13
13
  from loguru import logger
14
14
 
15
- from aprsd.utils import counter
15
+ from aprsd.utils import counter, trace
16
16
 
17
17
 
18
18
  # For mypy to be happy
@@ -63,15 +63,11 @@ def _init_msgNo(): # noqa: N802
63
63
 
64
64
 
65
65
  def _translate_fields(raw: dict) -> dict:
66
- translate_fields = {
67
- "from": "from_call",
68
- "to": "to_call",
69
- }
70
- # First translate some fields
71
- for key in translate_fields:
72
- if key in raw:
73
- raw[translate_fields[key]] = raw[key]
74
- del raw[key]
66
+ # Direct key checks instead of iteration
67
+ if "from" in raw:
68
+ raw["from_call"] = raw.pop("from")
69
+ if "to" in raw:
70
+ raw["to_call"] = raw.pop("to")
75
71
 
76
72
  # addresse overrides to_call
77
73
  if "addresse" in raw:
@@ -103,6 +99,8 @@ class Packet:
103
99
  send_count: int = field(repr=False, default=0, compare=False, hash=False)
104
100
  retry_count: int = field(repr=False, default=3, compare=False, hash=False)
105
101
  last_send_time: float = field(repr=False, default=0, compare=False, hash=False)
102
+ # Was the packet acked?
103
+ acked: bool = field(repr=False, default=False, compare=False, hash=False)
106
104
 
107
105
  # Do we allow this packet to be saved to send later?
108
106
  allow_delay: bool = field(repr=False, default=True, compare=False, hash=False)
@@ -110,11 +108,7 @@ class Packet:
110
108
  via: Optional[str] = field(default=None, compare=False, hash=False)
111
109
 
112
110
  def get(self, key: str, default: Optional[str] = None):
113
- """Emulate a getter on a dict."""
114
- if hasattr(self, key):
115
- return getattr(self, key)
116
- else:
117
- return default
111
+ return getattr(self, key, default)
118
112
 
119
113
  @property
120
114
  def key(self) -> str:
@@ -135,10 +129,11 @@ class Packet:
135
129
  msg = self._filter_for_send(self.raw).rstrip("\n")
136
130
  return msg
137
131
 
138
- def prepare(self) -> None:
132
+ @trace.trace
133
+ def prepare(self, create_msg_number=False) -> None:
139
134
  """Do stuff here that is needed prior to sending over the air."""
140
135
  # now build the raw message for sending
141
- if not self.msgNo:
136
+ if not self.msgNo and create_msg_number:
142
137
  self.msgNo = _init_msgNo()
143
138
  self._build_payload()
144
139
  self._build_raw()
@@ -252,11 +247,17 @@ class MessagePacket(Packet):
252
247
  return self._filter_for_send(self.message_text).rstrip("\n")
253
248
 
254
249
  def _build_payload(self):
255
- self.payload = ":{}:{}{{{}".format(
256
- self.to_call.ljust(9),
257
- self._filter_for_send(self.message_text).rstrip("\n"),
258
- str(self.msgNo),
259
- )
250
+ if self.msgNo:
251
+ self.payload = ":{}:{}{{{}".format(
252
+ self.to_call.ljust(9),
253
+ self._filter_for_send(self.message_text).rstrip("\n"),
254
+ str(self.msgNo),
255
+ )
256
+ else:
257
+ self.payload = ":{}:{}".format(
258
+ self.to_call.ljust(9),
259
+ self._filter_for_send(self.message_text).rstrip("\n"),
260
+ )
260
261
 
261
262
 
262
263
  @dataclass_json
@@ -393,7 +394,7 @@ class BeaconPacket(GPSPacket):
393
394
  if self.raw_timestamp:
394
395
  return f"{self.from_call}:{self.raw_timestamp}"
395
396
  else:
396
- return f"{self.from_call}:{self.human_info.replace(' ','')}"
397
+ return f"{self.from_call}:{self.human_info.replace(' ', '')}"
397
398
 
398
399
  @property
399
400
  def human_info(self) -> str:
@@ -449,7 +450,7 @@ class TelemetryPacket(GPSPacket):
449
450
  if self.raw_timestamp:
450
451
  return f"{self.from_call}:{self.raw_timestamp}"
451
452
  else:
452
- return f"{self.from_call}:{self.human_info.replace(' ','')}"
453
+ return f"{self.from_call}:{self.human_info.replace(' ', '')}"
453
454
 
454
455
  @property
455
456
  def human_info(self) -> str:
@@ -628,11 +629,11 @@ class WeatherPacket(GPSPacket, DataClassJsonMixin):
628
629
  # Temperature in degrees F
629
630
  f"t{self.temperature:03.0f}",
630
631
  # Rainfall (in hundredths of an inch) in the last hour
631
- f"r{self.rain_1h*100:03.0f}",
632
+ f"r{self.rain_1h * 100:03.0f}",
632
633
  # Rainfall (in hundredths of an inch) in last 24 hours
633
- f"p{self.rain_24h*100:03.0f}",
634
+ f"p{self.rain_24h * 100:03.0f}",
634
635
  # Rainfall (in hundredths of an inch) since midnigt
635
- f"P{self.rain_since_midnight*100:03.0f}",
636
+ f"P{self.rain_since_midnight * 100:03.0f}",
636
637
  # Humidity
637
638
  f"h{self.humidity:02d}",
638
639
  # Barometric pressure (in tenths of millibars/tenths of hPascal)