aprsd 4.0.1__py3-none-any.whl → 4.1.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.
@@ -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:
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  import logging
2
3
  import threading
3
4
  import time
@@ -20,6 +21,9 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
20
21
  # flag to tell us to stop
21
22
  thread_stop = False
22
23
 
24
+ # date for last time we heard from the server
25
+ aprsd_keepalive = datetime.datetime.now()
26
+
23
27
  lock = threading.Lock()
24
28
  path = []
25
29
 
@@ -63,6 +67,7 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
63
67
  raw = 'GTOWN>APDW16,WIDE1-1,WIDE2-1:}KM6LYW-9>APZ100,TCPIP,GTOWN*::KM6LYW :KM6LYW: 19 Miles SW'
64
68
  pkt_raw = aprslib.parse(raw)
65
69
  pkt = core.factory(pkt_raw)
70
+ self.aprsd_keepalive = datetime.datetime.now()
66
71
  callback(packet=pkt)
67
72
  LOG.debug(f'END blocking FAKE consumer {self}')
68
73
  time.sleep(8)
@@ -1,32 +1,36 @@
1
+ import datetime
1
2
  import logging
2
3
 
3
- from ax253 import Frame
4
4
  import kiss
5
+ from ax253 import Frame
5
6
  from oslo_config import cfg
6
7
 
7
8
  from aprsd import conf # noqa
8
9
  from aprsd.packets import core
9
10
  from aprsd.utils import trace
10
11
 
11
-
12
12
  CONF = cfg.CONF
13
- LOG = logging.getLogger("APRSD")
13
+ LOG = logging.getLogger('APRSD')
14
14
 
15
15
 
16
16
  class KISS3Client:
17
17
  path = []
18
18
 
19
+ # date for last time we heard from the server
20
+ aprsd_keepalive = datetime.datetime.now()
21
+ _connected = False
22
+
19
23
  def __init__(self):
20
24
  self.setup()
21
25
 
22
26
  def is_alive(self):
23
- return True
27
+ return self._connected
24
28
 
25
29
  def setup(self):
26
30
  # we can be TCP kiss or Serial kiss
27
31
  if CONF.kiss_serial.enabled:
