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.
- aprsd/cli_helper.py +12 -5
- aprsd/client/aprsis.py +68 -17
- aprsd/client/base.py +60 -12
- aprsd/client/drivers/aprsis.py +11 -5
- aprsd/client/drivers/fake.py +15 -20
- aprsd/client/factory.py +6 -3
- aprsd/client/fake.py +5 -4
- aprsd/client/kiss.py +43 -7
- aprsd/client/stats.py +2 -22
- aprsd/cmds/completion.py +7 -4
- aprsd/cmds/dev.py +39 -43
- aprsd/cmds/fetch_stats.py +221 -69
- aprsd/cmds/healthcheck.py +7 -5
- aprsd/cmds/list_plugins.py +140 -134
- aprsd/cmds/listen.py +102 -11
- aprsd/cmds/server.py +71 -37
- aprsd/conf/__init__.py +1 -2
- aprsd/conf/client.py +3 -4
- aprsd/conf/common.py +29 -92
- 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 +19 -9
- aprsd/packets/__init__.py +14 -4
- aprsd/packets/core.py +82 -91
- aprsd/packets/log.py +8 -8
- aprsd/packets/packet_list.py +18 -25
- aprsd/plugin.py +33 -15
- aprsd/plugins/fortune.py +2 -2
- aprsd/plugins/notify.py +1 -4
- aprsd/plugins/weather.py +10 -8
- aprsd/stats/__init__.py +0 -2
- aprsd/stats/collector.py +11 -3
- aprsd/threads/__init__.py +3 -2
- aprsd/threads/aprsd.py +57 -10
- aprsd/threads/{keep_alive.py → keepalive.py} +14 -37
- aprsd/threads/registry.py +3 -3
- aprsd/threads/rx.py +48 -17
- aprsd/threads/stats.py +2 -2
- aprsd/threads/tx.py +34 -10
- aprsd/utils/__init__.py +62 -15
- aprsd/utils/counter.py +15 -12
- aprsd/utils/json.py +9 -4
- aprsd/utils/keepalive_collector.py +55 -0
- aprsd/utils/trace.py +4 -4
- aprsd-4.0.0.dist-info/AUTHORS +1 -0
- aprsd-4.0.0.dist-info/METADATA +293 -0
- aprsd-4.0.0.dist-info/RECORD +74 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/WHEEL +1 -1
- aprsd/cmds/webchat.py +0 -674
- aprsd/conf/plugin_email.py +0 -105
- aprsd/plugins/email.py +0 -709
- aprsd/plugins/location.py +0 -179
- 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 -585
- 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 -315
- aprsd-3.4.3.dist-info/AUTHORS +0 -13
- aprsd-3.4.3.dist-info/METADATA +0 -793
- aprsd-3.4.3.dist-info/RECORD +0 -133
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/LICENSE +0 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/entry_points.txt +0 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/top_level.txt +0 -0
aprsd/cli_helper.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
from functools import update_wrapper
|
2
1
|
import logging
|
3
|
-
from pathlib import Path
|
4
2
|
import typing as t
|
3
|
+
from functools import update_wrapper
|
4
|
+
from pathlib import Path
|
5
5
|
|
6
6
|
import click
|
7
7
|
from oslo_config import cfg
|
@@ -11,7 +11,6 @@ from aprsd import conf # noqa: F401
|
|
11
11
|
from aprsd.log import log
|
12
12
|
from aprsd.utils import trace
|
13
13
|
|
14
|
-
|
15
14
|
CONF = cfg.CONF
|
16
15
|
home = str(Path.home())
|
17
16
|
DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
|
@@ -58,6 +57,7 @@ class AliasedGroup(click.Group):
|
|
58
57
|
calling into :meth:`add_command`.
|
59
58
|
Copied from `click` and extended for `aliases`.
|
60
59
|
"""
|
60
|
+
|
61
61
|
def decorator(f):
|
62
62
|
aliases = kwargs.pop("aliases", [])
|
63
63
|
cmd = click.decorators.command(*args, **kwargs)(f)
|
@@ -65,6 +65,7 @@ class AliasedGroup(click.Group):
|
|
65
65
|
for alias in aliases:
|
66
66
|
self.add_command(cmd, name=alias)
|
67
67
|
return cmd
|
68
|
+
|
68
69
|
return decorator
|
69
70
|
|
70
71
|
def group(self, *args, **kwargs):
|
@@ -74,6 +75,7 @@ class AliasedGroup(click.Group):
|
|
74
75
|
calling into :meth:`add_command`.
|
75
76
|
Copied from `click` and extended for `aliases`.
|
76
77
|
"""
|
78
|
+
|
77
79
|
def decorator(f):
|
78
80
|
aliases = kwargs.pop("aliases", [])
|
79
81
|
cmd = click.decorators.group(*args, **kwargs)(f)
|
@@ -81,6 +83,7 @@ class AliasedGroup(click.Group):
|
|
81
83
|
for alias in aliases:
|
82
84
|
self.add_command(cmd, name=alias)
|
83
85
|
return cmd
|
86
|
+
|
84
87
|
return decorator
|
85
88
|
|
86
89
|
|
@@ -89,6 +92,7 @@ def add_options(options):
|
|
89
92
|
for option in reversed(options):
|
90
93
|
func = option(func)
|
91
94
|
return func
|
95
|
+
|
92
96
|
return _add_options
|
93
97
|
|
94
98
|
|
@@ -103,7 +107,9 @@ def process_standard_options(f: F) -> F:
|
|
103
107
|
default_config_files = None
|
104
108
|
try:
|
105
109
|
CONF(
|
106
|
-
[],
|
110
|
+
[],
|
111
|
+
project="aprsd",
|
112
|
+
version=aprsd.__version__,
|
107
113
|
default_config_files=default_config_files,
|
108
114
|
)
|
109
115
|
except cfg.ConfigFilesNotFoundError:
|
@@ -119,7 +125,7 @@ def process_standard_options(f: F) -> F:
|
|
119
125
|
trace.setup_tracing(["method", "api"])
|
120
126
|
|
121
127
|
if not config_file_found:
|
122
|
-
LOG = logging.getLogger("APRSD")
|
128
|
+
LOG = logging.getLogger("APRSD") # noqa: N806
|
123
129
|
LOG.error("No config file found!! run 'aprsd sample-config'")
|
124
130
|
|
125
131
|
del kwargs["loglevel"]
|
@@ -132,6 +138,7 @@ def process_standard_options(f: F) -> F:
|
|
132
138
|
|
133
139
|
def process_standard_options_no_config(f: F) -> F:
|
134
140
|
"""Use this as a decorator when config isn't needed."""
|
141
|
+
|
135
142
|
def new_func(*args, **kwargs):
|
136
143
|
ctx = args[0]
|
137
144
|
ctx.ensure_object(dict)
|
aprsd/client/aprsis.py
CHANGED
@@ -2,7 +2,9 @@ import datetime
|
|
2
2
|
import logging
|
3
3
|
import time
|
4
4
|
|
5
|
+
import timeago
|
5
6
|
from aprslib.exceptions import LoginError
|
7
|
+
from loguru import logger
|
6
8
|
from oslo_config import cfg
|
7
9
|
|
8
10
|
from aprsd import client, exception
|
@@ -10,30 +12,55 @@ from aprsd.client import base
|
|
10
12
|
from aprsd.client.drivers import aprsis
|
11
13
|
from aprsd.packets import core
|
12
14
|
|
13
|
-
|
14
15
|
CONF = cfg.CONF
|
15
16
|
LOG = logging.getLogger("APRSD")
|
17
|
+
LOGU = logger
|
16
18
|
|
17
19
|
|
18
20
|
class APRSISClient(base.APRSClient):
|
19
|
-
|
20
21
|
_client = None
|
22
|
+
_checks = False
|
21
23
|
|
22
24
|
def __init__(self):
|
23
25
|
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
|
24
26
|
self.max_delta = datetime.timedelta(**max_timeout)
|
25
27
|
|
26
|
-
def stats(self) -> dict:
|
28
|
+
def stats(self, serializable=False) -> dict:
|
27
29
|
stats = {}
|
28
30
|
if self.is_configured():
|
31
|
+
if self._client:
|
32
|
+
keepalive = self._client.aprsd_keepalive
|
33
|
+
server_string = self._client.server_string
|
34
|
+
if serializable:
|
35
|
+
keepalive = keepalive.isoformat()
|
36
|
+
else:
|
37
|
+
keepalive = "None"
|
38
|
+
server_string = "None"
|
29
39
|
stats = {
|
30
|
-
"
|
31
|
-
"sever_keepalive": self._client.aprsd_keepalive,
|
40
|
+
"connected": self.is_connected,
|
32
41
|
"filter": self.filter,
|
42
|
+
"login_status": self.login_status,
|
43
|
+
"connection_keepalive": keepalive,
|
44
|
+
"server_string": server_string,
|
45
|
+
"transport": self.transport(),
|
33
46
|
}
|
34
47
|
|
35
48
|
return stats
|
36
49
|
|
50
|
+
def keepalive_check(self):
|
51
|
+
# Don't check the first time through.
|
52
|
+
if not self.is_alive() and self._checks:
|
53
|
+
LOG.warning("Resetting client. It's not alive.")
|
54
|
+
self.reset()
|
55
|
+
self._checks = True
|
56
|
+
|
57
|
+
def keepalive_log(self):
|
58
|
+
if ka := self._client.aprsd_keepalive:
|
59
|
+
keepalive = timeago.format(ka)
|
60
|
+
else:
|
61
|
+
keepalive = "N/A"
|
62
|
+
LOGU.opt(colors=True).info(f"<green>Client keepalive {keepalive}</green>")
|
63
|
+
|
37
64
|
@staticmethod
|
38
65
|
def is_enabled():
|
39
66
|
# Defaults to True if the enabled flag is non existent
|
@@ -70,13 +97,13 @@ class APRSISClient(base.APRSClient):
|
|
70
97
|
if delta > self.max_delta:
|
71
98
|
LOG.error(f"Connection is stale, last heard {delta} ago.")
|
72
99
|
return True
|
100
|
+
return False
|
73
101
|
|
74
102
|
def is_alive(self):
|
75
|
-
if self._client:
|
76
|
-
return self._client.is_alive() and not self._is_stale_connection()
|
77
|
-
else:
|
103
|
+
if not self._client:
|
78
104
|
LOG.warning(f"APRS_CLIENT {self._client} alive? NO!!!")
|
79
105
|
return False
|
106
|
+
return self._client.is_alive() and not self._is_stale_connection()
|
80
107
|
|
81
108
|
def close(self):
|
82
109
|
if self._client:
|
@@ -99,22 +126,35 @@ class APRSISClient(base.APRSClient):
|
|
99
126
|
self.connected = False
|
100
127
|
backoff = 1
|
101
128
|
aprs_client = None
|
129
|
+
retries = 3
|
130
|
+
retry_count = 0
|
102
131
|
while not self.connected:
|
132
|
+
retry_count += 1
|
133
|
+
if retry_count >= retries:
|
134
|
+
break
|
103
135
|
try:
|
104
|
-
LOG.info(
|
105
|
-
|
136
|
+
LOG.info(
|
137
|
+
f"Creating aprslib client({host}:{port}) and logging in {user}."
|
138
|
+
)
|
139
|
+
aprs_client = aprsis.Aprsdis(
|
140
|
+
user, passwd=password, host=host, port=port
|
141
|
+
)
|
106
142
|
# Force the log to be the same
|
107
143
|
aprs_client.logger = LOG
|
108
144
|
aprs_client.connect()
|
109
|
-
self.connected = True
|
145
|
+
self.connected = self.login_status["success"] = True
|
146
|
+
self.login_status["message"] = aprs_client.server_string
|
110
147
|
backoff = 1
|
111
148
|
except LoginError as e:
|
112
149
|
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
|
113
|
-
self.connected = False
|
150
|
+
self.connected = self.login_status["success"] = False
|
151
|
+
self.login_status["message"] = e.message
|
152
|
+
LOG.error(e.message)
|
114
153
|
time.sleep(backoff)
|
115
154
|
except Exception as e:
|
116
155
|
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
|
117
|
-
self.connected = False
|
156
|
+
self.connected = self.login_status["success"] = False
|
157
|
+
self.login_status["message"] = e.message
|
118
158
|
time.sleep(backoff)
|
119
159
|
# Don't allow the backoff to go to inifinity.
|
120
160
|
if backoff > 5:
|
@@ -126,7 +166,18 @@ class APRSISClient(base.APRSClient):
|
|
126
166
|
return aprs_client
|
127
167
|
|
128
168
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
129
|
-
self._client
|
130
|
-
|
131
|
-
|
132
|
-
|
169
|
+
if self._client:
|
170
|
+
try:
|
171
|
+
self._client.consumer(
|
172
|
+
callback,
|
173
|
+
blocking=blocking,
|
174
|
+
immortal=immortal,
|
175
|
+
raw=raw,
|
176
|
+
)
|
177
|
+
except Exception as e:
|
178
|
+
LOG.error(e)
|
179
|
+
LOG.info(e.__cause__)
|
180
|
+
raise e
|
181
|
+
else:
|
182
|
+
LOG.warning("client is None, might be resetting.")
|
183
|
+
self.connected = False
|
aprsd/client/base.py
CHANGED
@@ -2,11 +2,11 @@ import abc
|
|
2
2
|
import logging
|
3
3
|
import threading
|
4
4
|
|
5
|
-
from oslo_config import cfg
|
6
5
|
import wrapt
|
6
|
+
from oslo_config import cfg
|
7
7
|
|
8
8
|
from aprsd.packets import core
|
9
|
-
|
9
|
+
from aprsd.utils import keepalive_collector
|
10
10
|
|
11
11
|
CONF = cfg.CONF
|
12
12
|
LOG = logging.getLogger("APRSD")
|
@@ -19,6 +19,10 @@ class APRSClient:
|
|
19
19
|
_client = None
|
20
20
|
|
21
21
|
connected = False
|
22
|
+
login_status = {
|
23
|
+
"success": False,
|
24
|
+
"message": None,
|
25
|
+
}
|
22
26
|
filter = None
|
23
27
|
lock = threading.Lock()
|
24
28
|
|
@@ -26,13 +30,40 @@ class APRSClient:
|
|
26
30
|
"""This magic turns this into a singleton."""
|
27
31
|
if cls._instance is None:
|
28
32
|
cls._instance = super().__new__(cls)
|
33
|
+
keepalive_collector.KeepAliveCollector().register(cls)
|
29
34
|
# Put any initialization here.
|
30
35
|
cls._instance._create_client()
|
31
36
|
return cls._instance
|
32
37
|
|
33
38
|
@abc.abstractmethod
|
34
39
|
def stats(self) -> dict:
|
35
|
-
|
40
|
+
"""Return statistics about the client connection.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
dict: Statistics about the connection and packet handling
|
44
|
+
"""
|
45
|
+
|
46
|
+
@abc.abstractmethod
|
47
|
+
def keepalive_check(self) -> None:
|
48
|
+
"""Called during keepalive run to check status."""
|
49
|
+
...
|
50
|
+
|
51
|
+
@abc.abstractmethod
|
52
|
+
def keepalive_log(self) -> None:
|
53
|
+
"""Log any keepalive information."""
|
54
|
+
...
|
55
|
+
|
56
|
+
@property
|
57
|
+
def is_connected(self):
|
58
|
+
return self.connected
|
59
|
+
|
60
|
+
@property
|
61
|
+
def login_success(self):
|
62
|
+
return self.login_status.get("success", False)
|
63
|
+
|
64
|
+
@property
|
65
|
+
def login_failure(self):
|
66
|
+
return self.login_status["message"]
|
36
67
|
|
37
68
|
def set_filter(self, filter):
|
38
69
|
self.filter = filter
|
@@ -46,22 +77,31 @@ class APRSClient:
|
|
46
77
|
return self._client
|
47
78
|
|
48
79
|
def _create_client(self):
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
80
|
+
try:
|
81
|
+
self._client = self.setup_connection()
|
82
|
+
if self.filter:
|
83
|
+
LOG.info("Creating APRS client filter")
|
84
|
+
self._client.set_filter(self.filter)
|
85
|
+
except Exception as e:
|
86
|
+
LOG.error(f"Failed to create APRS client: {e}")
|
87
|
+
self._client = None
|
88
|
+
raise
|
53
89
|
|
54
90
|
def stop(self):
|
55
91
|
if self._client:
|
56
92
|
LOG.info("Stopping client connection.")
|
57
93
|
self._client.stop()
|
58
94
|
|
59
|
-
def send(self, packet: core.Packet):
|
60
|
-
"""Send a packet to the network.
|
95
|
+
def send(self, packet: core.Packet) -> None:
|
96
|
+
"""Send a packet to the network.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
packet: The APRS packet to send
|
100
|
+
"""
|
61
101
|
self.client.send(packet)
|
62
102
|
|
63
103
|
@wrapt.synchronized(lock)
|
64
|
-
def reset(self):
|
104
|
+
def reset(self) -> None:
|
65
105
|
"""Call this to force a rebuild/reconnect."""
|
66
106
|
LOG.info("Resetting client connection.")
|
67
107
|
if self._client:
|
@@ -76,7 +116,11 @@ class APRSClient:
|
|
76
116
|
|
77
117
|
@abc.abstractmethod
|
78
118
|
def setup_connection(self):
|
79
|
-
|
119
|
+
"""Initialize and return the underlying APRS connection.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
object: The initialized connection object
|
123
|
+
"""
|
80
124
|
|
81
125
|
@staticmethod
|
82
126
|
@abc.abstractmethod
|
@@ -90,7 +134,11 @@ class APRSClient:
|
|
90
134
|
|
91
135
|
@abc.abstractmethod
|
92
136
|
def decode_packet(self, *args, **kwargs):
|
93
|
-
|
137
|
+
"""Decode raw APRS packet data into a Packet object.
|
138
|
+
|
139
|
+
Returns:
|
140
|
+
Packet: Decoded APRS packet
|
141
|
+
"""
|
94
142
|
|
95
143
|
@abc.abstractmethod
|
96
144
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
aprsd/client/drivers/aprsis.py
CHANGED
@@ -4,17 +4,20 @@ import select
|
|
4
4
|
import threading
|
5
5
|
|
6
6
|
import aprslib
|
7
|
+
import wrapt
|
7
8
|
from aprslib import is_py3
|
8
9
|
from aprslib.exceptions import (
|
9
|
-
ConnectionDrop,
|
10
|
+
ConnectionDrop,
|
11
|
+
ConnectionError,
|
12
|
+
GenericError,
|
13
|
+
LoginError,
|
14
|
+
ParseError,
|
10
15
|
UnknownFormat,
|
11
16
|
)
|
12
|
-
import wrapt
|
13
17
|
|
14
18
|
import aprsd
|
15
19
|
from aprsd.packets import core
|
16
20
|
|
17
|
-
|
18
21
|
LOG = logging.getLogger("APRSD")
|
19
22
|
|
20
23
|
|
@@ -27,6 +30,9 @@ class Aprsdis(aprslib.IS):
|
|
27
30
|
# date for last time we heard from the server
|
28
31
|
aprsd_keepalive = datetime.datetime.now()
|
29
32
|
|
33
|
+
# Which server we are connected to?
|
34
|
+
server_string = "None"
|
35
|
+
|
30
36
|
# timeout in seconds
|
31
37
|
select_timeout = 1
|
32
38
|
lock = threading.Lock()
|
@@ -193,14 +199,14 @@ class Aprsdis(aprslib.IS):
|
|
193
199
|
except ParseError as exp:
|
194
200
|
self.logger.log(
|
195
201
|
11,
|
196
|
-
"%s
|
202
|
+
"%s Packet: '%s'",
|
197
203
|
exp,
|
198
204
|
exp.packet,
|
199
205
|
)
|
200
206
|
except UnknownFormat as exp:
|
201
207
|
self.logger.log(
|
202
208
|
9,
|
203
|
-
"%s
|
209
|
+
"%s Packet: '%s'",
|
204
210
|
exp,
|
205
211
|
exp.packet,
|
206
212
|
)
|
aprsd/client/drivers/fake.py
CHANGED
@@ -3,20 +3,19 @@ import threading
|
|
3
3
|
import time
|
4
4
|
|
5
5
|
import aprslib
|
6
|
-
from oslo_config import cfg
|
7
6
|
import wrapt
|
7
|
+
from oslo_config import cfg
|
8
8
|
|
9
9
|
from aprsd import conf # noqa
|
10
10
|
from aprsd.packets import core
|
11
11
|
from aprsd.utils import trace
|
12
12
|
|
13
|
-
|
14
13
|
CONF = cfg.CONF
|
15
|
-
LOG = logging.getLogger(
|
14
|
+
LOG = logging.getLogger('APRSD')
|
16
15
|
|
17
16
|
|
18
17
|
class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
19
|
-
|
18
|
+
"""Fake client for testing."""
|
20
19
|
|
21
20
|
# flag to tell us to stop
|
22
21
|
thread_stop = False
|
@@ -25,12 +24,12 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
|
25
24
|
path = []
|
26
25
|
|
27
26
|
def __init__(self):
|
28
|
-
LOG.info(
|
29
|
-
self.path = [
|
27
|
+
LOG.info('Starting APRSDFakeClient client.')
|
28
|
+
self.path = ['WIDE1-1', 'WIDE2-1']
|
30
29
|
|
31
30
|
def stop(self):
|
32
31
|
self.thread_stop = True
|
33
|
-
LOG.info(
|
32
|
+
LOG.info('Shutdown APRSDFakeClient client.')
|
34
33
|
|
35
34
|
def is_alive(self):
|
36
35
|
"""If the connection is alive or not."""
|
@@ -39,35 +38,31 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
|
39
38
|
@wrapt.synchronized(lock)
|
40
39
|
def send(self, packet: core.Packet):
|
41
40
|
"""Send an APRS Message object."""
|
42
|
-
LOG.info(f
|
41
|
+
LOG.info(f'Sending packet: {packet}')
|
43
42
|
payload = None
|
44
43
|
if isinstance(packet, core.Packet):
|
45
44
|
packet.prepare()
|
46
|
-
payload = packet.payload.encode(
|
47
|
-
if packet.path:
|
48
|
-
packet.path
|
49
|
-
else:
|
50
|
-
self.path
|
45
|
+
payload = packet.payload.encode('US-ASCII')
|
51
46
|
else:
|
52
|
-
msg_payload = f
|
47
|
+
msg_payload = f'{packet.raw}{{{str(packet.msgNo)}'
|
53
48
|
payload = (
|
54
|
-
|
49
|
+
':{:<9}:{}'.format(
|
55
50
|
packet.to_call,
|
56
51
|
msg_payload,
|
57
52
|
)
|
58
|
-
).encode(
|
53
|
+
).encode('US-ASCII')
|
59
54
|
|
60
55
|
LOG.debug(
|
61
56
|
f"FAKE::Send '{payload}' TO '{packet.to_call}' From "
|
62
|
-
f
|
57
|
+
f'\'{packet.from_call}\' with PATH "{self.path}"',
|
63
58
|
)
|
64
59
|
|
65
60
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
66
|
-
LOG.debug(
|
61
|
+
LOG.debug('Start non blocking FAKE consumer')
|
67
62
|
# Generate packets here?
|
68
|
-
raw =
|
63
|
+
raw = 'GTOWN>APDW16,WIDE1-1,WIDE2-1:}KM6LYW-9>APZ100,TCPIP,GTOWN*::KM6LYW :KM6LYW: 19 Miles SW'
|
69
64
|
pkt_raw = aprslib.parse(raw)
|
70
65
|
pkt = core.factory(pkt_raw)
|
71
66
|
callback(packet=pkt)
|
72
|
-
LOG.debug(f
|
67
|
+
LOG.debug(f'END blocking FAKE consumer {self}')
|
73
68
|
time.sleep(8)
|
aprsd/client/factory.py
CHANGED
@@ -4,13 +4,11 @@ from typing import Callable, Protocol, runtime_checkable
|
|
4
4
|
from aprsd import exception
|
5
5
|
from aprsd.packets import core
|
6
6
|
|
7
|
-
|
8
7
|
LOG = logging.getLogger("APRSD")
|
9
8
|
|
10
9
|
|
11
10
|
@runtime_checkable
|
12
11
|
class Client(Protocol):
|
13
|
-
|
14
12
|
def __init__(self):
|
15
13
|
pass
|
16
14
|
|
@@ -42,6 +40,7 @@ class Client(Protocol):
|
|
42
40
|
class ClientFactory:
|
43
41
|
_instance = None
|
44
42
|
clients = []
|
43
|
+
client = None
|
45
44
|
|
46
45
|
def __new__(cls, *args, **kwargs):
|
47
46
|
"""This magic turns this into a singleton."""
|
@@ -62,9 +61,13 @@ class ClientFactory:
|
|
62
61
|
def create(self, key=None):
|
63
62
|
for client in self.clients:
|
64
63
|
if client.is_enabled():
|
65
|
-
|
64
|
+
self.client = client()
|
65
|
+
return self.client
|
66
66
|
raise Exception("No client is configured!!")
|
67
67
|
|
68
|
+
def client_exists(self):
|
69
|
+
return bool(self.client)
|
70
|
+
|
68
71
|
def is_client_enabled(self):
|
69
72
|
"""Make sure at least one client is enabled."""
|
70
73
|
enabled = False
|
aprsd/client/fake.py
CHANGED
@@ -7,15 +7,16 @@ from aprsd.client import base
|
|
7
7
|
from aprsd.client.drivers import fake as fake_driver
|
8
8
|
from aprsd.utils import trace
|
9
9
|
|
10
|
-
|
11
10
|
CONF = cfg.CONF
|
12
11
|
LOG = logging.getLogger("APRSD")
|
13
12
|
|
14
13
|
|
15
14
|
class APRSDFakeClient(base.APRSClient, metaclass=trace.TraceWrapperMetaclass):
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def stats(self, serializable=False) -> dict:
|
16
|
+
return {
|
17
|
+
"transport": "Fake",
|
18
|
+
"connected": True,
|
19
|
+
}
|
19
20
|
|
20
21
|
@staticmethod
|
21
22
|
def is_enabled():
|
aprsd/client/kiss.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
import datetime
|
1
2
|
import logging
|
2
3
|
|
3
4
|
import aprslib
|
5
|
+
import timeago
|
6
|
+
from loguru import logger
|
4
7
|
from oslo_config import cfg
|
5
8
|
|
6
9
|
from aprsd import client, exception
|
@@ -8,21 +11,31 @@ from aprsd.client import base
|
|
8
11
|
from aprsd.client.drivers import kiss
|
9
12
|
from aprsd.packets import core
|
10
13
|
|
11
|
-
|
12
14
|
CONF = cfg.CONF
|
13
15
|
LOG = logging.getLogger("APRSD")
|
16
|
+
LOGU = logger
|
14
17
|
|
15
18
|
|
16
19
|
class KISSClient(base.APRSClient):
|
17
|
-
|
18
20
|
_client = None
|
21
|
+
keepalive = datetime.datetime.now()
|
19
22
|
|
20
|
-
def stats(self) -> dict:
|
23
|
+
def stats(self, serializable=False) -> dict:
|
21
24
|
stats = {}
|
22
25
|
if self.is_configured():
|
23
|
-
|
26
|
+
keepalive = self.keepalive
|
27
|
+
if serializable:
|
28
|
+
keepalive = keepalive.isoformat()
|
29
|
+
stats = {
|
30
|
+
"connected": self.is_connected,
|
31
|
+
"connection_keepalive": keepalive,
|
24
32
|
"transport": self.transport(),
|
25
33
|
}
|
34
|
+
if self.transport() == client.TRANSPORT_TCPKISS:
|
35
|
+
stats["host"] = CONF.kiss_tcp.host
|
36
|
+
stats["port"] = CONF.kiss_tcp.port
|
37
|
+
elif self.transport() == client.TRANSPORT_SERIALKISS:
|
38
|
+
stats["device"] = CONF.kiss_serial.device
|
26
39
|
return stats
|
27
40
|
|
28
41
|
@staticmethod
|
@@ -67,6 +80,20 @@ class KISSClient(base.APRSClient):
|
|
67
80
|
if self._client:
|
68
81
|
self._client.stop()
|
69
82
|
|
83
|
+
def keepalive_check(self):
|
84
|
+
# Don't check the first time through.
|
85
|
+
if not self.is_alive() and self._checks:
|
86
|
+
LOG.warning("Resetting client. It's not alive.")
|
87
|
+
self.reset()
|
88
|
+
self._checks = True
|
89
|
+
|
90
|
+
def keepalive_log(self):
|
91
|
+
if ka := self._client.aprsd_keepalive:
|
92
|
+
keepalive = timeago.format(ka)
|
93
|
+
else:
|
94
|
+
keepalive = "N/A"
|
95
|
+
LOGU.opt(colors=True).info(f"<green>Client keepalive {keepalive}</green>")
|
96
|
+
|
70
97
|
@staticmethod
|
71
98
|
def transport():
|
72
99
|
if CONF.kiss_serial.enabled:
|
@@ -95,9 +122,18 @@ class KISSClient(base.APRSClient):
|
|
95
122
|
return packet
|
96
123
|
|
97
124
|
def setup_connection(self):
|
98
|
-
|
99
|
-
|
125
|
+
try:
|
126
|
+
self._client = kiss.KISS3Client()
|
127
|
+
self.connected = self.login_status["success"] = True
|
128
|
+
except Exception as ex:
|
129
|
+
self.connected = self.login_status["success"] = False
|
130
|
+
self.login_status["message"] = str(ex)
|
100
131
|
return self._client
|
101
132
|
|
102
133
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
103
|
-
|
134
|
+
try:
|
135
|
+
self._client.consumer(callback)
|
136
|
+
self.keepalive = datetime.datetime.now()
|
137
|
+
except Exception as ex:
|
138
|
+
LOG.error(f"Consumer failed {ex}")
|
139
|
+
LOG.error(ex)
|
aprsd/client/stats.py
CHANGED
@@ -1,38 +1,18 @@
|
|
1
1
|
import threading
|
2
2
|
|
3
|
-
from oslo_config import cfg
|
4
3
|
import wrapt
|
4
|
+
from oslo_config import cfg
|
5
5
|
|
6
6
|
from aprsd import client
|
7
7
|
from aprsd.utils import singleton
|
8
8
|
|
9
|
-
|
10
9
|
CONF = cfg.CONF
|
11
10
|
|
12
11
|
|
13
12
|
@singleton
|
14
13
|
class APRSClientStats:
|
15
|
-
|
16
14
|
lock = threading.Lock()
|
17
15
|
|
18
16
|
@wrapt.synchronized(lock)
|
19
17
|
def stats(self, serializable=False):
|
20
|
-
|
21
|
-
stats = {
|
22
|
-
"transport": cl.transport(),
|
23
|
-
"filter": cl.filter,
|
24
|
-
"connected": cl.connected,
|
25
|
-
}
|
26
|
-
|
27
|
-
if cl.transport() == client.TRANSPORT_APRSIS:
|
28
|
-
stats["server_string"] = cl.client.server_string
|
29
|
-
keepalive = cl.client.aprsd_keepalive
|
30
|
-
if serializable:
|
31
|
-
keepalive = keepalive.isoformat()
|
32
|
-
stats["server_keepalive"] = keepalive
|
33
|
-
elif cl.transport() == client.TRANSPORT_TCPKISS:
|
34
|
-
stats["host"] = CONF.kiss_tcp.host
|
35
|
-
stats["port"] = CONF.kiss_tcp.port
|
36
|
-
elif cl.transport() == client.TRANSPORT_SERIALKISS:
|
37
|
-
stats["device"] = CONF.kiss_serial.device
|
38
|
-
return stats
|
18
|
+
return client.client_factory.create().stats(serializable=serializable)
|