2021-07-29 14:49:41 +00:00
|
|
|
import asyncio
|
2016-02-28 11:11:16 +00:00
|
|
|
import socket
|
|
|
|
import logging
|
2017-03-07 20:41:22 +00:00
|
|
|
from time import time, sleep
|
2016-02-28 11:11:16 +00:00
|
|
|
|
2016-02-28 11:28:43 +00:00
|
|
|
from ogn.client import settings
|
2016-02-28 11:11:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
def create_aprs_login(user_name, pass_code, app_name, app_version, aprs_filter=None):
|
|
|
|
if not aprs_filter:
|
|
|
|
return "user {} pass {} vers {} {}\n".format(user_name, pass_code, app_name, app_version)
|
|
|
|
else:
|
|
|
|
return "user {} pass {} vers {} {} filter {}\n".format(user_name, pass_code, app_name, app_version, aprs_filter)
|
|
|
|
|
|
|
|
|
2016-02-28 11:28:43 +00:00
|
|
|
class AprsClient:
|
Allow dynamic settings override
Additional (optional) 'settings' attribute in AprsClient,
to be able to pass custom settings to the client,
without changing the settings.py file.
Added 'CustomSettings' class for backward compatible attribute access.
Example (myapp.py):
...
args = ... parse arguments from command line
settings = CustomSettings(
APRS_SERVER_HOST = args.server,
APRS_SERVER_PORT_FULL_FEED = args.port,
APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = args.port,
APRS_APP_NAME = 'python-ogn-client',
PACKAGE_VERSION = '0.9.5',
APRS_APP_VER = '0.9',
APRS_KEEPALIVE_TIME = 240,
TELNET_SERVER_HOST = 'localhost',
TELNET_SERVER_PORT = 50001,
)
client = AprsClient(aprs_user='N0CALL', aprs_filter=args.filter, settings=settings)
client.connect()
...
2019-12-18 07:30:39 +00:00
|
|
|
def __init__(self, aprs_user, aprs_filter='', settings=settings):
|
2016-02-28 11:11:16 +00:00
|
|
|
self.logger = logging.getLogger(__name__)
|
2020-10-25 20:23:40 +00:00
|
|
|
self.logger.addHandler(logging.NullHandler())
|
2020-10-25 20:21:58 +00:00
|
|
|
|
2016-02-28 11:11:16 +00:00
|
|
|
self.aprs_user = aprs_user
|
|
|
|
self.aprs_filter = aprs_filter
|
Allow dynamic settings override
Additional (optional) 'settings' attribute in AprsClient,
to be able to pass custom settings to the client,
without changing the settings.py file.
Added 'CustomSettings' class for backward compatible attribute access.
Example (myapp.py):
...
args = ... parse arguments from command line
settings = CustomSettings(
APRS_SERVER_HOST = args.server,
APRS_SERVER_PORT_FULL_FEED = args.port,
APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = args.port,
APRS_APP_NAME = 'python-ogn-client',
PACKAGE_VERSION = '0.9.5',
APRS_APP_VER = '0.9',
APRS_KEEPALIVE_TIME = 240,
TELNET_SERVER_HOST = 'localhost',
TELNET_SERVER_PORT = 50001,
)
client = AprsClient(aprs_user='N0CALL', aprs_filter=args.filter, settings=settings)
client.connect()
...
2019-12-18 07:30:39 +00:00
|
|
|
self.settings = settings
|
2016-02-28 11:11:16 +00:00
|
|
|
|
2021-06-02 21:24:10 +00:00
|
|
|
self._sock_peer_ip = None
|
2017-07-20 08:58:28 +00:00
|
|
|
self._kill = False
|
|
|
|
|
2021-07-29 14:49:41 +00:00
|
|
|
self.reader = None
|
|
|
|
self.writer = None
|
|
|
|
|
|
|
|
async def connect(self, retries=1, wait_period=15):
|
2016-02-28 11:11:16 +00:00
|
|
|
# create socket, connect to server, login and make a file object associated with the socket
|
2020-10-25 20:21:58 +00:00
|
|
|
while retries > 0:
|
|
|
|
retries -= 1
|
|
|
|
try:
|
2021-07-29 14:49:41 +00:00
|
|
|
#self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
#self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
|
|
#self.sock.settimeout(5)
|
2020-10-25 20:21:58 +00:00
|
|
|
|
|
|
|
if self.aprs_filter:
|
|
|
|
port = self.settings.APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS
|
|
|
|
else:
|
|
|
|
port = self.settings.APRS_SERVER_PORT_FULL_FEED
|
|
|
|
|
2021-07-29 14:49:41 +00:00
|
|
|
# self.sock.connect((self.settings.APRS_SERVER_HOST, port))
|
|
|
|
self.reader, self.writer = await asyncio.open_connection(self.settings.APRS_SERVER_HOST, port)
|
|
|
|
# self._sock_peer_ip = self.sock.getpeername()[0]
|
|
|
|
if (sock := self.writer.get_extra_info('socket')):
|
|
|
|
self._sock_peer_ip = sock.getpeername()[0]
|
2020-10-25 20:21:58 +00:00
|
|
|
|
|
|
|
login = create_aprs_login(self.aprs_user, -1, self.settings.APRS_APP_NAME, self.settings.APRS_APP_VER, self.aprs_filter)
|
2021-07-29 14:49:41 +00:00
|
|
|
#self.sock.send(login.encode())
|
|
|
|
self.writer.write(login.encode())
|
|
|
|
await self.writer.drain()
|
|
|
|
#self.sock_file = self.sock.makefile('rb')
|
2020-10-25 20:21:58 +00:00
|
|
|
|
|
|
|
self._kill = False
|
2021-06-02 21:24:10 +00:00
|
|
|
self.logger.info("Connect to OGN ({}/{}:{}) as {} with filter: {}".
|
|
|
|
format(self.settings.APRS_SERVER_HOST, self._sock_peer_ip, port, self.aprs_user,
|
|
|
|
"'" + self.aprs_filter + "'" if self.aprs_filter else 'none (full-feed)'))
|
2020-10-25 20:21:58 +00:00
|
|
|
break
|
|
|
|
except (socket.error, ConnectionError) as e:
|
|
|
|
self.logger.error('Connect error: {}'.format(e))
|
|
|
|
if retries > 0:
|
|
|
|
self.logger.info('Waiting {}s before next connection try ({} attempts left).'.format(wait_period, retries))
|
|
|
|
sleep(wait_period)
|
|
|
|
else:
|
|
|
|
self._kill = True
|
2020-11-02 20:46:59 +00:00
|
|
|
self.logger.critical('Could not connect to OGN.')
|
2017-07-20 13:28:01 +00:00
|
|
|
|
2021-07-29 14:49:41 +00:00
|
|
|
async def disconnect(self):
|
2021-06-02 21:24:10 +00:00
|
|
|
self.logger.info('Disconnect from {}'.format(self._sock_peer_ip))
|
2016-02-28 11:11:16 +00:00
|
|
|
try:
|
|
|
|
# close everything
|
2021-07-29 14:49:41 +00:00
|
|
|
# self.sock.shutdown(0)
|
|
|
|
# self.sock.close()
|
|
|
|
self.writer.close()
|
|
|
|
await self.writer.wait_closed()
|
2016-02-28 11:11:16 +00:00
|
|
|
except OSError:
|
2020-10-25 20:21:58 +00:00
|
|
|
self.logger.error('Socket close error')
|
2016-02-28 11:11:16 +00:00
|
|
|
|
2017-07-20 08:58:28 +00:00
|
|
|
self._kill = True
|
|
|
|
|
2021-07-29 14:49:41 +00:00
|
|
|
async def run(self, callback, timed_callback=lambda client: None, autoreconnect=False, ignore_decoding_error=True,
|
2021-05-31 18:45:16 +00:00
|
|
|
**kwargs):
|
2017-07-20 08:58:28 +00:00
|
|
|
while not self._kill:
|
2016-02-28 11:11:16 +00:00
|
|
|
try:
|
|
|
|
keepalive_time = time()
|
2017-07-20 08:58:28 +00:00
|
|
|
while not self._kill:
|
Allow dynamic settings override
Additional (optional) 'settings' attribute in AprsClient,
to be able to pass custom settings to the client,
without changing the settings.py file.
Added 'CustomSettings' class for backward compatible attribute access.
Example (myapp.py):
...
args = ... parse arguments from command line
settings = CustomSettings(
APRS_SERVER_HOST = args.server,
APRS_SERVER_PORT_FULL_FEED = args.port,
APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = args.port,
APRS_APP_NAME = 'python-ogn-client',
PACKAGE_VERSION = '0.9.5',
APRS_APP_VER = '0.9',
APRS_KEEPALIVE_TIME = 240,
TELNET_SERVER_HOST = 'localhost',
TELNET_SERVER_PORT = 50001,
)
client = AprsClient(aprs_user='N0CALL', aprs_filter=args.filter, settings=settings)
client.connect()
...
2019-12-18 07:30:39 +00:00
|
|
|
if time() - keepalive_time > self.settings.APRS_KEEPALIVE_TIME:
|
2021-06-02 21:24:10 +00:00
|
|
|
self.logger.info('Send keepalive to {}'.format(self._sock_peer_ip))
|
2021-07-29 14:49:41 +00:00
|
|
|
#self.sock.send('#keepalive\n'.encode())
|
|
|
|
self.writer.write('#keepalive\n'.encode())
|
|
|
|
await self.writer.drain()
|
2016-03-25 17:48:44 +00:00
|
|
|
timed_callback(self)
|
2016-02-28 11:11:16 +00:00
|
|
|
keepalive_time = time()
|
|
|
|
|
|
|
|
# Read packet string from socket
|
2021-07-29 14:49:41 +00:00
|
|
|
#packet_b = self.sock_file.readline().strip()
|
|
|
|
packet_b = await self.reader.readline()
|
|
|
|
packet_str = packet_b.strip().decode(errors="replace") if ignore_decoding_error else packet_b.decode()
|
2016-02-28 11:11:16 +00:00
|
|
|
|
|
|
|
# A zero length line should not be return if keepalives are being sent
|
|
|
|
# A zero length line will only be returned after ~30m if keepalives are not sent
|
|
|
|
if len(packet_str) == 0:
|
2021-06-02 21:24:10 +00:00
|
|
|
self.logger.warning('Read returns zero length string. Failure. Orderly closeout from {}'.
|
|
|
|
format(self._sock_peer_ip))
|
2016-02-28 11:11:16 +00:00
|
|
|
break
|
|
|
|
|
2020-07-18 10:21:18 +00:00
|
|
|
callback(packet_str, **kwargs)
|
2016-02-28 11:11:16 +00:00
|
|
|
except socket.error:
|
2020-10-25 20:21:58 +00:00
|
|
|
self.logger.error('socket.error')
|
2020-10-11 12:01:55 +00:00
|
|
|
except OSError:
|
2020-10-25 20:21:58 +00:00
|
|
|
self.logger.error('OSError')
|
2017-12-13 18:08:57 +00:00
|
|
|
except UnicodeDecodeError:
|
2020-10-25 20:21:58 +00:00
|
|
|
self.logger.error('UnicodeDecodeError')
|
2021-05-31 18:45:16 +00:00
|
|
|
self.logger.debug(packet_b)
|
2016-02-28 11:11:16 +00:00
|
|
|
|
2017-07-20 08:58:28 +00:00
|
|
|
if autoreconnect and not self._kill:
|
2021-07-29 14:49:41 +00:00
|
|
|
await self.connect(retries=100)
|
2016-02-28 11:11:16 +00:00
|
|
|
else:
|
|
|
|
return
|
2017-03-07 20:41:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TelnetClient:
|
Allow dynamic settings override
Additional (optional) 'settings' attribute in AprsClient,
to be able to pass custom settings to the client,
without changing the settings.py file.
Added 'CustomSettings' class for backward compatible attribute access.
Example (myapp.py):
...
args = ... parse arguments from command line
settings = CustomSettings(
APRS_SERVER_HOST = args.server,
APRS_SERVER_PORT_FULL_FEED = args.port,
APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = args.port,
APRS_APP_NAME = 'python-ogn-client',
PACKAGE_VERSION = '0.9.5',
APRS_APP_VER = '0.9',
APRS_KEEPALIVE_TIME = 240,
TELNET_SERVER_HOST = 'localhost',
TELNET_SERVER_PORT = 50001,
)
client = AprsClient(aprs_user='N0CALL', aprs_filter=args.filter, settings=settings)
client.connect()
...
2019-12-18 07:30:39 +00:00
|
|
|
def __init__(self, settings=settings):
|
2017-03-07 20:41:22 +00:00
|
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
self.logger.info("Connect to local telnet server")
|
Allow dynamic settings override
Additional (optional) 'settings' attribute in AprsClient,
to be able to pass custom settings to the client,
without changing the settings.py file.
Added 'CustomSettings' class for backward compatible attribute access.
Example (myapp.py):
...
args = ... parse arguments from command line
settings = CustomSettings(
APRS_SERVER_HOST = args.server,
APRS_SERVER_PORT_FULL_FEED = args.port,
APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = args.port,
APRS_APP_NAME = 'python-ogn-client',
PACKAGE_VERSION = '0.9.5',
APRS_APP_VER = '0.9',
APRS_KEEPALIVE_TIME = 240,
TELNET_SERVER_HOST = 'localhost',
TELNET_SERVER_PORT = 50001,
)
client = AprsClient(aprs_user='N0CALL', aprs_filter=args.filter, settings=settings)
client.connect()
...
2019-12-18 07:30:39 +00:00
|
|
|
self.settings = settings
|
2017-03-07 20:41:22 +00:00
|
|
|
|
|
|
|
def connect(self):
|
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
Allow dynamic settings override
Additional (optional) 'settings' attribute in AprsClient,
to be able to pass custom settings to the client,
without changing the settings.py file.
Added 'CustomSettings' class for backward compatible attribute access.
Example (myapp.py):
...
args = ... parse arguments from command line
settings = CustomSettings(
APRS_SERVER_HOST = args.server,
APRS_SERVER_PORT_FULL_FEED = args.port,
APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = args.port,
APRS_APP_NAME = 'python-ogn-client',
PACKAGE_VERSION = '0.9.5',
APRS_APP_VER = '0.9',
APRS_KEEPALIVE_TIME = 240,
TELNET_SERVER_HOST = 'localhost',
TELNET_SERVER_PORT = 50001,
)
client = AprsClient(aprs_user='N0CALL', aprs_filter=args.filter, settings=settings)
client.connect()
...
2019-12-18 07:30:39 +00:00
|
|
|
self.sock.connect((self.settings.TELNET_SERVER_HOST, self.settings.TELNET_SERVER_PORT))
|
2017-03-07 20:41:22 +00:00
|
|
|
|
|
|
|
def run(self, callback, autoreconnect=False):
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
self.sock_file = self.sock.makefile(mode='rw', encoding='iso-8859-1')
|
|
|
|
while True:
|
|
|
|
packet_str = self.sock_file.readline().strip()
|
|
|
|
callback(packet_str)
|
|
|
|
|
|
|
|
except ConnectionRefusedError:
|
|
|
|
self.logger.error('Telnet server not running', exc_info=True)
|
|
|
|
|
|
|
|
if autoreconnect:
|
|
|
|
sleep(1)
|
|
|
|
self.connect()
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
def disconnect(self):
|
|
|
|
self.logger.info('Disconnect')
|
|
|
|
self.sock.shutdown(0)
|
|
|
|
self.sock.close()
|