28
32
  LOG.debug(
29
- "KISS({}) Serial connection to {}".format(
33
+ 'KISS({}) Serial connection to {}'.format(
30
34
  kiss.__version__,
31
35
  CONF.kiss_serial.device,
32
36
  ),
@@ -39,7 +43,7 @@ class KISS3Client:
39
43
  self.path = CONF.kiss_serial.path
40
44
  elif CONF.kiss_tcp.enabled:
41
45
  LOG.debug(
42
- "KISS({}) TCP Connection to {}:{}".format(
46
+ 'KISS({}) TCP Connection to {}:{}'.format(
43
47
  kiss.__version__,
44
48
  CONF.kiss_tcp.host,
45
49
  CONF.kiss_tcp.port,
@@ -52,18 +56,34 @@ class KISS3Client:
52
56
  )
53
57
  self.path = CONF.kiss_tcp.path
54
58
 
55
- LOG.debug("Starting KISS interface connection")
56
- self.kiss.start()
59
+ LOG.debug('Starting KISS interface connection')
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
57
70
 
58
71
  @trace.trace
59
72
  def stop(self):
73
+ if not self._connected:
74
+ # do nothing since we aren't connected
75
+ return
76
+
60
77
  try:
61
78
  self.kiss.stop()
62
79
  self.kiss.loop.call_soon_threadsafe(
63
80
  self.kiss.protocol.transport.close,
64
81
  )
65
- except Exception as ex:
66
- LOG.exception(ex)
82
+ except Exception:
83
+ LOG.error('Failed to stop KISS interface.')
84
+
85
+ def close(self):
86
+ self.stop()
67
87
 
68
88
  def set_filter(self, filter):
69
89
  # This does nothing right now.
@@ -74,18 +94,23 @@ class KISS3Client:
74
94
  frame = Frame.from_bytes(frame_bytes)
75
95
  # Now parse it with aprslib
76
96
  kwargs = {
77
- "frame": frame,
97
+ 'frame': frame,
78
98
  }
79
99
  self._parse_callback(**kwargs)
100
+ self.aprsd_keepalive = datetime.datetime.now()
80
101
  except Exception as ex:
81
- LOG.error("Failed to parse bytes received from KISS interface.")
102
+ LOG.error('Failed to parse bytes received from KISS interface.')
82
103
  LOG.exception(ex)
83
104
 
84
105
  def consumer(self, callback):
85
- LOG.debug("Start blocking KISS consumer")
106
+ if not self._connected:
107
+ raise Exception('KISS transport is not connected')
108
+
86
109
  self._parse_callback = callback
87
- self.kiss.read(callback=self.parse_frame, min_frames=None)
88
- LOG.debug(f"END blocking KISS consumer {self.kiss}")
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
89
114
 
90
115
  def send(self, packet):
91
116
  """Send an APRS Message object."""
@@ -94,24 +119,24 @@ class KISS3Client:
94
119
  path = self.path
95
120
  if isinstance(packet, core.Packet):
96
121
  packet.prepare()
97
- payload = packet.payload.encode("US-ASCII")
122
+ payload = packet.payload.encode('US-ASCII')
98
123
  if packet.path:
99
124
  path = packet.path
100
125
  else:
101
- msg_payload = f"{packet.raw}{{{str(packet.msgNo)}"
126
+ msg_payload = f'{packet.raw}{{{str(packet.msgNo)}'
102
127
  payload = (
103
- ":{:<9}:{}".format(
128
+ ':{:<9}:{}'.format(
104
129
  packet.to_call,
105
130
  msg_payload,
106
131
  )
107
- ).encode("US-ASCII")
132
+ ).encode('US-ASCII')
108
133
 
109
134
  LOG.debug(
110
135
  f"KISS Send '{payload}' TO '{packet.to_call}' From "
111
136
  f"'{packet.from_call}' with PATH '{path}'",
112
137
  )
113
138
  frame = Frame.ui(
114
- destination="APZ100",
139
+ destination='APZ100',
115
140
  source=packet.from_call,
116
141
  path=path,
117
142
  info=payload,
aprsd/client/kiss.py CHANGED
@@ -12,7 +12,7 @@ from aprsd.client.drivers import kiss
12
12
  from aprsd.packets import core
13
13
 
14
14
  CONF = cfg.CONF
15
- LOG = logging.getLogger("APRSD")
15
+ LOG = logging.getLogger('APRSD')
16
16
  LOGU = logger
17
17
 
18
18
 
@@ -27,15 +27,15 @@ class KISSClient(base.APRSClient):
27
27
  if serializable:
28
28
  keepalive = keepalive.isoformat()
29
29
  stats = {
30
- "connected": self.is_connected,
31
- "connection_keepalive": keepalive,
32
- "transport": self.transport(),
30
+ 'connected': self.is_connected,
31
+ 'connection_keepalive': keepalive,
32
+ 'transport': self.transport(),
33
33
  }
34
34
  if self.transport() == client.TRANSPORT_TCPKISS:
35
- stats["host"] = CONF.kiss_tcp.host
36
- stats["port"] = CONF.kiss_tcp.port
35
+ stats['host'] = CONF.kiss_tcp.host
36
+ stats['port'] = CONF.kiss_tcp.port
37
37
  elif self.transport() == client.TRANSPORT_SERIALKISS:
38
- stats["device"] = CONF.kiss_serial.device
38
+ stats['device'] = CONF.kiss_serial.device
39
39
  return stats
40
40
 
41
41
  @staticmethod
@@ -56,15 +56,15 @@ class KISSClient(base.APRSClient):
56
56
  transport = KISSClient.transport()
57
57
  if transport == client.TRANSPORT_SERIALKISS:
58
58
  if not CONF.kiss_serial.device:
59
- LOG.error("KISS serial enabled, but no device is set.")
59
+ LOG.error('KISS serial enabled, but no device is set.')
60
60
  raise exception.MissingConfigOptionException(
61
- "kiss_serial.device is not set.",
61
+ 'kiss_serial.device is not set.',
62
62
  )
63
63
  elif transport == client.TRANSPORT_TCPKISS:
64
64
  if not CONF.kiss_tcp.host:
65
- LOG.error("KISS TCP enabled, but no host is set.")
65
+ LOG.error('KISS TCP enabled, but no host is set.')
66
66
  raise exception.MissingConfigOptionException(
67
- "kiss_tcp.host is not set.",
67
+ 'kiss_tcp.host is not set.',
68
68
  )
69
69
 
70
70
  return True
@@ -91,8 +91,8 @@ class KISSClient(base.APRSClient):
91
91
  if ka := self._client.aprsd_keepalive:
92
92
  keepalive = timeago.format(ka)
93
93
  else:
94
- keepalive = "N/A"
95
- LOGU.opt(colors=True).info(f"<green>Client keepalive {keepalive}</green>")
94
+ keepalive = 'N/A'
95
+ LOGU.opt(colors=True).info(f'<green>Client keepalive {keepalive}</green>')
96
96
 
97
97
  @staticmethod
98
98
  def transport():
@@ -104,8 +104,8 @@ class KISSClient(base.APRSClient):
104
104
 
105
105
  def decode_packet(self, *args, **kwargs):
106
106
  """We get a frame, which has to be decoded."""
107
- LOG.debug(f"kwargs {kwargs}")
108
- frame = kwargs["frame"]
107
+ LOG.debug(f'kwargs {kwargs}')
108
+ frame = kwargs['frame']
109
109
  LOG.debug(f"Got an APRS Frame '{frame}'")
110
110
  # try and nuke the * from the fromcall sign.
111
111
  # frame.header._source._ch = False
@@ -114,20 +114,23 @@ class KISSClient(base.APRSClient):
114
114
  # msg = frame.tnc2
115
115
  # LOG.debug(f"Decoding {msg}")
116
116
 
117
- raw = aprslib.parse(str(frame))
118
- packet = core.factory(raw)
119
- if isinstance(packet, core.ThirdPartyPacket):
120
- return packet.subpacket
121
- else:
122
- return packet
117
+ try:
118
+ raw = aprslib.parse(str(frame))
119
+ packet = core.factory(raw)
120
+ if isinstance(packet, core.ThirdPartyPacket):
121
+ return packet.subpacket
122
+ else:
123
+ return packet
124
+ except Exception as ex:
125
+ LOG.error(f'Error decoding packet: {ex}')
123
126
 
124
127
  def setup_connection(self):
125
128
  try:
126
129
  self._client = kiss.KISS3Client()
127
- self.connected = self.login_status["success"] = True
130
+ self.connected = self.login_status['success'] = True
128
131
  except Exception as ex:
129
- self.connected = self.login_status["success"] = False
130
- self.login_status["message"] = str(ex)
132
+ self.connected = self.login_status['success'] = False
133
+ self.login_status['message'] = str(ex)
131
134
  return self._client
132
135
 
133
136
  def consumer(self, callback, blocking=False, immortal=False, raw=False):
@@ -135,5 +138,6 @@ class KISSClient(base.APRSClient):
135
138
  self._client.consumer(callback)
136
139
  self.keepalive = datetime.datetime.now()
137
140
  except Exception as ex:
138
- LOG.error(f"Consumer failed {ex}")
141
+ LOG.error(f'Consumer failed {ex}')
139
142
  LOG.error(ex)
143
+ raise ex