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.
- aprsd/client/aprsis.py +39 -10
- aprsd/client/base.py +47 -10
- aprsd/client/drivers/aprsis.py +5 -2
- aprsd/client/factory.py +6 -1
- aprsd/client/fake.py +5 -2
- aprsd/client/kiss.py +26 -5
- aprsd/client/stats.py +1 -19
- aprsd/cmds/admin.py +57 -0
- aprsd/cmds/dev.py +1 -1
- aprsd/cmds/fetch_stats.py +155 -0
- aprsd/cmds/healthcheck.py +2 -2
- aprsd/cmds/listen.py +93 -6
- aprsd/cmds/send_message.py +2 -1
- aprsd/cmds/server.py +18 -10
- aprsd/cmds/webchat.py +41 -53
- aprsd/conf/common.py +11 -0
- aprsd/main.py +10 -6
- aprsd/packets/core.py +28 -27
- aprsd/packets/packet_list.py +13 -23
- aprsd/plugin.py +12 -5
- aprsd/plugins/email.py +6 -0
- aprsd/plugins/fortune.py +2 -2
- aprsd/plugins/location.py +6 -4
- aprsd/stats/__init__.py +0 -2
- aprsd/stats/collector.py +6 -1
- aprsd/threads/aprsd.py +47 -5
- aprsd/threads/keep_alive.py +8 -0
- aprsd/threads/rx.py +40 -12
- aprsd/threads/tx.py +31 -6
- aprsd/utils/__init__.py +20 -5
- aprsd/utils/counter.py +15 -12
- aprsd/utils/trace.py +4 -2
- aprsd/web/chat/static/js/send-message.js +48 -21
- aprsd/wsgi.py +8 -1
- {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/METADATA +89 -33
- {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/RECORD +41 -40
- {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/WHEEL +1 -1
- {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/AUTHORS +0 -0
- {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/LICENSE +0 -0
- {aprsd-3.4.2.dist-info → aprsd-3.4.4.dist-info}/entry_points.txt +0 -0
- {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__(
|
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
|
-
|
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
|
-
|
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")
|
aprsd/cmds/send_message.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
"
|
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.
|
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
|
-
"
|
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
|
-
|
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
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
67
|
-
|
68
|
-
"
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
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.
|
256
|
-
self.
|
257
|
-
|
258
|
-
|
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)
|