split the mudule into seperate files
This commit is contained in:
parent
3696ed93a2
commit
5581f651e0
|
|
@ -0,0 +1,2 @@
|
|||
*.swp
|
||||
*.pyc
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
"""
|
||||
IS class is used for connection to APRS-IS network
|
||||
"""
|
||||
import socket
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from .version import __version__
|
||||
from .parse import parse
|
||||
from .exceptions import (
|
||||
GenericError,
|
||||
ConnectionDrop,
|
||||
ConnectionError,
|
||||
LoginError,
|
||||
)
|
||||
|
||||
__all__ = ['IS']
|
||||
|
||||
|
||||
class IS(object):
|
||||
"""
|
||||
The IS class is used to connect to aprs-is network and listen to the stream
|
||||
of packets. You can either run them through aprs.parse() or get them in raw
|
||||
form.
|
||||
|
||||
Note: sending of packets is not supported yet
|
||||
|
||||
"""
|
||||
def __init__(self, callsign, host="rotate.aprs.net", port=14580, passwd="-1"):
|
||||
"""
|
||||
Host & port - aprs-is server
|
||||
callsign - used when login in
|
||||
passwd - for verification, or "-1" if only listening
|
||||
"""
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
self.set_server(host, port)
|
||||
self.set_login(callsign, passwd)
|
||||
|
||||
self.sock = None
|
||||
self.filter = "t/poimqstunw" # default filter, everything
|
||||
|
||||
self._connected = False
|
||||
self.buf = ''
|
||||
|
||||
def callsign_filter(self, callsigns):
|
||||
"""
|
||||
Sets a filter for the specified callsigns.
|
||||
Only those will be sent to us by the server
|
||||
"""
|
||||
|
||||
if type(callsigns) is not list or len(callsigns) == 0:
|
||||
return False
|
||||
|
||||
return self.set_filter("b/%s" % "/".join(callsigns))
|
||||
|
||||
def set_filter(self, filter_text):
|
||||
"""
|
||||
Set a specified aprs-is filter for this connection
|
||||
"""
|
||||
self.filter = filter_text
|
||||
|
||||
self.logger.info("Setting filter to: %s", self.filter)
|
||||
|
||||
if self._connected:
|
||||
self.sock.sendall("#filter %s\r\n" % self.filter)
|
||||
|
||||
return True
|
||||
|
||||
def set_login(self, callsign, passwd):
|
||||
"""
|
||||
Set callsign and password
|
||||
"""
|
||||
self.callsign = callsign
|
||||
self.passwd = passwd
|
||||
|
||||
def set_server(self, host, port):
|
||||
"""
|
||||
Set server ip/host and port to use
|
||||
"""
|
||||
self.server = (host, port)
|
||||
|
||||
def connect(self, blocking=False):
|
||||
"""
|
||||
Initiate connection to APRS server and attempt to login
|
||||
"""
|
||||
|
||||
if not self._connected:
|
||||
while True:
|
||||
try:
|
||||
self.logger.info("Attempting connection to %s:%s", self.server[0], self.server[1])
|
||||
self._connect()
|
||||
|
||||
self.logger.info("Sending login information")
|
||||
self._send_login()
|
||||
|
||||
self.logger.info("Filter set to: %s", self.filter)
|
||||
|
||||
if self.passwd == "-1":
|
||||
self.logger.info("Login successful (receive only)")
|
||||
else:
|
||||
self.logger.info("Login successful")
|
||||
|
||||
break
|
||||
except:
|
||||
if not blocking:
|
||||
raise
|
||||
|
||||
time.sleep(30) # attempt to reconnect after 30 seconds
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Closes the socket
|
||||
Called internally when Exceptions are raised
|
||||
"""
|
||||
|
||||
self._connected = False
|
||||
self.buf = ''
|
||||
|
||||
if self.sock is not None:
|
||||
self.sock.close()
|
||||
|
||||
def consumer(self, callback, blocking=True, immortal=False, raw=False):
|
||||
"""
|
||||
When a position sentence is received, it will be passed to the callback function
|
||||
|
||||
blocking: if true (default), runs forever, otherwise will return after one sentence
|
||||
You can still exit the loop, by raising StopIteration in the callback function
|
||||
|
||||
immortal: When true, consumer will try to reconnect and stop propagation of Parse exceptions
|
||||
if false (default), consumer will return
|
||||
|
||||
raw: when true, raw packet is passed to callback, otherwise the result from aprs.parse()
|
||||
"""
|
||||
|
||||
if not self._connected:
|
||||
raise ConnectionError("not connected to a server")
|
||||
|
||||
while True:
|
||||
try:
|
||||
for line in self._socket_readlines(blocking):
|
||||
if line[0] != "#":
|
||||
if raw:
|
||||
callback(line)
|
||||
else:
|
||||
callback(parse(line))
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except (ConnectionDrop, ConnectionError):
|
||||
self.close()
|
||||
|
||||
if not immortal:
|
||||
raise
|
||||
else:
|
||||
self.connect(blocking=blocking)
|
||||
continue
|
||||
except GenericError:
|
||||
continue
|
||||
except StopIteration:
|
||||
break
|
||||
except:
|
||||
self.logger.error("APRS Packet: %s", line)
|
||||
raise
|
||||
|
||||
if not blocking:
|
||||
break
|
||||
|
||||
def _connect(self):
|
||||
"""
|
||||
Attemps to open a connection to the server
|
||||
"""
|
||||
|
||||
try:
|
||||
# 15 seconds connection timeout
|
||||
self.sock = socket.create_connection(self.server, 15)
|
||||
|
||||
# 5 second timeout to receive server banner
|
||||
self.sock.setblocking(1)
|
||||
self.sock.settimeout(5)
|
||||
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
|
||||
if sys.platform not in ['cygwin', 'win32']:
|
||||
self.sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 15)
|
||||
self.sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 3)
|
||||
self.sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 5)
|
||||
|
||||
if self.sock.recv(512)[0] != "#":
|
||||
raise ConnectionError("invalid banner from server")
|
||||
|
||||
except Exception, e:
|
||||
self.close()
|
||||
|
||||
if e == "timed out":
|
||||
raise ConnectionError("no banner from server")
|
||||
else:
|
||||
raise ConnectionError(e)
|
||||
|
||||
self._connected = True
|
||||
|
||||
def _send_login(self):
|
||||
"""
|
||||
Sends login string to server
|
||||
"""
|
||||
login_str = "user {0} pass {1} vers pyaprs {3} filter {2}\r\n"
|
||||
login_str = login_str.format(
|
||||
self.callsign,
|
||||
self.passwd,
|
||||
self.filter,
|
||||
__version__
|
||||
)
|
||||
|
||||
try:
|
||||
self.sock.sendall(login_str)
|
||||
self.sock.settimeout(5)
|
||||
test = self.sock.recv(len(login_str) + 100)
|
||||
|
||||
(x, x, callsign, status, x) = test.split(' ', 4)
|
||||
|
||||
if callsign == "":
|
||||
raise LoginError("No callsign provided")
|
||||
if callsign != self.callsign:
|
||||
raise LoginError("Server: %s" % test[2:])
|
||||
if status != "verified," and self.passwd != "-1":
|
||||
raise LoginError("Password is incorrect")
|
||||
|
||||
except LoginError, e:
|
||||
self.close()
|
||||
raise LoginError("failed to login: %s" % e)
|
||||
except:
|
||||
self.close()
|
||||
raise LoginError("failed to login")
|
||||
|
||||
def _socket_readlines(self, blocking=False):
|
||||
"""
|
||||
Generator for complete lines, received from the server
|
||||
"""
|
||||
try:
|
||||
self.sock.setblocking(0)
|
||||
except socket.error, e:
|
||||
raise ConnectionDrop("connection dropped")
|
||||
|
||||
while True:
|
||||
short_buf = ''
|
||||
|
||||
try:
|
||||
short_buf = self.sock.recv(1024)
|
||||
|
||||
# sock.recv returns empty if the connection drops
|
||||
if not short_buf:
|
||||
raise ConnectionDrop("connection dropped")
|
||||
except socket.error, e:
|
||||
if "Resource temporarily unavailable" in e:
|
||||
if not blocking:
|
||||
if len(self.buf) == 0:
|
||||
break
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
self.buf += short_buf
|
||||
|
||||
while "\r\n" in self.buf:
|
||||
line, self.buf = self.buf.split("\r\n", 1)
|
||||
|
||||
yield line
|
||||
|
||||
# lets not hog the CPU when there's nothing to do
|
||||
if blocking:
|
||||
time.sleep(0.1)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
"""
|
||||
APRS library in Python
|
||||
|
||||
Currently the library provides facilities to:
|
||||
- parse APRS packets
|
||||
- Connect and listen to an aprs-is packet feed
|
||||
|
||||
Copyright 2013-2014 (C), Rossen Georgiev
|
||||
"""
|
||||
|
||||
from datetime import date as _date
|
||||
__date__ = str(_date.today())
|
||||
del _date
|
||||
|
||||
import version
|
||||
__version__ = version.__version__
|
||||
del version
|
||||
|
||||
__author__ = "Rossen Georgiev"
|
||||
|
||||
from .IS import IS
|
||||
from .parse import parse
|
||||
|
||||
__all__ = ['IS', 'parse']
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
"""
|
||||
Contains exception definitions for the module
|
||||
"""
|
||||
import logging
|
||||
|
||||
__all__ = [
|
||||
"GenericError",
|
||||
"UnknownFormat",
|
||||
"ParseError",
|
||||
"LoginError",
|
||||
"ConnectionError",
|
||||
"ConnectionDrop",
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.raiseExceptions = False
|
||||
logging.addLevelName(11, "ParseError")
|
||||
|
||||
|
||||
class GenericError(Exception):
|
||||
"""
|
||||
Base exception class for the library. Logs information via logging module
|
||||
"""
|
||||
def __init__(self, message):
|
||||
logger.debug("%s: %s", self.__class__.__name__, message)
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class UnknownFormat(GenericError):
|
||||
"""
|
||||
Raised when aprs.parse() encounters an unsupported packet format
|
||||
|
||||
"""
|
||||
def __init__(self, message, packet=''):
|
||||
logger.log(9, "%s\nPacket: %s", message, packet)
|
||||
self.message = message
|
||||
self.packet = packet
|
||||
|
||||
|
||||
class ParseError(GenericError):
|
||||
"""
|
||||
Raised when unexpected format of a supported packet format is encountered
|
||||
"""
|
||||
def __init__(self, message, packet=''):
|
||||
logger.log(11, "%s\nPacket: %s", message, packet)
|
||||
self.message = message
|
||||
self.packet = packet
|
||||
|
||||
|
||||
class LoginError(GenericError):
|
||||
"""
|
||||
Raised when IS servers didn't respond correctly to our loging attempt
|
||||
"""
|
||||
def __init__(self, message):
|
||||
logger.error("%s: %s", self.__class__.__name__, message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class ConnectionError(GenericError):
|
||||
"""
|
||||
Riased when connection dies for some reason
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionDrop(ConnectionError):
|
||||
"""
|
||||
Raised when connetion drops or detected to be dead
|
||||
"""
|
||||
pass
|
||||
|
|
@ -1,282 +1,14 @@
|
|||
"""
|
||||
APRS library in Python
|
||||
|
||||
Currently the library provides facilities to:
|
||||
- parse APRS packets
|
||||
- Connect and listen to an aprs-is packet feed
|
||||
|
||||
Copyright 2013-2014 (C), Rossen Georgiev
|
||||
"""
|
||||
|
||||
import socket
|
||||
import time
|
||||
import datetime
|
||||
import re
|
||||
import math
|
||||
import logging
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
__version__ = "0.5.1"
|
||||
__author__ = "Rossen Georgiev"
|
||||
__date__ = str(datetime.date.today())
|
||||
from .exceptions import *
|
||||
|
||||
__all__ = ['IS', 'base91', 'parse']
|
||||
__all__ = ['parse']
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.addLevelName(11, "ParseError")
|
||||
|
||||
|
||||
class IS(object):
|
||||
"""
|
||||
The IS class is used to connect to aprs-is network and listen to the stream
|
||||
of packets. You can either run them through aprs.parse() or get them in raw
|
||||
form.
|
||||
|
||||
Note: sending of packets is not supported yet
|
||||
|
||||
"""
|
||||
def __init__(self, host, port, callsign, passwd):
|
||||
"""
|
||||
Host & port - aprs-is server
|
||||
callsign - used when login in
|
||||
passwd - for verification, or "-1" if only listening
|
||||
"""
|
||||
|
||||
self.set_server(host, port)
|
||||
self.set_login(callsign, passwd)
|
||||
|
||||
self.sock = None
|
||||
self.filter = "b/" # empty bud filter
|
||||
|
||||
self._connected = False
|
||||
self.buf = ''
|
||||
|
||||
def callsign_filter(self, callsigns):
|
||||
"""
|
||||
Sets a filter for the specified callsigns.
|
||||
Only those will be sent to us by the server
|
||||
"""
|
||||
|
||||
if type(callsigns) is not list or len(callsigns) == 0:
|
||||
return False
|
||||
|
||||
return self.set_filter("b/%s" % "/".join(callsigns))
|
||||
|
||||
def set_filter(self, filter_text):
|
||||
"""
|
||||
Set a specified aprs-is filter for this connection
|
||||
"""
|
||||
self.filter = filter_text
|
||||
|
||||
logger.info("Setting filter to: %s", self.filter)
|
||||
|
||||
if self._connected:
|
||||
self.sock.sendall("#filter %s\r\n" % self.filter)
|
||||
|
||||
return True
|
||||
|
||||
def set_login(self, callsign, passwd):
|
||||
"""
|
||||
Set callsign and password
|
||||
"""
|
||||
self.callsign = callsign
|
||||
self.passwd = passwd
|
||||
|
||||
def set_server(self, host, port):
|
||||
"""
|
||||
Set server ip/host and port to use
|
||||
"""
|
||||
self.server = (host, port)
|
||||
|
||||
def connect(self, blocking=False):
|
||||
"""
|
||||
Initiate connection to APRS server and attempt to login
|
||||
"""
|
||||
|
||||
if not self._connected:
|
||||
while True:
|
||||
try:
|
||||
logger.info("Attempting connection to %s:%s", self.server[0], self.server[1])
|
||||
self._connect()
|
||||
|
||||
logger.info("Sending login information")
|
||||
self._send_login()
|
||||
|
||||
logger.info("Filter set to: %s", self.filter)
|
||||
|
||||
if self.passwd == "-1":
|
||||
logger.info("Login successful (receive only)")
|
||||
else:
|
||||
logger.info("Login successful")
|
||||
|
||||
break
|
||||
except:
|
||||
if not blocking:
|
||||
raise
|
||||
|
||||
time.sleep(30) # attempt to reconnect after 30 seconds
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Closes the socket
|
||||
Called internally when Exceptions are raised
|
||||
"""
|
||||
|
||||
self._connected = False
|
||||
self.buf = ''
|
||||
|
||||
if self.sock is not None:
|
||||
self.sock.close()
|
||||
|
||||
def consumer(self, callback, blocking=True, immortal=False, raw=False):
|
||||
"""
|
||||
When a position sentence is received, it will be passed to the callback function
|
||||
|
||||
blocking: if true (default), runs forever, otherwise will return after one sentence
|
||||
You can still exit the loop, by raising StopIteration in the callback function
|
||||
|
||||
immortal: When true, consumer will try to reconnect and stop propagation of Parse exceptions.
|
||||
if false (default), consumer will return
|
||||
|
||||
raw: when true, raw packet is passed to callback, otherwise the result from aprs.parse()
|
||||
"""
|
||||
|
||||
if not self._connected:
|
||||
raise ConnectionError("not connected to a server")
|
||||
|
||||
while True:
|
||||
try:
|
||||
for line in self._socket_readlines(blocking):
|
||||
if line[0] != "#":
|
||||
if raw:
|
||||
callback(line)
|
||||
else:
|
||||
callback(parse(line))
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except (ConnectionDrop, ConnectionError):
|
||||
self.close()
|
||||
|
||||
if not immortal:
|
||||
raise
|
||||
else:
|
||||
self.connect(blocking=blocking)
|
||||
continue
|
||||
except GenericError:
|
||||
continue
|
||||
except StopIteration:
|
||||
break
|
||||
except:
|
||||
logger.error("APRS Packet: %s", line)
|
||||
raise
|
||||
|
||||
if not blocking:
|
||||
break
|
||||
|
||||
def _connect(self):
|
||||
"""
|
||||
Attemps to open a connection to the server
|
||||
"""
|
||||
|
||||
try:
|
||||
# 15 seconds connection timeout
|
||||
self.sock = socket.create_connection(self.server, 15)
|
||||
|
||||
# 5 second timeout to receive server banner
|
||||
self.sock.settimeout(5)
|
||||
self.sock.setblocking(True)
|
||||
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
|
||||
if sys.platform not in ['cygwin', 'win32']:
|
||||
self.sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 15)
|
||||
self.sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 3)
|
||||
self.sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 5)
|
||||
|
||||
if self.sock.recv(512)[0] != "#":
|
||||
raise ConnectionError("invalid banner from server")
|
||||
|
||||
except Exception, e:
|
||||
self.close()
|
||||
|
||||
if e == "timed out":
|
||||
raise ConnectionError("no banner from server")
|
||||
else:
|
||||
raise ConnectionError(e)
|
||||
|
||||
self._connected = True
|
||||
|
||||
def _send_login(self):
|
||||
"""
|
||||
Sends login string to server
|
||||
"""
|
||||
login_str = "user {0} pass {1} vers pytinyaprs 0.2 filter {2}\r\n".format(
|
||||
self.callsign,
|
||||
self.passwd,
|
||||
self.filter
|
||||
)
|
||||
|
||||
try:
|
||||
self.sock.sendall(login_str)
|
||||
self.sock.settimeout(5)
|
||||
test = self.sock.recv(len(login_str) + 100)
|
||||
self.sock.setblocking(True)
|
||||
|
||||
(x, x, callsign, status, x) = test.split(' ', 4)
|
||||
|
||||
if callsign != self.callsign:
|
||||
raise LoginError("login callsign does not match")
|
||||
if status != "verified," and self.passwd != "-1":
|
||||
raise LoginError("callsign is not 'verified'")
|
||||
|
||||
except LoginError, e:
|
||||
self.close()
|
||||
raise LoginError("failed to login: %s" % e)
|
||||
except:
|
||||
self.close()
|
||||
raise LoginError("failed to login")
|
||||
|
||||
def _socket_readlines(self, blocking=False):
|
||||
"""
|
||||
Generator for complete lines, received from the server
|
||||
"""
|
||||
try:
|
||||
self.sock.setblocking(False)
|
||||
except socket.error, e:
|
||||
raise ConnectionDrop("connection dropped")
|
||||
|
||||
while True:
|
||||
short_buf = ''
|
||||
|
||||
try:
|
||||
short_buf = self.sock.recv(1024)
|
||||
|
||||
# sock.recv returns empty if the connection drops
|
||||
if not short_buf:
|
||||
raise ConnectionDrop("connection dropped")
|
||||
except socket.error, e:
|
||||
if "Resource temporarily unavailable" in e:
|
||||
if not blocking:
|
||||
if len(self.buf) == 0:
|
||||
break
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
self.buf += short_buf
|
||||
|
||||
while "\r\n" in self.buf:
|
||||
line, self.buf = self.buf.split("\r\n", 1)
|
||||
|
||||
yield line
|
||||
|
||||
if not blocking:
|
||||
raise StopIteration
|
||||
|
||||
# in blocking mode this will fast if there is no data
|
||||
# so we should sleep and not hog the CPU
|
||||
if blocking:
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
# Mic-e message type table
|
||||
|
||||
|
|
@ -388,7 +120,7 @@ def parse(raw_sentence):
|
|||
match = re.findall(r"^((\d{6})(.))$", body[0:7])
|
||||
if match:
|
||||
rawts, ts, form = match[0]
|
||||
utc = datetime.datetime.utcnow()
|
||||
utc = datetime.utcnow()
|
||||
|
||||
if packet_type == '>' and form != 'z':
|
||||
raise ParseError("Time format for status reports should be zulu", raw_sentence)
|
||||
|
|
@ -622,7 +354,7 @@ def parse(raw_sentence):
|
|||
logger.debug("Packet is just a status message")
|
||||
parsed.update({
|
||||
'format': 'status',
|
||||
'comment': body
|
||||
'status': body
|
||||
})
|
||||
|
||||
# MESSAGE PACKET
|
||||
|
|
@ -952,60 +684,3 @@ def _parse_comment_telemetry(text):
|
|||
return [text, parsed]
|
||||
else:
|
||||
return [text, {}]
|
||||
|
||||
|
||||
# Exceptions
|
||||
class GenericError(Exception):
|
||||
"""
|
||||
Base exception class for the library. Logs information via logging module
|
||||
"""
|
||||
def __init__(self, message):
|
||||
logger.debug("%s: %s", self.__class__.__name__, message)
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class UnknownFormat(GenericError):
|
||||
"""
|
||||
Raised when aprs.parse() encounters an unsupported packet format
|
||||
|
||||
"""
|
||||
def __init__(self, message, packet=''):
|
||||
logger.log(9, "%s\nPacket: %s" % (message, packet))
|
||||
self.message = message
|
||||
self.packet = packet
|
||||
|
||||
|
||||
class ParseError(GenericError):
|
||||
"""
|
||||
Raised when aprs.parse() encounters unexpected formating of a supported packet format
|
||||
"""
|
||||
def __init__(self, message, packet=''):
|
||||
logger.log(11, "%s\nPacket: %s" % (message, packet))
|
||||
self.message = message
|
||||
self.packet = packet
|
||||
|
||||
|
||||
class LoginError(GenericError):
|
||||
"""
|
||||
Raised when IS servers didn't respond correctly to our loging attempt
|
||||
"""
|
||||
def __init__(self, message):
|
||||
logger.error("%s: %s", self.__class__.__name__, message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class ConnectionError(GenericError):
|
||||
"""
|
||||
Riased when connection dies for some reason
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionDrop(ConnectionError):
|
||||
"""
|
||||
Raised when connetion drops or detected to be dead
|
||||
"""
|
||||
pass
|
||||
|
|
@ -0,0 +1 @@
|
|||
__version__ = "0.6.0"
|
||||
Loading…
Reference in New Issue