aprsd 4.1.2__py3-none-any.whl → 4.2.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/client/__init__.py +5 -13
- aprsd/client/client.py +156 -0
- aprsd/client/drivers/__init__.py +10 -0
- aprsd/client/drivers/aprsis.py +174 -255
- aprsd/client/drivers/fake.py +59 -11
- aprsd/client/drivers/lib/__init__.py +0 -0
- aprsd/client/drivers/lib/aprslib.py +296 -0
- aprsd/client/drivers/registry.py +86 -0
- aprsd/client/drivers/tcpkiss.py +423 -0
- aprsd/client/stats.py +2 -2
- aprsd/cmds/dev.py +6 -4
- aprsd/cmds/fetch_stats.py +2 -0
- aprsd/cmds/list_plugins.py +6 -133
- aprsd/cmds/listen.py +5 -3
- aprsd/cmds/send_message.py +8 -5
- aprsd/cmds/server.py +7 -11
- aprsd/conf/common.py +7 -1
- aprsd/exception.py +7 -0
- aprsd/log/log.py +1 -1
- aprsd/main.py +0 -7
- aprsd/packets/core.py +168 -169
- aprsd/packets/log.py +69 -59
- aprsd/plugin.py +3 -2
- aprsd/plugin_utils.py +2 -2
- aprsd/plugins/weather.py +2 -2
- aprsd/stats/collector.py +5 -4
- aprsd/threads/rx.py +13 -11
- aprsd/threads/tx.py +32 -31
- aprsd/utils/keepalive_collector.py +7 -5
- aprsd/utils/package.py +176 -0
- {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/METADATA +48 -48
- {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/RECORD +37 -37
- {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/WHEEL +1 -1
- aprsd/client/aprsis.py +0 -183
- aprsd/client/base.py +0 -156
- aprsd/client/drivers/kiss.py +0 -144
- aprsd/client/factory.py +0 -91
- aprsd/client/fake.py +0 -49
- aprsd/client/kiss.py +0 -143
- {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/entry_points.txt +0 -0
- {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info/licenses}/AUTHORS +0 -0
- {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info/licenses}/LICENSE +0 -0
- {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/top_level.txt +0 -0
aprsd/client/__init__.py
CHANGED
@@ -1,13 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
TRANSPORT_SERIALKISS = "serialkiss"
|
7
|
-
TRANSPORT_FAKE = "fake"
|
8
|
-
|
9
|
-
|
10
|
-
client_factory = factory.ClientFactory()
|
11
|
-
client_factory.register(aprsis.APRSISClient)
|
12
|
-
client_factory.register(kiss.KISSClient)
|
13
|
-
client_factory.register(fake.APRSDFakeClient)
|
1
|
+
# define the client transports here
|
2
|
+
TRANSPORT_APRSIS = 'aprsis'
|
3
|
+
TRANSPORT_TCPKISS = 'tcpkiss'
|
4
|
+
TRANSPORT_SERIALKISS = 'serialkiss'
|
5
|
+
TRANSPORT_FAKE = 'fake'
|
aprsd/client/client.py
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
import logging
|
2
|
+
import threading
|
3
|
+
from typing import Callable
|
4
|
+
|
5
|
+
import timeago
|
6
|
+
import wrapt
|
7
|
+
from loguru import logger
|
8
|
+
from oslo_config import cfg
|
9
|
+
|
10
|
+
from aprsd.client import drivers # noqa - ensure drivers are registered
|
11
|
+
from aprsd.client.drivers.registry import DriverRegistry
|
12
|
+
from aprsd.packets import core
|
13
|
+
from aprsd.utils import keepalive_collector
|
14
|
+
|
15
|
+
CONF = cfg.CONF
|
16
|
+
LOG = logging.getLogger('APRSD')
|
17
|
+
LOGU = logger
|
18
|
+
|
19
|
+
|
20
|
+
class APRSDClient:
|
21
|
+
"""APRSD client class.
|
22
|
+
|
23
|
+
This is a singleton class that provides a single instance of the APRSD client.
|
24
|
+
It is responsible for connecting to the appropriate APRSD client driver based on
|
25
|
+
the configuration.
|
26
|
+
|
27
|
+
"""
|
28
|
+
|
29
|
+
_instance = None
|
30
|
+
driver = None
|
31
|
+
lock = threading.Lock()
|
32
|
+
filter = None
|
33
|
+
|
34
|
+
def __new__(cls, *args, **kwargs):
|
35
|
+
"""This magic turns this into a singleton."""
|
36
|
+
if cls._instance is None:
|
37
|
+
cls._instance = super().__new__(cls)
|
38
|
+
keepalive_collector.KeepAliveCollector().register(cls)
|
39
|
+
return cls._instance
|
40
|
+
|
41
|
+
def __init__(self, auto_connect: bool = True):
|
42
|
+
self.auto_connect = auto_connect
|
43
|
+
self.connected = False
|
44
|
+
self.login_status = {
|
45
|
+
'success': False,
|
46
|
+
'message': None,
|
47
|
+
}
|
48
|
+
self.driver = DriverRegistry().get_driver()
|
49
|
+
if self.auto_connect:
|
50
|
+
self.connect()
|
51
|
+
|
52
|
+
def stats(self, serializable=False) -> dict:
|
53
|
+
stats = {}
|
54
|
+
if self.driver:
|
55
|
+
stats = self.driver.stats(serializable=serializable)
|
56
|
+
return stats
|
57
|
+
|
58
|
+
@staticmethod
|
59
|
+
def is_enabled():
|
60
|
+
for driver in DriverRegistry().drivers:
|
61
|
+
if driver.is_enabled():
|
62
|
+
return True
|
63
|
+
return False
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def is_configured():
|
67
|
+
"""Check if ANY driver is configured."""
|
68
|
+
for driver in DriverRegistry().drivers:
|
69
|
+
if driver.is_configured():
|
70
|
+
return True
|
71
|
+
return False
|
72
|
+
|
73
|
+
# @property
|
74
|
+
# def is_connected(self):
|
75
|
+
# if not self.driver:
|
76
|
+
# return False
|
77
|
+
# return self.driver.is_connected()
|
78
|
+
|
79
|
+
@property
|
80
|
+
def login_success(self):
|
81
|
+
if not self.driver:
|
82
|
+
return False
|
83
|
+
return self.driver.login_success
|
84
|
+
|
85
|
+
@property
|
86
|
+
def login_failure(self):
|
87
|
+
if not self.driver:
|
88
|
+
return None
|
89
|
+
return self.driver.login_failure
|
90
|
+
|
91
|
+
def set_filter(self, filter):
|
92
|
+
self.filter = filter
|
93
|
+
if not self.driver:
|
94
|
+
return
|
95
|
+
self.driver.set_filter(filter)
|
96
|
+
|
97
|
+
def get_filter(self):
|
98
|
+
if not self.driver:
|
99
|
+
return None
|
100
|
+
return self.driver.filter
|
101
|
+
|
102
|
+
def is_alive(self):
|
103
|
+
return self.driver.is_alive()
|
104
|
+
|
105
|
+
def connect(self):
|
106
|
+
if not self.driver:
|
107
|
+
self.driver = DriverRegistry().get_driver()
|
108
|
+
self.driver.setup_connection()
|
109
|
+
|
110
|
+
def close(self):
|
111
|
+
if not self.driver:
|
112
|
+
return
|
113
|
+
self.driver.close()
|
114
|
+
|
115
|
+
@wrapt.synchronized(lock)
|
116
|
+
def reset(self):
|
117
|
+
"""Call this to force a rebuild/reconnect."""
|
118
|
+
LOG.info('Resetting client connection.')
|
119
|
+
if self.driver:
|
120
|
+
self.driver.close()
|
121
|
+
if not self.delay_connect:
|
122
|
+
self.driver.setup_connection()
|
123
|
+
if self.filter:
|
124
|
+
self.driver.set_filter(self.filter)
|
125
|
+
else:
|
126
|
+
LOG.warning('Client not initialized, nothing to reset.')
|
127
|
+
|
128
|
+
def send(self, packet: core.Packet) -> bool:
|
129
|
+
return self.driver.send(packet)
|
130
|
+
|
131
|
+
# For the keepalive collector
|
132
|
+
def keepalive_check(self):
|
133
|
+
# Don't check the first time through.
|
134
|
+
if not self.driver.is_alive and self._checks:
|
135
|
+
LOG.warning("Resetting client. It's not alive.")
|
136
|
+
self.reset()
|
137
|
+
self._checks = True
|
138
|
+
|
139
|
+
# For the keepalive collector
|
140
|
+
def keepalive_log(self):
|
141
|
+
if ka := self.driver.keepalive:
|
142
|
+
keepalive = timeago.format(ka)
|
143
|
+
else:
|
144
|
+
keepalive = 'N/A'
|
145
|
+
LOGU.opt(colors=True).info(f'<green>Client keepalive {keepalive}</green>')
|
146
|
+
|
147
|
+
def consumer(self, callback: Callable, raw: bool = False):
|
148
|
+
return self.driver.consumer(callback=callback, raw=raw)
|
149
|
+
|
150
|
+
def decode_packet(self, *args, **kwargs) -> core.Packet:
|
151
|
+
try:
|
152
|
+
packet = self.driver.decode_packet(*args, **kwargs)
|
153
|
+
except Exception as e:
|
154
|
+
LOG.error(f'Error decoding packet: {e}')
|
155
|
+
return None
|
156
|
+
return packet
|
aprsd/client/drivers/__init__.py
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
# All client drivers must be registered here
|
2
|
+
from aprsd.client.drivers.aprsis import APRSISDriver
|
3
|
+
from aprsd.client.drivers.fake import APRSDFakeDriver
|
4
|
+
from aprsd.client.drivers.registry import DriverRegistry
|
5
|
+
from aprsd.client.drivers.tcpkiss import TCPKISSDriver
|
6
|
+
|
7
|
+
driver_registry = DriverRegistry()
|
8
|
+
driver_registry.register(APRSDFakeDriver)
|
9
|
+
driver_registry.register(APRSISDriver)
|
10
|
+
driver_registry.register(TCPKISSDriver)
|
aprsd/client/drivers/aprsis.py
CHANGED
@@ -1,286 +1,205 @@
|
|
1
1
|
import datetime
|
2
2
|
import logging
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import threading
|
3
|
+
import time
|
4
|
+
from typing import Callable
|
6
5
|
|
7
|
-
import
|
8
|
-
import
|
9
|
-
from
|
10
|
-
from aprslib.exceptions import (
|
11
|
-
ConnectionDrop,
|
12
|
-
ConnectionError,
|
13
|
-
GenericError,
|
14
|
-
LoginError,
|
15
|
-
ParseError,
|
16
|
-
UnknownFormat,
|
17
|
-
)
|
6
|
+
from aprslib.exceptions import LoginError
|
7
|
+
from loguru import logger
|
8
|
+
from oslo_config import cfg
|
18
9
|
|
19
|
-
import
|
10
|
+
from aprsd import client, exception
|
11
|
+
from aprsd.client.drivers.lib.aprslib import APRSLibClient
|
20
12
|
from aprsd.packets import core
|
21
13
|
|
14
|
+
CONF = cfg.CONF
|
22
15
|
LOG = logging.getLogger('APRSD')
|
16
|
+
LOGU = logger
|
23
17
|
|
24
18
|
|
25
|
-
class
|
26
|
-
|
19
|
+
# class APRSISDriver(metaclass=trace.TraceWrapperMetaclass):
|
20
|
+
class APRSISDriver:
|
21
|
+
"""This is the APRS-IS driver for the APRSD client.
|
27
22
|
|
28
|
-
|
29
|
-
thread_stop = False
|
23
|
+
This driver uses our modified aprslib.IS class to connect to the APRS-IS server.
|
30
24
|
|
31
|
-
|
32
|
-
aprsd_keepalive = datetime.datetime.now()
|
25
|
+
"""
|
33
26
|
|
34
|
-
|
35
|
-
|
27
|
+
_client = None
|
28
|
+
_checks = False
|
29
|
+
connected = False
|
36
30
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
def close(self):
|
46
|
-
LOG.warning('Closing Aprsdis client.')
|
47
|
-
super().close()
|
48
|
-
|
49
|
-
@wrapt.synchronized(lock)
|
50
|
-
def send(self, packet: core.Packet):
|
51
|
-
"""Send an APRS Message object."""
|
52
|
-
self.sendall(packet.raw)
|
53
|
-
|
54
|
-
def is_alive(self):
|
55
|
-
"""If the connection is alive or not."""
|
56
|
-
return self._connected
|
57
|
-
|
58
|
-
def _connect(self):
|
59
|
-
"""
|
60
|
-
Attemps connection to the server
|
61
|
-
"""
|
62
|
-
|
63
|
-
self.logger.info(
|
64
|
-
'Attempting connection to %s:%s', self.server[0], self.server[1]
|
65
|
-
)
|
31
|
+
def __init__(self):
|
32
|
+
max_timeout = {'hours': 0.0, 'minutes': 2, 'seconds': 0}
|
33
|
+
self.max_delta = datetime.timedelta(**max_timeout)
|
34
|
+
self.login_status = {
|
35
|
+
'success': False,
|
36
|
+
'message': None,
|
37
|
+
}
|
66
38
|
|
39
|
+
@staticmethod
|
40
|
+
def is_enabled():
|
41
|
+
# Defaults to True if the enabled flag is non existent
|
67
42
|
try:
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
if
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
else:
|
92
|
-
raise ConnectionError('invalid banner from server')
|
93
|
-
|
94
|
-
except ConnectionError as e:
|
95
|
-
self.logger.error(str(e))
|
96
|
-
self.close()
|
97
|
-
raise
|
98
|
-
except (socket.error, socket.timeout) as e:
|
99
|
-
self.close()
|
100
|
-
|
101
|
-
self.logger.error('Socket error: %s' % str(e))
|
102
|
-
if str(e) == 'timed out':
|
103
|
-
raise ConnectionError('no banner from server') from e
|
104
|
-
else:
|
105
|
-
raise ConnectionError(e) from e
|
106
|
-
|
107
|
-
self._connected = True
|
108
|
-
|
109
|
-
def _socket_readlines(self, blocking=False):
|
110
|
-
"""
|
111
|
-
Generator for complete lines, received from the server
|
112
|
-
"""
|
113
|
-
try:
|
114
|
-
self.sock.setblocking(0)
|
115
|
-
except OSError as e:
|
116
|
-
self.logger.error(f'socket error when setblocking(0): {str(e)}')
|
117
|
-
raise aprslib.ConnectionDrop('connection dropped') from e
|
43
|
+
return CONF.aprs_network.enabled
|
44
|
+
except KeyError:
|
45
|
+
return False
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def is_configured():
|
49
|
+
if APRSISDriver.is_enabled():
|
50
|
+
# Ensure that the config vars are correctly set
|
51
|
+
if not CONF.aprs_network.login:
|
52
|
+
LOG.error('Config aprs_network.login not set.')
|
53
|
+
raise exception.MissingConfigOptionException(
|
54
|
+
'aprs_network.login is not set.',
|
55
|
+
)
|
56
|
+
if not CONF.aprs_network.password:
|
57
|
+
LOG.error('Config aprs_network.password not set.')
|
58
|
+
raise exception.MissingConfigOptionException(
|
59
|
+
'aprs_network.password is not set.',
|
60
|
+
)
|
61
|
+
if not CONF.aprs_network.host:
|
62
|
+
LOG.error('Config aprs_network.host not set.')
|
63
|
+
raise exception.MissingConfigOptionException(
|
64
|
+
'aprs_network.host is not set.',
|
65
|
+
)
|
118
66
|
|
119
|
-
|
120
|
-
|
121
|
-
newline = b'\r\n'
|
67
|
+
return True
|
68
|
+
return True
|
122
69
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
self.select_timeout,
|
130
|
-
)
|
131
|
-
if not readable:
|
132
|
-
if not blocking:
|
133
|
-
break
|
134
|
-
else:
|
135
|
-
continue
|
70
|
+
@property
|
71
|
+
def is_alive(self):
|
72
|
+
if not self._client:
|
73
|
+
LOG.warning(f'APRS_CLIENT {self._client} alive? NO!!!')
|
74
|
+
return False
|
75
|
+
return self._client.is_alive() and not self._is_stale_connection()
|
136
76
|
|
77
|
+
def close(self):
|
78
|
+
if self._client:
|
79
|
+
self._client.stop()
|
80
|
+
self._client.close()
|
81
|
+
|
82
|
+
def send(self, packet: core.Packet) -> bool:
|
83
|
+
return self._client.send(packet)
|
84
|
+
|
85
|
+
def setup_connection(self):
|
86
|
+
user = CONF.aprs_network.login
|
87
|
+
password = CONF.aprs_network.password
|
88
|
+
host = CONF.aprs_network.host
|
89
|
+
port = CONF.aprs_network.port
|
90
|
+
self.connected = False
|
91
|
+
backoff = 1
|
92
|
+
retries = 3
|
93
|
+
retry_count = 0
|
94
|
+
while not self.connected:
|
95
|
+
retry_count += 1
|
96
|
+
if retry_count >= retries:
|
97
|
+
break
|
137
98
|
try:
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
self.passwd,
|
170
|
-
(' filter ' + self.filter) if self.filter != '' else '',
|
171
|
-
aprsd.__version__,
|
172
|
-
)
|
173
|
-
|
174
|
-
self.logger.debug('Sending login information')
|
175
|
-
|
176
|
-
try:
|
177
|
-
self._sendall(login_str)
|
178
|
-
self.sock.settimeout(5)
|
179
|
-
test = self.sock.recv(len(login_str) + 100)
|
180
|
-
if is_py3:
|
181
|
-
test = test.decode('latin-1')
|
182
|
-
test = test.rstrip()
|
183
|
-
|
184
|
-
self.logger.debug("Server: '%s'", test)
|
185
|
-
|
186
|
-
if not test:
|
187
|
-
raise LoginError(f"Server Response Empty: '{test}'")
|
188
|
-
|
189
|
-
_, _, callsign, status, e = test.split(' ', 4)
|
190
|
-
s = e.split(',')
|
191
|
-
if len(s):
|
192
|
-
server_string = s[0].replace('server ', '')
|
193
|
-
else:
|
194
|
-
server_string = e.replace('server ', '')
|
195
|
-
|
196
|
-
if callsign == '':
|
197
|
-
raise LoginError('Server responded with empty callsign???')
|
198
|
-
if callsign != self.callsign:
|
199
|
-
raise LoginError(f'Server: {test}')
|
200
|
-
if status != 'verified,' and self.passwd != '-1':
|
201
|
-
raise LoginError('Password is incorrect')
|
99
|
+
LOG.info(
|
100
|
+
f'Creating aprslib client({host}:{port}) and logging in {user}.'
|
101
|
+
)
|
102
|
+
self._client = APRSLibClient(
|
103
|
+
user, passwd=password, host=host, port=port
|
104
|
+
)
|
105
|
+
# Force the log to be the same
|
106
|
+
self._client.logger = LOG
|
107
|
+
self._client.connect()
|
108
|
+
self.connected = self.login_status['success'] = True
|
109
|
+
self.login_status['message'] = self._client.server_string
|
110
|
+
backoff = 1
|
111
|
+
except LoginError as e:
|
112
|
+
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
|
113
|
+
self.connected = self.login_status['success'] = False
|
114
|
+
self.login_status['message'] = (
|
115
|
+
e.message if hasattr(e, 'message') else str(e)
|
116
|
+
)
|
117
|
+
LOG.error(self.login_status['message'])
|
118
|
+
time.sleep(backoff)
|
119
|
+
except Exception as e:
|
120
|
+
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
|
121
|
+
self.connected = self.login_status['success'] = False
|
122
|
+
self.login_status['message'] = getattr(e, 'message', str(e))
|
123
|
+
time.sleep(backoff)
|
124
|
+
# Don't allow the backoff to go to inifinity.
|
125
|
+
if backoff > 5:
|
126
|
+
backoff = 5
|
127
|
+
else:
|
128
|
+
backoff += 1
|
129
|
+
continue
|
202
130
|
|
203
|
-
|
204
|
-
|
205
|
-
else:
|
206
|
-
self.logger.info('Login successful')
|
131
|
+
def set_filter(self, filter):
|
132
|
+
self._client.set_filter(filter)
|
207
133
|
|
208
|
-
|
209
|
-
|
134
|
+
def login_success(self) -> bool:
|
135
|
+
return self.login_status.get('success', False)
|
210
136
|
|
211
|
-
|
212
|
-
|
213
|
-
self.close()
|
214
|
-
raise
|
215
|
-
except Exception as e:
|
216
|
-
self.close()
|
217
|
-
self.logger.error(f"Failed to login '{e}'")
|
218
|
-
self.logger.exception(e)
|
219
|
-
raise LoginError('Failed to login') from e
|
137
|
+
def login_failure(self) -> str:
|
138
|
+
return self.login_status.get('message', None)
|
220
139
|
|
221
|
-
|
222
|
-
|
223
|
-
|
140
|
+
@property
|
141
|
+
def filter(self):
|
142
|
+
return self._client.filter
|
224
143
|
|
225
|
-
|
226
|
-
|
144
|
+
@property
|
145
|
+
def server_string(self):
|
146
|
+
return self._client.server_string
|
227
147
|
|
228
|
-
|
229
|
-
|
148
|
+
@property
|
149
|
+
def keepalive(self):
|
150
|
+
return self._client.aprsd_keepalive
|
230
151
|
|
231
|
-
|
232
|
-
|
152
|
+
def _is_stale_connection(self):
|
153
|
+
delta = datetime.datetime.now() - self._client.aprsd_keepalive
|
154
|
+
if delta > self.max_delta:
|
155
|
+
LOG.error(f'Connection is stale, last heard {delta} ago.')
|
156
|
+
return True
|
157
|
+
return False
|
233
158
|
|
234
|
-
|
235
|
-
|
159
|
+
@staticmethod
|
160
|
+
def transport():
|
161
|
+
return client.TRANSPORT_APRSIS
|
236
162
|
|
237
|
-
|
163
|
+
def decode_packet(self, *args, **kwargs):
|
164
|
+
"""APRS lib already decodes this."""
|
165
|
+
return core.factory(args[0])
|
238
166
|
|
239
|
-
|
167
|
+
def consumer(self, callback: Callable, raw: bool = False):
|
168
|
+
if self._client and self.connected:
|
240
169
|
try:
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
else:
|
247
|
-
callback(self._parse(line))
|
248
|
-
else:
|
249
|
-
self.logger.debug('Server: %s', line.decode('utf8'))
|
250
|
-
self.aprsd_keepalive = datetime.datetime.now()
|
251
|
-
except ParseError as exp:
|
252
|
-
self.logger.log(
|
253
|
-
11,
|
254
|
-
"%s Packet: '%s'",
|
255
|
-
exp,
|
256
|
-
exp.packet,
|
257
|
-
)
|
258
|
-
except UnknownFormat as exp:
|
259
|
-
self.logger.log(
|
260
|
-
9,
|
261
|
-
"%s Packet: '%s'",
|
262
|
-
exp,
|
263
|
-
exp.packet,
|
170
|
+
self._client.consumer(
|
171
|
+
callback,
|
172
|
+
blocking=False,
|
173
|
+
immortal=False,
|
174
|
+
raw=raw,
|
264
175
|
)
|
265
|
-
except
|
266
|
-
|
267
|
-
|
268
|
-
raise
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
176
|
+
except Exception as e:
|
177
|
+
LOG.error(e)
|
178
|
+
LOG.info(e.__cause__)
|
179
|
+
raise e
|
180
|
+
else:
|
181
|
+
self.connected = False
|
182
|
+
|
183
|
+
def stats(self, serializable: bool = False) -> dict:
|
184
|
+
stats = {}
|
185
|
+
if self.is_configured():
|
186
|
+
if self._client:
|
187
|
+
keepalive = self._client.aprsd_keepalive
|
188
|
+
server_string = self._client.server_string
|
189
|
+
if serializable:
|
190
|
+
keepalive = keepalive.isoformat()
|
191
|
+
filter = self.filter
|
192
|
+
else:
|
193
|
+
keepalive = 'None'
|
194
|
+
server_string = 'None'
|
195
|
+
filter = 'None'
|
196
|
+
stats = {
|
197
|
+
'connected': self.is_alive,
|
198
|
+
'filter': filter,
|
199
|
+
'login_status': self.login_status,
|
200
|
+
'connection_keepalive': keepalive,
|
201
|
+
'server_string': server_string,
|
202
|
+
'transport': self.transport(),
|
203
|
+
}
|
204
|
+
|
205
|
+
return stats
|