aprsd 3.4.4__py3-none-any.whl → 4.0.1__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/cli_helper.py +12 -5
- aprsd/client/aprsis.py +31 -9
- aprsd/client/base.py +13 -2
- aprsd/client/drivers/aprsis.py +6 -3
- aprsd/client/drivers/fake.py +15 -20
- aprsd/client/factory.py +0 -2
- aprsd/client/fake.py +0 -2
- aprsd/client/kiss.py +17 -2
- aprsd/client/stats.py +1 -3
- aprsd/cmds/completion.py +7 -4
- aprsd/cmds/dev.py +38 -42
- aprsd/cmds/fetch_stats.py +140 -143
- aprsd/cmds/healthcheck.py +5 -3
- aprsd/cmds/list_plugins.py +140 -134
- aprsd/cmds/listen.py +13 -9
- aprsd/cmds/server.py +53 -27
- aprsd/conf/__init__.py +1 -2
- aprsd/conf/client.py +3 -4
- aprsd/conf/common.py +19 -93
- aprsd/conf/log.py +2 -4
- aprsd/conf/opts.py +5 -4
- aprsd/conf/plugin_common.py +11 -121
- aprsd/exception.py +2 -0
- aprsd/log/log.py +7 -46
- aprsd/main.py +10 -4
- aprsd/packets/__init__.py +14 -4
- aprsd/packets/core.py +57 -67
- aprsd/packets/log.py +8 -8
- aprsd/packets/packet_list.py +9 -6
- aprsd/plugin.py +22 -11
- aprsd/plugins/notify.py +1 -4
- aprsd/plugins/weather.py +10 -8
- aprsd/stats/collector.py +5 -2
- aprsd/threads/__init__.py +3 -2
- aprsd/threads/aprsd.py +12 -7
- aprsd/threads/{keep_alive.py → keepalive.py} +14 -45
- aprsd/threads/registry.py +3 -3
- aprsd/threads/rx.py +9 -6
- aprsd/threads/stats.py +2 -2
- aprsd/threads/tx.py +3 -4
- aprsd/utils/__init__.py +42 -10
- aprsd/utils/json.py +9 -4
- aprsd/utils/keepalive_collector.py +55 -0
- aprsd/utils/trace.py +0 -2
- aprsd-4.0.1.dist-info/AUTHORS +1 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/METADATA +307 -408
- aprsd-4.0.1.dist-info/RECORD +74 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/WHEEL +1 -1
- aprsd/cmds/admin.py +0 -57
- aprsd/cmds/webchat.py +0 -662
- aprsd/conf/plugin_email.py +0 -105
- aprsd/plugins/email.py +0 -715
- aprsd/plugins/location.py +0 -181
- aprsd/threads/log_monitor.py +0 -121
- aprsd/web/__init__.py +0 -0
- aprsd/web/admin/__init__.py +0 -0
- aprsd/web/admin/static/css/index.css +0 -84
- aprsd/web/admin/static/css/prism.css +0 -4
- aprsd/web/admin/static/css/tabs.css +0 -35
- aprsd/web/admin/static/images/Untitled.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/admin/static/js/charts.js +0 -235
- aprsd/web/admin/static/js/echarts.js +0 -465
- aprsd/web/admin/static/js/logs.js +0 -26
- aprsd/web/admin/static/js/main.js +0 -231
- aprsd/web/admin/static/js/prism.js +0 -12
- aprsd/web/admin/static/js/send-message.js +0 -114
- aprsd/web/admin/static/js/tabs.js +0 -28
- aprsd/web/admin/templates/index.html +0 -196
- aprsd/web/chat/static/css/chat.css +0 -115
- aprsd/web/chat/static/css/index.css +0 -66
- aprsd/web/chat/static/css/style.css.map +0 -1
- aprsd/web/chat/static/css/tabs.css +0 -41
- aprsd/web/chat/static/css/upstream/bootstrap.min.css +0 -6
- aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/google-fonts.css +0 -23
- aprsd/web/chat/static/css/upstream/jquery-ui.css +0 -1311
- aprsd/web/chat/static/css/upstream/jquery.toast.css +0 -28
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
- aprsd/web/chat/static/images/Untitled.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/chat/static/images/globe.svg +0 -3
- aprsd/web/chat/static/js/gps.js +0 -84
- aprsd/web/chat/static/js/main.js +0 -45
- aprsd/web/chat/static/js/send-message.js +0 -612
- aprsd/web/chat/static/js/tabs.js +0 -28
- aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +0 -7
- aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +0 -2
- aprsd/web/chat/static/js/upstream/jquery-ui.min.js +0 -13
- aprsd/web/chat/static/js/upstream/jquery.toast.js +0 -374
- aprsd/web/chat/static/js/upstream/semantic.min.js +0 -11
- aprsd/web/chat/static/js/upstream/socket.io.min.js +0 -7
- aprsd/web/chat/templates/index.html +0 -139
- aprsd/wsgi.py +0 -322
- aprsd-3.4.4.dist-info/AUTHORS +0 -13
- aprsd-3.4.4.dist-info/RECORD +0 -134
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/LICENSE +0 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/entry_points.txt +0 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/top_level.txt +0 -0
aprsd/packets/core.py
CHANGED
@@ -1,19 +1,22 @@
|
|
1
|
-
from dataclasses import dataclass, field
|
2
|
-
from datetime import datetime
|
3
1
|
import logging
|
4
2
|
import re
|
5
3
|
import time
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from datetime import datetime
|
6
|
+
|
6
7
|
# Due to a failure in python 3.8
|
7
8
|
from typing import Any, List, Optional, Type, TypeVar, Union
|
8
9
|
|
9
10
|
from aprslib import util as aprslib_util
|
10
11
|
from dataclasses_json import (
|
11
|
-
CatchAll,
|
12
|
+
CatchAll,
|
13
|
+
DataClassJsonMixin,
|
14
|
+
Undefined,
|
15
|
+
dataclass_json,
|
12
16
|
)
|
13
17
|
from loguru import logger
|
14
18
|
|
15
|
-
from aprsd.utils import counter
|
16
|
-
|
19
|
+
from aprsd.utils import counter
|
17
20
|
|
18
21
|
# For mypy to be happy
|
19
22
|
A = TypeVar("A", bound="DataClassJsonMixin")
|
@@ -51,7 +54,7 @@ def _init_send_time():
|
|
51
54
|
return NO_DATE
|
52
55
|
|
53
56
|
|
54
|
-
def _init_msgNo():
|
57
|
+
def _init_msgNo(): # noqa: N802
|
55
58
|
"""For some reason __post__init doesn't get called.
|
56
59
|
|
57
60
|
So in order to initialize the msgNo field in the packet
|
@@ -84,14 +87,16 @@ class Packet:
|
|
84
87
|
to_call: Optional[str] = field(default=None)
|
85
88
|
addresse: Optional[str] = field(default=None)
|
86
89
|
format: Optional[str] = field(default=None)
|
87
|
-
msgNo: Optional[str] = field(default=None)
|
88
|
-
ackMsgNo: Optional[str] = field(default=None)
|
90
|
+
msgNo: Optional[str] = field(default=None) # noqa: N815
|
91
|
+
ackMsgNo: Optional[str] = field(default=None) # noqa: N815
|
89
92
|
packet_type: Optional[str] = field(default=None)
|
90
93
|
timestamp: float = field(default_factory=_init_timestamp, compare=False, hash=False)
|
91
94
|
# Holds the raw text string to be sent over the wire
|
92
95
|
# or holds the raw string from input packet
|
93
96
|
raw: Optional[str] = field(default=None, compare=False, hash=False)
|
94
|
-
raw_dict: dict = field(
|
97
|
+
raw_dict: dict = field(
|
98
|
+
repr=False, default_factory=lambda: {}, compare=False, hash=False
|
99
|
+
)
|
95
100
|
# Built by calling prepare(). raw needs this built first.
|
96
101
|
payload: Optional[str] = field(default=None)
|
97
102
|
|
@@ -129,7 +134,6 @@ class Packet:
|
|
129
134
|
msg = self._filter_for_send(self.raw).rstrip("\n")
|
130
135
|
return msg
|
131
136
|
|
132
|
-
@trace.trace
|
133
137
|
def prepare(self, create_msg_number=False) -> None:
|
134
138
|
"""Do stuff here that is needed prior to sending over the air."""
|
135
139
|
# now build the raw message for sending
|
@@ -141,12 +145,12 @@ class Packet:
|
|
141
145
|
def _build_payload(self) -> None:
|
142
146
|
"""The payload is the non headers portion of the packet."""
|
143
147
|
if not self.to_call:
|
144
|
-
raise ValueError(
|
148
|
+
raise ValueError(
|
149
|
+
"to_call isn't set. Must set to_call before calling prepare()"
|
150
|
+
)
|
145
151
|
|
146
152
|
# The base packet class has no real payload
|
147
|
-
self.payload = (
|
148
|
-
f":{self.to_call.ljust(9)}"
|
149
|
-
)
|
153
|
+
self.payload = f":{self.to_call.ljust(9)}"
|
150
154
|
|
151
155
|
def _build_raw(self) -> None:
|
152
156
|
"""Build the self.raw which is what is sent over the air."""
|
@@ -167,8 +171,10 @@ class Packet:
|
|
167
171
|
message = msg[:67]
|
168
172
|
# We all miss George Carlin
|
169
173
|
return re.sub(
|
170
|
-
"fuck|shit|cunt|piss|cock|bitch",
|
171
|
-
|
174
|
+
"fuck|shit|cunt|piss|cock|bitch",
|
175
|
+
"****",
|
176
|
+
message,
|
177
|
+
flags=re.IGNORECASE,
|
172
178
|
)
|
173
179
|
|
174
180
|
def __str__(self) -> str:
|
@@ -215,10 +221,7 @@ class BulletinPacket(Packet):
|
|
215
221
|
return f"BLN{self.bid} {self.message_text}"
|
216
222
|
|
217
223
|
def _build_payload(self) -> None:
|
218
|
-
self.payload =
|
219
|
-
f":BLN{self.bid:<9}"
|
220
|
-
f":{self.message_text}"
|
221
|
-
)
|
224
|
+
self.payload = f":BLN{self.bid:<9}" f":{self.message_text}"
|
222
225
|
|
223
226
|
|
224
227
|
@dataclass_json
|
@@ -336,10 +339,7 @@ class GPSPacket(Packet):
|
|
336
339
|
self.payload = "".join(payload)
|
337
340
|
|
338
341
|
def _build_raw(self):
|
339
|
-
self.raw =
|
340
|
-
f"{self.from_call}>{self.to_call},WIDE2-1:"
|
341
|
-
f"{self.payload}"
|
342
|
-
)
|
342
|
+
self.raw = f"{self.from_call}>{self.to_call},WIDE2-1:" f"{self.payload}"
|
343
343
|
|
344
344
|
@property
|
345
345
|
def human_info(self) -> str:
|
@@ -371,10 +371,7 @@ class BeaconPacket(GPSPacket):
|
|
371
371
|
lat = aprslib_util.latitude_to_ddm(self.latitude)
|
372
372
|
lon = aprslib_util.longitude_to_ddm(self.longitude)
|
373
373
|
|
374
|
-
self.payload =
|
375
|
-
f"@{time_zulu}z{lat}{self.symbol_table}"
|
376
|
-
f"{lon}"
|
377
|
-
)
|
374
|
+
self.payload = f"@{time_zulu}z{lat}{self.symbol_table}" f"{lon}"
|
378
375
|
|
379
376
|
if self.comment:
|
380
377
|
comment = self._filter_for_send(self.comment)
|
@@ -383,10 +380,7 @@ class BeaconPacket(GPSPacket):
|
|
383
380
|
self.payload = f"{self.payload}{self.symbol}APRSD Beacon"
|
384
381
|
|
385
382
|
def _build_raw(self):
|
386
|
-
self.raw =
|
387
|
-
f"{self.from_call}>APZ100:"
|
388
|
-
f"{self.payload}"
|
389
|
-
)
|
383
|
+
self.raw = f"{self.from_call}>APZ100:" f"{self.payload}"
|
390
384
|
|
391
385
|
@property
|
392
386
|
def key(self) -> str:
|
@@ -475,10 +469,7 @@ class ObjectPacket(GPSPacket):
|
|
475
469
|
lat = aprslib_util.latitude_to_ddm(self.latitude)
|
476
470
|
long = aprslib_util.longitude_to_ddm(self.longitude)
|
477
471
|
|
478
|
-
self.payload =
|
479
|
-
f"*{time_zulu}z{lat}{self.symbol_table}"
|
480
|
-
f"{long}{self.symbol}"
|
481
|
-
)
|
472
|
+
self.payload = f"*{time_zulu}z{lat}{self.symbol_table}" f"{long}{self.symbol}"
|
482
473
|
|
483
474
|
if self.comment:
|
484
475
|
comment = self._filter_for_send(self.comment)
|
@@ -495,10 +486,7 @@ class ObjectPacket(GPSPacket):
|
|
495
486
|
The frequency, uplink_tone, offset is part of the comment
|
496
487
|
"""
|
497
488
|
|
498
|
-
self.raw =
|
499
|
-
f"{self.from_call}>APZ100:;{self.to_call:9s}"
|
500
|
-
f"{self.payload}"
|
501
|
-
)
|
489
|
+
self.raw = f"{self.from_call}>APZ100:;{self.to_call:9s}" f"{self.payload}"
|
502
490
|
|
503
491
|
@property
|
504
492
|
def human_info(self) -> str:
|
@@ -548,11 +536,13 @@ class WeatherPacket(GPSPacket, DataClassJsonMixin):
|
|
548
536
|
if "speed" in raw:
|
549
537
|
del raw["speed"]
|
550
538
|
# Let's adjust the rain numbers as well, since it's wrong
|
551
|
-
raw["rain_1h"] = round((raw.get("rain_1h", 0) / .254) * .01, 3)
|
539
|
+
raw["rain_1h"] = round((raw.get("rain_1h", 0) / 0.254) * 0.01, 3)
|
552
540
|
raw["weather"]["rain_1h"] = raw["rain_1h"]
|
553
|
-
raw["rain_24h"] = round((raw.get("rain_24h", 0) / .254) * .01, 3)
|
541
|
+
raw["rain_24h"] = round((raw.get("rain_24h", 0) / 0.254) * 0.01, 3)
|
554
542
|
raw["weather"]["rain_24h"] = raw["rain_24h"]
|
555
|
-
raw["rain_since_midnight"] = round(
|
543
|
+
raw["rain_since_midnight"] = round(
|
544
|
+
(raw.get("rain_since_midnight", 0) / 0.254) * 0.01, 3
|
545
|
+
)
|
556
546
|
raw["weather"]["rain_since_midnight"] = raw["rain_since_midnight"]
|
557
547
|
|
558
548
|
if "wind_direction" not in raw:
|
@@ -594,26 +584,26 @@ class WeatherPacket(GPSPacket, DataClassJsonMixin):
|
|
594
584
|
def _build_payload(self):
|
595
585
|
"""Build an uncompressed weather packet
|
596
586
|
|
597
|
-
|
587
|
+
Format =
|
598
588
|
|
599
|
-
|
600
|
-
|
589
|
+
_CSE/SPDgXXXtXXXrXXXpXXXPXXXhXXbXXXXX%type NEW FORMAT APRS793 June 97
|
590
|
+
NOT BACKWARD COMPATIBLE
|
601
591
|
|
602
592
|
|
603
|
-
|
604
|
-
|
593
|
+
Where: CSE/SPD is wind direction and sustained 1 minute speed
|
594
|
+
t is in degrees F
|
605
595
|
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
596
|
+
r is Rain per last 60 minutes
|
597
|
+
1.04 inches of rain will show as r104
|
598
|
+
p is precipitation per last 24 hours (sliding 24 hour window)
|
599
|
+
P is precip per last 24 hours since midnight
|
600
|
+
b is Baro in tenths of a mb
|
601
|
+
h is humidity in percent. 00=100
|
602
|
+
g is Gust (peak winds in last 5 minutes)
|
603
|
+
# is the raw rain counter for remote WX stations
|
604
|
+
See notes on remotes below
|
605
|
+
% shows software type d=Dos, m=Mac, w=Win, etc
|
606
|
+
type shows type of WX instrument
|
617
607
|
|
618
608
|
"""
|
619
609
|
time_zulu = self._build_time_zulu()
|
@@ -623,7 +613,8 @@ class WeatherPacket(GPSPacket, DataClassJsonMixin):
|
|
623
613
|
f"{self.longitude}{self.symbol}",
|
624
614
|
f"{self.wind_direction:03d}",
|
625
615
|
# Speed = sustained 1 minute wind speed in mph
|
626
|
-
f"{self.symbol_table}",
|
616
|
+
f"{self.symbol_table}",
|
617
|
+
f"{self.wind_speed:03.0f}",
|
627
618
|
# wind gust (peak wind speed in mph in the last 5 minutes)
|
628
619
|
f"g{self.wind_gust:03.0f}",
|
629
620
|
# Temperature in degrees F
|
@@ -645,11 +636,7 @@ class WeatherPacket(GPSPacket, DataClassJsonMixin):
|
|
645
636
|
self.payload = "".join(contents)
|
646
637
|
|
647
638
|
def _build_raw(self):
|
648
|
-
|
649
|
-
self.raw = (
|
650
|
-
f"{self.from_call}>{self.to_call},WIDE1-1,WIDE2-1:"
|
651
|
-
f"{self.payload}"
|
652
|
-
)
|
639
|
+
self.raw = f"{self.from_call}>{self.to_call},WIDE1-1,WIDE2-1:" f"{self.payload}"
|
653
640
|
|
654
641
|
|
655
642
|
@dataclass(unsafe_hash=True)
|
@@ -693,14 +680,17 @@ class UnknownPacket:
|
|
693
680
|
|
694
681
|
All of the unknown attributes are stored in the unknown_fields
|
695
682
|
"""
|
683
|
+
|
696
684
|
unknown_fields: CatchAll
|
697
685
|
_type: str = "UnknownPacket"
|
698
686
|
from_call: Optional[str] = field(default=None)
|
699
687
|
to_call: Optional[str] = field(default=None)
|
700
|
-
msgNo: str = field(default_factory=_init_msgNo)
|
688
|
+
msgNo: str = field(default_factory=_init_msgNo) # noqa: N815
|
701
689
|
format: Optional[str] = field(default=None)
|
702
690
|
raw: Optional[str] = field(default=None)
|
703
|
-
raw_dict: dict = field(
|
691
|
+
raw_dict: dict = field(
|
692
|
+
repr=False, default_factory=lambda: {}, compare=False, hash=False
|
693
|
+
)
|
704
694
|
path: List[str] = field(default_factory=list, compare=False, hash=False)
|
705
695
|
packet_type: Optional[str] = field(default=None)
|
706
696
|
via: Optional[str] = field(default=None, compare=False, hash=False)
|
aprsd/packets/log.py
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import Optional
|
3
3
|
|
4
|
-
from
|
4
|
+
from haversine import Unit, haversine
|
5
5
|
from loguru import logger
|
6
6
|
from oslo_config import cfg
|
7
7
|
|
8
8
|
from aprsd import utils
|
9
9
|
from aprsd.packets.core import AckPacket, GPSPacket, RejectPacket
|
10
10
|
|
11
|
-
|
12
11
|
LOG = logging.getLogger()
|
13
12
|
LOGU = logger
|
14
13
|
CONF = cfg.CONF
|
@@ -22,7 +21,9 @@ DISTANCE_COLOR = "fg #FF5733"
|
|
22
21
|
DEGREES_COLOR = "fg #FFA900"
|
23
22
|
|
24
23
|
|
25
|
-
def log_multiline(
|
24
|
+
def log_multiline(
|
25
|
+
packet, tx: Optional[bool] = False, header: Optional[bool] = True
|
26
|
+
) -> None:
|
26
27
|
"""LOG a packet to the logfile."""
|
27
28
|
if not CONF.enable_packet_logging:
|
28
29
|
return
|
@@ -121,8 +122,7 @@ def log(packet, tx: Optional[bool] = False, header: Optional[bool] = True) -> No
|
|
121
122
|
via_color = "green"
|
122
123
|
arrow = f"<{via_color}>-></{via_color}>"
|
123
124
|
logit.append(
|
124
|
-
f"<cyan>{name}</cyan>"
|
125
|
-
f":{packet.msgNo}",
|
125
|
+
f"<cyan>{name}</cyan>" f":{packet.msgNo}",
|
126
126
|
)
|
127
127
|
|
128
128
|
tmp = None
|
@@ -145,8 +145,8 @@ def log(packet, tx: Optional[bool] = False, header: Optional[bool] = True) -> No
|
|
145
145
|
|
146
146
|
# is there distance information?
|
147
147
|
if isinstance(packet, GPSPacket) and CONF.latitude and CONF.longitude:
|
148
|
-
my_coords = (CONF.latitude, CONF.longitude)
|
149
|
-
packet_coords = (packet.latitude, packet.longitude)
|
148
|
+
my_coords = (float(CONF.latitude), float(CONF.longitude))
|
149
|
+
packet_coords = (float(packet.latitude), float(packet.longitude))
|
150
150
|
try:
|
151
151
|
bearing = utils.calculate_initial_compass_bearing(my_coords, packet_coords)
|
152
152
|
except Exception as e:
|
@@ -154,7 +154,7 @@ def log(packet, tx: Optional[bool] = False, header: Optional[bool] = True) -> No
|
|
154
154
|
bearing = 0
|
155
155
|
logit.append(
|
156
156
|
f" : <{DEGREES_COLOR}>{utils.degrees_to_cardinal(bearing, full_string=True)}</{DEGREES_COLOR}>"
|
157
|
-
f"<{DISTANCE_COLOR}>@{
|
157
|
+
f"<{DISTANCE_COLOR}>@{haversine(my_coords, packet_coords, unit=Unit.MILES):.2f}miles</{DISTANCE_COLOR}>",
|
158
158
|
)
|
159
159
|
|
160
160
|
LOGU.opt(colors=True).info(" ".join(logit))
|
aprsd/packets/packet_list.py
CHANGED
@@ -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
|
@@ -38,7 +38,8 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
38
38
|
self._add(packet)
|
39
39
|
ptype = packet.__class__.__name__
|
40
40
|
type_stats = self.data["types"].setdefault(
|
41
|
-
ptype,
|
41
|
+
ptype,
|
42
|
+
{"tx": 0, "rx": 0},
|
42
43
|
)
|
43
44
|
type_stats["rx"] += 1
|
44
45
|
|
@@ -49,7 +50,8 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
49
50
|
self._add(packet)
|
50
51
|
ptype = packet.__class__.__name__
|
51
52
|
type_stats = self.data["types"].setdefault(
|
52
|
-
ptype,
|
53
|
+
ptype,
|
54
|
+
{"tx": 0, "rx": 0},
|
53
55
|
)
|
54
56
|
type_stats["tx"] += 1
|
55
57
|
|
@@ -86,10 +88,11 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
86
88
|
with self.lock:
|
87
89
|
# Get last N packets directly using list slicing
|
88
90
|
packets_list = list(self.data.get("packets", {}).values())
|
89
|
-
pkts = packets_list[-CONF.packet_list_stats_maxlen:][::-1]
|
91
|
+
pkts = packets_list[-CONF.packet_list_stats_maxlen :][::-1]
|
90
92
|
|
91
93
|
stats = {
|
92
|
-
"total_tracked": self._total_rx
|
94
|
+
"total_tracked": self._total_rx
|
95
|
+
+ self._total_tx, # Fixed typo: was rx + rx
|
93
96
|
"rx": self._total_rx,
|
94
97
|
"tx": self._total_tx,
|
95
98
|
"types": self.data.get("types", {}), # Changed default from [] to {}
|
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__,
|
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(
|
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__,
|
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
|
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(
|
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(
|
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(
|
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)
|
@@ -473,7 +483,8 @@ class PluginManager:
|
|
473
483
|
self.setup_plugins(load_help_plugin=CONF.load_help_plugin)
|
474
484
|
|
475
485
|
def setup_plugins(
|
476
|
-
self,
|
486
|
+
self,
|
487
|
+
load_help_plugin=True,
|
477
488
|
plugin_list=[],
|
478
489
|
):
|
479
490
|
"""Create the plugin manager and register plugins."""
|
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
|
-
|
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
|
-
|
332
|
-
|
333
|
-
"weather from <callsign>".format(
|
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/collector.py
CHANGED
@@ -3,13 +3,13 @@ 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."""
|
12
|
+
|
13
13
|
def stats(self, serializable=False) -> dict:
|
14
14
|
"""provide stats in a dictionary format."""
|
15
15
|
...
|
@@ -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(
|
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
|
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,
|
7
|
+
APRSDDupeRXThread,
|
8
|
+
APRSDProcessPacketThread,
|
9
|
+
APRSDRXThread,
|
8
10
|
)
|
9
11
|
|
10
|
-
|
11
12
|
packet_queue = queue.Queue(maxsize=20)
|
aprsd/threads/aprsd.py
CHANGED
@@ -7,7 +7,6 @@ from typing import List
|
|
7
7
|
|
8
8
|
import wrapt
|
9
9
|
|
10
|
-
|
11
10
|
LOG = logging.getLogger("APRSD")
|
12
11
|
|
13
12
|
|
@@ -25,7 +24,7 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
|
25
24
|
self._last_loop = datetime.datetime.now()
|
26
25
|
|
27
26
|
def _should_quit(self):
|
28
|
-
"""
|
27
|
+
"""see if we have a quit message from the global queue."""
|
29
28
|
if self.thread_stop:
|
30
29
|
return True
|
31
30
|
|
@@ -51,7 +50,9 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
|
51
50
|
"""Add code to subclass to do any cleanup"""
|
52
51
|
|
53
52
|
def __str__(self):
|
54
|
-
out =
|
53
|
+
out = (
|
54
|
+
f"Thread <{self.__class__.__name__}({self.name}) Alive? {self.is_alive()}>"
|
55
|
+
)
|
55
56
|
return out
|
56
57
|
|
57
58
|
def loop_age(self):
|
@@ -124,7 +125,7 @@ class APRSDThreadList:
|
|
124
125
|
for th in self.threads_list:
|
125
126
|
LOG.info(f"Stopping Thread {th.name}")
|
126
127
|
if hasattr(th, "packet"):
|
127
|
-
LOG.info(
|
128
|
+
LOG.info(f"{th.name} packet {th.packet}")
|
128
129
|
th.stop()
|
129
130
|
|
130
131
|
@wrapt.synchronized
|
@@ -133,7 +134,7 @@ class APRSDThreadList:
|
|
133
134
|
for th in self.threads_list:
|
134
135
|
LOG.info(f"Pausing Thread {th.name}")
|
135
136
|
if hasattr(th, "packet"):
|
136
|
-
LOG.info(
|
137
|
+
LOG.info(f"{th.name} packet {th.packet}")
|
137
138
|
th.pause()
|
138
139
|
|
139
140
|
@wrapt.synchronized
|
@@ -142,7 +143,7 @@ class APRSDThreadList:
|
|
142
143
|
for th in self.threads_list:
|
143
144
|
LOG.info(f"Resuming Thread {th.name}")
|
144
145
|
if hasattr(th, "packet"):
|
145
|
-
LOG.info(
|
146
|
+
LOG.info(f"{th.name} packet {th.packet}")
|
146
147
|
th.unpause()
|
147
148
|
|
148
149
|
@wrapt.synchronized(lock)
|
@@ -153,7 +154,11 @@ class APRSDThreadList:
|
|
153
154
|
alive = thread.is_alive()
|
154
155
|
age = thread.loop_age()
|
155
156
|
key = thread.__class__.__name__
|
156
|
-
info[key] = {
|
157
|
+
info[key] = {
|
158
|
+
"alive": True if alive else False,
|
159
|
+
"age": age,
|
160
|
+
"name": thread.name,
|
161
|
+
}
|
157
162
|
return info
|
158
163
|
|
159
164
|
@wrapt.synchronized(lock)
|