aprsd 4.0.2__py3-none-any.whl → 4.1.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 CHANGED
@@ -13,35 +13,35 @@ from aprsd.utils import trace
13
13
 
14
14
  CONF = cfg.CONF
15
15
  home = str(Path.home())
16
- DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
17
- DEFAULT_SAVE_FILE = f"{home}/.config/aprsd/aprsd.p"
18
- DEFAULT_CONFIG_FILE = f"{home}/.config/aprsd/aprsd.conf"
16
+ DEFAULT_CONFIG_DIR = f'{home}/.config/aprsd/'
17
+ DEFAULT_SAVE_FILE = f'{home}/.config/aprsd/aprsd.p'
18
+ DEFAULT_CONFIG_FILE = f'{home}/.config/aprsd/aprsd.conf'
19
19
 
20
20
 
21
- F = t.TypeVar("F", bound=t.Callable[..., t.Any])
21
+ F = t.TypeVar('F', bound=t.Callable[..., t.Any])
22
22
 
23
23
  common_options = [
24
24
  click.option(
25
- "--loglevel",
26
- default="INFO",
25
+ '--loglevel',
26
+ default='INFO',
27
27
  show_default=True,
28
28
  type=click.Choice(
29
- ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
29
+ ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'],
30
30
  case_sensitive=False,
31
31
  ),
32
32
  show_choices=True,
33
- help="The log level to use for aprsd.log",
33
+ help='The log level to use for aprsd.log',
34
34
  ),
35
35
  click.option(
36
- "-c",
37
- "--config",
38
- "config_file",
36
+ '-c',
37
+ '--config',
38
+ 'config_file',
39
39
  show_default=True,
40
40
  default=DEFAULT_CONFIG_FILE,
41
- help="The aprsd config file to use for options.",
41
+ help='The aprsd config file to use for options.',
42
42
  ),
43
43
  click.option(
44
- "--quiet",
44
+ '--quiet',
45
45
  is_flag=True,
46
46
  default=False,
47
47
  help="Don't log to stdout",
@@ -59,7 +59,7 @@ class AliasedGroup(click.Group):
59
59
  """
60
60
 
61
61
  def decorator(f):
62
- aliases = kwargs.pop("aliases", [])
62
+ aliases = kwargs.pop('aliases', [])
63
63
  cmd = click.decorators.command(*args, **kwargs)(f)
64
64
  self.add_command(cmd)
65
65
  for alias in aliases:
@@ -77,7 +77,7 @@ class AliasedGroup(click.Group):
77
77
  """
78
78
 
79
79
  def decorator(f):
80
- aliases = kwargs.pop("aliases", [])
80
+ aliases = kwargs.pop('aliases', [])
81
81
  cmd = click.decorators.group(*args, **kwargs)(f)
82
82
  self.add_command(cmd)
83
83
  for alias in aliases:
@@ -101,36 +101,37 @@ def process_standard_options(f: F) -> F:
101
101
  ctx = args[0]
102
102
  ctx.ensure_object(dict)
103
103
  config_file_found = True
104
- if kwargs["config_file"]:
105
- default_config_files = [kwargs["config_file"]]
104
+ if kwargs['config_file']:
105
+ default_config_files = [kwargs['config_file']]
106
106
  else:
107
107
  default_config_files = None
108
+
108
109
  try:
109
110
  CONF(
110
111
  [],
111
- project="aprsd",
112
+ project='aprsd',
112
113
  version=aprsd.__version__,
113
114
  default_config_files=default_config_files,
114
115
  )
115
116
  except cfg.ConfigFilesNotFoundError:
116
117
  config_file_found = False
117
- ctx.obj["loglevel"] = kwargs["loglevel"]
118
+ ctx.obj['loglevel'] = kwargs['loglevel']
118
119
  # ctx.obj["config_file"] = kwargs["config_file"]
119
- ctx.obj["quiet"] = kwargs["quiet"]
120
+ ctx.obj['quiet'] = kwargs['quiet']
120
121
  log.setup_logging(
121
- ctx.obj["loglevel"],
122
- ctx.obj["quiet"],
122
+ ctx.obj['loglevel'],
123
+ ctx.obj['quiet'],
123
124
  )
124
125
  if CONF.trace_enabled:
125
- trace.setup_tracing(["method", "api"])
126
+ trace.setup_tracing(['method', 'api'])
126
127
 
127
128
  if not config_file_found:
128
- LOG = logging.getLogger("APRSD") # noqa: N806
129
+ LOG = logging.getLogger('APRSD') # noqa: N806
129
130
  LOG.error("No config file found!! run 'aprsd sample-config'")
130
131
 
131
- del kwargs["loglevel"]
132
- del kwargs["config_file"]
133
- del kwargs["quiet"]
132
+ del kwargs['loglevel']
133
+ del kwargs['config_file']
134
+ del kwargs['quiet']
134
135
  return f(*args, **kwargs)
135
136
 
136
137
  return update_wrapper(t.cast(F, new_func), f)
@@ -142,17 +143,17 @@ def process_standard_options_no_config(f: F) -> F:
142
143
  def new_func(*args, **kwargs):
143
144
  ctx = args[0]
144
145
  ctx.ensure_object(dict)
145
- ctx.obj["loglevel"] = kwargs["loglevel"]
146
- ctx.obj["config_file"] = kwargs["config_file"]
147
- ctx.obj["quiet"] = kwargs["quiet"]
146
+ ctx.obj['loglevel'] = kwargs['loglevel']
147
+ ctx.obj['config_file'] = kwargs['config_file']
148
+ ctx.obj['quiet'] = kwargs['quiet']
148
149
  log.setup_logging(
149
- ctx.obj["loglevel"],
150
- ctx.obj["quiet"],
150
+ ctx.obj['loglevel'],
151
+ ctx.obj['quiet'],
151
152
  )
152
153
 
153
- del kwargs["loglevel"]
154
- del kwargs["config_file"]
155
- del kwargs["quiet"]
154
+ del kwargs['loglevel']
155
+ del kwargs['config_file']
156
+ del kwargs['quiet']
156
157
  return f(*args, **kwargs)
157
158
 
158
159
  return update_wrapper(t.cast(F, new_func), f)
aprsd/client/base.py CHANGED
@@ -9,7 +9,7 @@ from aprsd.packets import core
9
9
  from aprsd.utils import keepalive_collector
10
10
 
11
11
  CONF = cfg.CONF
12
- LOG = logging.getLogger("APRSD")
12
+ LOG = logging.getLogger('APRSD')
13
13
 
14
14
 
15
15
  class APRSClient:
@@ -20,8 +20,8 @@ class APRSClient:
20
20
 
21
21
  connected = False
22
22
  login_status = {
23
- "success": False,
24
- "message": None,
23
+ 'success': False,
24
+ 'message': None,
25
25
  }
26
26
  filter = None
27
27
  lock = threading.Lock()
@@ -59,17 +59,20 @@ class APRSClient:
59
59
 
60
60
  @property
61
61
  def login_success(self):
62
- return self.login_status.get("success", False)
62
+ return self.login_status.get('success', False)
63
63
 
64
64
  @property
65
65
  def login_failure(self):
66
- return self.login_status["message"]
66
+ return self.login_status['message']
67
67
 
68
68
  def set_filter(self, filter):
69
69
  self.filter = filter
70
70
  if self._client:
71
71
  self._client.set_filter(filter)
72
72
 
73
+ def get_filter(self):
74
+ return self.filter
75
+
73
76
  @property
74
77
  def client(self):
75
78
  if not self._client:
@@ -80,16 +83,16 @@ class APRSClient:
80
83
  try:
81
84
  self._client = self.setup_connection()
82
85
  if self.filter:
83
- LOG.info("Creating APRS client filter")
86
+ LOG.info('Creating APRS client filter')
84
87
  self._client.set_filter(self.filter)
85
88
  except Exception as e:
86
- LOG.error(f"Failed to create APRS client: {e}")
89
+ LOG.error(f'Failed to create APRS client: {e}')
87
90
  self._client = None
88
91
  raise
89
92
 
90
93
  def stop(self):
91
94
  if self._client:
92
- LOG.info("Stopping client connection.")
95
+ LOG.info('Stopping client connection.')
93
96
  self._client.stop()
94
97
 
95
98
  def send(self, packet: core.Packet) -> None:
@@ -103,16 +106,16 @@ class APRSClient:
103
106
  @wrapt.synchronized(lock)
104
107
  def reset(self) -> None:
105
108
  """Call this to force a rebuild/reconnect."""
106
- LOG.info("Resetting client connection.")
109
+ LOG.info('Resetting client connection.')
107
110
  if self._client:
108
111
  self._client.close()
109
112
  del self._client
110
113
  self._create_client()
111
114
  else:
112
- LOG.warning("Client not initialized, nothing to reset.")
115
+ LOG.warning('Client not initialized, nothing to reset.')
113
116
 
114
117
  # Recreate the client
115
- LOG.info(f"Creating new client {self.client}")
118
+ LOG.info(f'Creating new client {self.client}')
116
119
 
117
120
  @abc.abstractmethod
118
121
  def setup_connection(self):
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  import logging
3
3
  import select
4
+ import socket
4
5
  import threading
5
6
 
6
7
  import aprslib
@@ -18,7 +19,7 @@ from aprslib.exceptions import (
18
19
  import aprsd
19
20
  from aprsd.packets import core
20
21
 
21
- LOG = logging.getLogger("APRSD")
22
+ LOG = logging.getLogger('APRSD')
22
23
 
23
24
 
24
25
  class Aprsdis(aprslib.IS):
@@ -31,7 +32,7 @@ class Aprsdis(aprslib.IS):
31
32
  aprsd_keepalive = datetime.datetime.now()
32
33
 
33
34
  # Which server we are connected to?
34
- server_string = "None"
35
+ server_string = 'None'
35
36
 
36
37
  # timeout in seconds
37
38
  select_timeout = 1
@@ -39,10 +40,10 @@ class Aprsdis(aprslib.IS):
39
40
 
40
41
  def stop(self):
41
42
  self.thread_stop = True
42
- LOG.warning("Shutdown Aprsdis client.")
43
+ LOG.warning('Shutdown Aprsdis client.')
43
44
 
44
45
  def close(self):
45
- LOG.warning("Closing Aprsdis client.")
46
+ LOG.warning('Closing Aprsdis client.')
46
47
  super().close()
47
48
 
48
49
  @wrapt.synchronized(lock)
@@ -54,6 +55,57 @@ class Aprsdis(aprslib.IS):
54
55
  """If the connection is alive or not."""
55
56
  return self._connected
56
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
+ )
66
+
67
+ try:
68
+ self._open_socket()
69
+
70
+ peer = self.sock.getpeername()
71
+
72
+ self.logger.info('Connected to %s', str(peer))
73
+
74
+ # 5 second timeout to receive server banner
75
+ self.sock.setblocking(1)
76
+ self.sock.settimeout(5)
77
+
78
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
79
+ # MACOS doesn't have TCP_KEEPIDLE
80
+ if hasattr(socket, 'TCP_KEEPIDLE'):
81
+ self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
82
+ self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
83
+ self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
84
+
85
+ banner = self.sock.recv(512)
86
+ if is_py3:
87
+ banner = banner.decode('latin-1')
88
+
89
+ if banner[0] == '#':
90
+ self.logger.debug('Banner: %s', banner.rstrip())
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
+
57
109
  def _socket_readlines(self, blocking=False):
58
110
  """
59
111
  Generator for complete lines, received from the server
@@ -61,12 +113,12 @@ class Aprsdis(aprslib.IS):
61
113
  try:
62
114
  self.sock.setblocking(0)
63
115
  except OSError as e:
64
- self.logger.error(f"socket error when setblocking(0): {str(e)}")
65
- raise aprslib.ConnectionDrop("connection dropped")
116
+ self.logger.error(f'socket error when setblocking(0): {str(e)}')
117
+ raise aprslib.ConnectionDrop('connection dropped') from e
66
118
 
67
119
  while not self.thread_stop:
68
- short_buf = b""
69
- newline = b"\r\n"
120
+ short_buf = b''
121
+ newline = b'\r\n'
70
122
 
71
123
  # set a select timeout, so we get a chance to exit
72
124
  # when user hits CTRL-C
@@ -91,11 +143,11 @@ class Aprsdis(aprslib.IS):
91
143
  # We could just not be blocking, so empty is expected
92
144
  continue
93
145
  else:
94
- self.logger.error("socket.recv(): returned empty")
95
- raise aprslib.ConnectionDrop("connection dropped")
146
+ self.logger.error('socket.recv(): returned empty')
147
+ raise aprslib.ConnectionDrop('connection dropped')
96
148
  except OSError as e:
97
149
  # self.logger.error("socket error on recv(): %s" % str(e))
98
- if "Resource temporarily unavailable" in str(e):
150
+ if 'Resource temporarily unavailable' in str(e):
99
151
  if not blocking:
100
152
  if len(self.buf) == 0:
101
153
  break
@@ -111,22 +163,22 @@ class Aprsdis(aprslib.IS):
111
163
  """
112
164
  Sends login string to server
113
165
  """
114
- login_str = "user {0} pass {1} vers github.com/craigerl/aprsd {3}{2}\r\n"
166
+ login_str = 'user {0} pass {1} vers Python-APRSD {3}{2}\r\n'
115
167
  login_str = login_str.format(
116
168
  self.callsign,
117
169
  self.passwd,
118
- (" filter " + self.filter) if self.filter != "" else "",
170
+ (' filter ' + self.filter) if self.filter != '' else '',
119
171
  aprsd.__version__,
120
172
  )
121
173
 
122
- self.logger.debug("Sending login information")
174
+ self.logger.debug('Sending login information')
123
175
 
124
176
  try:
125
177
  self._sendall(login_str)
126
178
  self.sock.settimeout(5)
127
179
  test = self.sock.recv(len(login_str) + 100)
128
180
  if is_py3:
129
- test = test.decode("latin-1")
181
+ test = test.decode('latin-1')
130
182
  test = test.rstrip()
131
183
 
132
184
  self.logger.debug("Server: '%s'", test)
@@ -134,26 +186,26 @@ class Aprsdis(aprslib.IS):
134
186
  if not test:
135
187
  raise LoginError(f"Server Response Empty: '{test}'")
136
188
 
137
- _, _, callsign, status, e = test.split(" ", 4)
138
- s = e.split(",")
189
+ _, _, callsign, status, e = test.split(' ', 4)
190
+ s = e.split(',')
139
191
  if len(s):
140
- server_string = s[0].replace("server ", "")
192
+ server_string = s[0].replace('server ', '')
141
193
  else:
142
- server_string = e.replace("server ", "")
194
+ server_string = e.replace('server ', '')
143
195
 
144
- if callsign == "":
145
- raise LoginError("Server responded with empty callsign???")
196
+ if callsign == '':
197
+ raise LoginError('Server responded with empty callsign???')
146
198
  if callsign != self.callsign:
147
- raise LoginError(f"Server: {test}")
148
- if status != "verified," and self.passwd != "-1":
149
- raise LoginError("Password is incorrect")
199
+ raise LoginError(f'Server: {test}')
200
+ if status != 'verified,' and self.passwd != '-1':
201
+ raise LoginError('Password is incorrect')
150
202
 
151
- if self.passwd == "-1":
152
- self.logger.info("Login successful (receive only)")
203
+ if self.passwd == '-1':
204
+ self.logger.info('Login successful (receive only)')
153
205
  else:
154
- self.logger.info("Login successful")
206
+ self.logger.info('Login successful')
155
207
 
156
- self.logger.info(f"Connected to {server_string}")
208
+ self.logger.info(f'Connected to {server_string}')
157
209
  self.server_string = server_string
158
210
 
159
211
  except LoginError as e:
@@ -164,7 +216,7 @@ class Aprsdis(aprslib.IS):
164
216
  self.close()
165
217
  self.logger.error(f"Failed to login '{e}'")
166
218
  self.logger.exception(e)
167
- raise LoginError("Failed to login")
219
+ raise LoginError('Failed to login') from e
168
220
 
169
221
  def consumer(self, callback, blocking=True, immortal=False, raw=False):
170
222
  """
@@ -180,21 +232,21 @@ class Aprsdis(aprslib.IS):
180
232
  """
181
233
 
182
234
  if not self._connected:
183
- raise ConnectionError("not connected to a server")
235
+ raise ConnectionError('not connected to a server')
184
236
 
185
- line = b""
237
+ line = b''
186
238
 
187
239
  while True and not self.thread_stop:
188
240
  try:
189
241
  for line in self._socket_readlines(blocking):
190
- if line[0:1] != b"#":
242
+ if line[0:1] != b'#':
191
243
  self.aprsd_keepalive = datetime.datetime.now()
192
244
  if raw:
193
245
  callback(line)
194
246
  else:
195
247
  callback(self._parse(line))
196
248
  else:
197
- self.logger.debug("Server: %s", line.decode("utf8"))
249
+ self.logger.debug('Server: %s', line.decode('utf8'))
198
250
  self.aprsd_keepalive = datetime.datetime.now()
199
251
  except ParseError as exp:
200
252
  self.logger.log(
@@ -211,7 +263,7 @@ class Aprsdis(aprslib.IS):
211
263
  exp.packet,
212
264
  )
213
265
  except LoginError as exp:
214
- self.logger.error("%s: %s", exp.__class__.__name__, exp)
266
+ self.logger.error('%s: %s', exp.__class__.__name__, exp)
215
267
  except (KeyboardInterrupt, SystemExit):
216
268
  raise
217
269
  except (ConnectionDrop, ConnectionError):
@@ -227,7 +279,7 @@ class Aprsdis(aprslib.IS):
227
279
  except StopIteration:
228
280
  break
229
281
  except Exception:
230
- self.logger.error("APRS Packet: %s", line)
282
+ self.logger.error('APRS Packet: %s', line)
231
283
  raise
232
284
 
233
285
  if not blocking:
@@ -18,12 +18,13 @@ class KISS3Client:
18
18
 
19
19
  # date for last time we heard from the server
20
20
  aprsd_keepalive = datetime.datetime.now()
21
+ _connected = False
21
22
 
22
23
  def __init__(self):
23
24
  self.setup()
24
25
 
25
26
  def is_alive(self):
26
- return True
27
+ return self._connected
27
28
 
28
29
  def setup(self):
29
30
  # we can be TCP kiss or Serial kiss
@@ -56,17 +57,33 @@ class KISS3Client:
56
57
  self.path = CONF.kiss_tcp.path
57
58
 
58
59
  LOG.debug('Starting KISS interface connection')
59
- self.kiss.start()
60
+ try:
61
+ self.kiss.start()
62
+ if self.kiss.protocol.transport.is_closing():
63
+ LOG.warning('KISS transport is closing, not setting consumer callback')
64
+ self._connected = False
65
+ else:
66
+ self._connected = True
67
+ except Exception:
68
+ LOG.error('Failed to start KISS interface.')
69
+ self._connected = False
60
70
 
61
71
  @trace.trace
62
72
  def stop(self):
73
+ if not self._connected:
74
+ # do nothing since we aren't connected
75
+ return
76
+
63
77
  try:
64
78
  self.kiss.stop()
65
79
  self.kiss.loop.call_soon_threadsafe(
66
80
  self.kiss.protocol.transport.close,
67
81
  )
68
- except Exception as ex:
69
- LOG.exception(ex)
82
+ except Exception:
83
+ LOG.error('Failed to stop KISS interface.')
84
+
85
+ def close(self):
86
+ self.stop()
70
87
 
71
88
  def set_filter(self, filter):
72
89
  # This does nothing right now.
@@ -86,8 +103,14 @@ class KISS3Client:
86
103
  LOG.exception(ex)
87
104
 
88
105
  def consumer(self, callback):
106
+ if not self._connected:
107
+ raise Exception('KISS transport is not connected')
108
+
89
109
  self._parse_callback = callback
90
- self.kiss.read(callback=self.parse_frame, min_frames=None)
110
+ if not self.kiss.protocol.transport.is_closing():
111
+ self.kiss.read(callback=self.parse_frame, min_frames=1)
112
+ else:
113
+ self._connected = False
91
114
 
92
115
  def send(self, packet):
93
116
  """Send an APRS Message object."""
aprsd/client/kiss.py CHANGED
@@ -140,3 +140,4 @@ class KISSClient(base.APRSClient):
140
140
  except Exception as ex:
141
141
  LOG.error(f'Consumer failed {ex}')
142
142
  LOG.error(ex)
143
+ raise ex