From 33eb7a457ef611569fd7cf2b0deedc5b84705bbe Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Fri, 28 Nov 2014 09:47:43 +0000 Subject: [PATCH] clean up and updated comments --- aprs.py | 373 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 244 insertions(+), 129 deletions(-) diff --git a/aprs.py b/aprs.py index ff0d0a3..8e9a1b0 100644 --- a/aprs.py +++ b/aprs.py @@ -1,4 +1,12 @@ -# Copyright 2013-2014 (C) Rossen Georgiev +""" +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 @@ -8,33 +16,45 @@ import math import logging import sys +__version__ = "0.5.1" +__author__ = "Rossen Georgiev" +__date__ = str(datetime.date.today()) + +__all__ = ['IS', 'base91', 'parse'] logger = logging.getLogger(__name__) logging.addLevelName(11, "ParseError") -__all__ = ['IS', 'GenericError', 'ParseError', 'UnknownFormat', - 'LoginError', 'ConnectionError', 'ConnectionDrop', 'parse'] - -# IS class is used to connect to aprs-is servers and listen to the feed 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): """ - APRS module that listens and parses sentences passed by aprs.net servers + 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.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 + 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: @@ -42,10 +62,13 @@ class IS(object): return self.set_filter("b/%s" % "/".join(callsigns)) - def set_filter(self, filter): - self.filter = filter + 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) + logger.info("Setting filter to: %s", self.filter) if self._connected: self.sock.sendall("#filter %s\r\n" % self.filter) @@ -59,7 +82,7 @@ class IS(object): self.callsign = callsign self.passwd = passwd - def set_server(self, host, port=14850): + def set_server(self, host, port): """ Set server ip/host and port to use """ @@ -73,13 +96,13 @@ class IS(object): if not self._connected: while True: try: - logger.info("Attempting connection to %s:%s" % (self.server[0], self.server[1])) + 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) + logger.info("Filter set to: %s", self.filter) if self.passwd == "-1": logger.info("Login successful (receive only)") @@ -115,7 +138,7 @@ class IS(object): immortal: When true, consumer will try to reconnect and stop propagation of Parse exceptions. if false (default), consumer will return - raw: when true, raw aprs sentence is passed to the callback, otherwise parsed data as dict + raw: when true, raw packet is passed to callback, otherwise the result from aprs.parse() """ if not self._connected: @@ -144,11 +167,7 @@ class IS(object): except StopIteration: break except: - #if not immortal: - # raise - #logger.exception(e) - #continue - logger.error("APRS Packet: " + line) + logger.error("APRS Packet: %s", line) raise if not blocking: @@ -156,18 +175,20 @@ class IS(object): def _connect(self): """ - Attemps to open a connection to the server, retries if it fails + Attemps to open a connection to the server """ try: - self.sock = socket.create_connection(self.server, 15) # 15 seconds connection timeout + # 15 seconds connection timeout + self.sock = socket.create_connection(self.server, 15) - self.sock.settimeout(5) # 5 second timeout to receive server banner + # 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']: + 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) @@ -175,7 +196,6 @@ class IS(object): if self.sock.recv(512)[0] != "#": raise ConnectionError("invalid banner from server") - except Exception, e: self.close() @@ -190,7 +210,11 @@ class IS(object): """ 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) + 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) @@ -198,7 +222,7 @@ class IS(object): test = self.sock.recv(len(login_str) + 100) self.sock.setblocking(True) - (x, x, callsign, status, x) = test.split(' ',4) + (x, x, callsign, status, x) = test.split(' ', 4) if callsign != self.callsign: raise LoginError("login callsign does not match") @@ -234,7 +258,7 @@ class IS(object): if "Resource temporarily unavailable" in e: if not blocking: if len(self.buf) == 0: - break; + break except Exception: raise @@ -277,33 +301,41 @@ MTYPE_TABLE_CUSTOM = { "000": "Emergency", } -# routine for decoding base91 values of arbitrary length def base91(text): + """ + Takes a base91 char string and returns decimal + """ + decimal = 0 - length = len(text); + length = len(text) for i in range(length): decimal += (ord(text[i]) - 33) * (91.0 ** (length-1-i)) return decimal if text != '' else '' -# parse a single packet -# throws expcetions def parse(raw_sentence): """ - Parses position sentences and returns a dict with the useful data - All attributes are in meteric units + Parses an APRS packet and returns a dict with decoded data + + - All attributes are in meteric units + + Supports: + * normal/compressed/mic-e position reports + - including comment extentions for altitude and telemetry + * messages including bulletins, announcements and telemetry + * status message """ raw_sentence = raw_sentence.rstrip("\r\n") - logger.debug("Parsing: %s" % raw_sentence) + logger.debug("Parsing: %s", raw_sentence) if len(raw_sentence) == 0: raise ParseError("packet is empty", raw_sentence) try: - (header, body) = raw_sentence.split(':',1) + (header, body) = raw_sentence.split(':', 1) except: raise ParseError("packet has no body", raw_sentence) @@ -314,7 +346,7 @@ def parse(raw_sentence): raise ParseError("packet header contains non-ascii characters ", raw_sentence) try: - (fromcall, path) = header.split('>',1) + (fromcall, path) = header.split('>', 1) except: raise ParseError("invalid packet header", raw_sentence) @@ -325,19 +357,19 @@ def parse(raw_sentence): if len(path) < 1 or len(path[0]) == 0: raise ParseError("no tocallsign", raw_sentence) - tocall = path[0] + tocall = path[0] path = path[1:] parsed = { - 'raw': raw_sentence, - 'from': fromcall, - 'to': tocall, - 'path': path, - } + 'raw': raw_sentence, + 'from': fromcall, + 'to': tocall, + 'path': path, + } try: viacall = path[-1] if re.match(r"^qA[CXUoOSrRRZI]$", path[-2]) else "" - parsed.update({ 'via': viacall }) + parsed.update({'via': viacall}) except: pass @@ -355,27 +387,39 @@ def parse(raw_sentence): # try to parse timestamp match = re.findall(r"^((\d{6})(.))$", body[0:7]) if match: - rawts,ts,form = match[0] + rawts, ts, form = match[0] utc = datetime.datetime.utcnow() if packet_type == '>' and form != 'z': raise ParseError("Time format for status reports should be zulu", raw_sentence) - parsed.update({ 'raw_timestamp': rawts }) + parsed.update({'raw_timestamp': rawts}) try: - if form == 'h': # zulu hhmmss format - timestamp = time.mktime(utc.strptime("%s %s %s %s" % (utc.year, utc.month, utc.day, ts), "%Y %m %d %H%M%S").timetuple()) - elif form == 'z': # zulu ddhhss format - timestamp = time.mktime(utc.strptime("%s %s %s" % (utc.year, utc.month, ts), "%Y %m %d%M%S").timetuple()) - elif form == '/': # '/' local ddhhss format - timestamp = time.mktime(utc.strptime("%s %s %s" % (utc.year, utc.month, ts), "%Y %m %d%M%S").timetuple()) + if form in "hz/": + if form == 'h': # zulu hhmmss format + timestamp = utc.strptime( + "%s %s %s %s" % (utc.year, utc.month, utc.day, ts), + "%Y %m %d %H%M%S" + ) + elif form == 'z': # zulu ddhhss format + timestamp = utc.strptime( + "%s %s %s" % (utc.year, utc.month, ts), + "%Y %m %d%M%S" + ) + elif form == '/': # '/' local ddhhss format + timestamp = utc.strptime( + "%s %s %s" % (utc.year, utc.month, ts), + "%Y %m %d%M%S" + ) + + timestamp = time.mktime(timestamp.timetuple()) else: timestamp = 0 except: timestamp = 0 - parsed.update({ 'timestamp': int(timestamp) }) + parsed.update({'timestamp': int(timestamp)}) # remove datetime from the body for further parsing body = body[7:] @@ -403,20 +447,23 @@ def parse(raw_sentence): raise ParseError("invalid data format", raw_sentence) # get symbol table and symbol - parsed.update({ 'symbol': body[6], 'symbol_table': body[7] }) + parsed.update({ + 'symbol': body[6], + 'symbol_table': body[7] + }) # parse latitude # the routine translates each characters into a lat digit as described in # 'Mic-E Destination Address Field Encoding' table tmpdstcall = "" for i in dstcall: - if i in "KLZ": # spaces + if i in "KLZ": # spaces tmpdstcall += " " - elif ord(i) > 76: # P-Y + elif ord(i) > 76: # P-Y tmpdstcall += chr(ord(i) - 32) - elif ord(i) > 57: # A-J + elif ord(i) > 57: # A-J tmpdstcall += chr(ord(i) - 16) - else: # 0-9 + else: # 0-9 tmpdstcall += i # determine position ambiguity @@ -425,7 +472,9 @@ def parse(raw_sentence): raise ParseError("invalid latitude ambiguity", raw_sentence) posambiguity = len(match[0]) - parsed.update({ 'posambiguity': posambiguity }) + parsed.update({ + 'posambiguity': posambiguity + }) # adjust the coordinates be in center of ambiguity box tmpdstcall = list(tmpdstcall) @@ -437,7 +486,7 @@ def parse(raw_sentence): tmpdstcall = "".join(tmpdstcall) - latminutes = float( ("%s.%s" % (tmpdstcall[2:4], tmpdstcall[4:6])).replace(" ", "0") ) + latminutes = float(("%s.%s" % (tmpdstcall[2:4], tmpdstcall[4:6])).replace(" ", "0")) if latminutes >= 60: raise ParseError("Latitude minutes >= 60", raw_sentence) @@ -447,27 +496,35 @@ def parse(raw_sentence): # determine the sign N/S latitude = -latitude if ord(dstcall[3]) <= 0x4c else latitude - parsed.update({ 'latitude': latitude }) + parsed.update({ + 'latitude': latitude + }) # parse message bits - mbits = re.sub(r"[0-9L]","0", dstcall[0:3]) - mbits = re.sub(r"[P-Z]","1", mbits) - mbits = re.sub(r"[A-K]","2", mbits) + mbits = re.sub(r"[0-9L]", "0", dstcall[0:3]) + mbits = re.sub(r"[P-Z]", "1", mbits) + mbits = re.sub(r"[A-K]", "2", mbits) - parsed.update({ 'mbits': mbits }) + parsed.update({ + 'mbits': mbits + }) # resolve message type if mbits.find("2") > -1: - parsed.update({ 'mtype': MTYPE_TABLE_CUSTOM[mbits.replace("2","1")] }) + parsed.update({ + 'mtype': MTYPE_TABLE_CUSTOM[mbits.replace("2", "1")] + }) else: - parsed.update({ 'mtype': MTYPE_TABLE_STD[mbits] }) + parsed.update({ + 'mtype': MTYPE_TABLE_STD[mbits] + }) # parse longitude - longitude = ord(body[0]) - 28 # decimal part of longitude - longitude += 100 if ord(dstcall[4]) >= 0x50 else 0 # apply lng offset + longitude = ord(body[0]) - 28 # decimal part of longitude + longitude += 100 if ord(dstcall[4]) >= 0x50 else 0 # apply lng offset longitude += -80 if longitude >= 180 and longitude <= 189 else 0 longitude += -190 if longitude >= 190 and longitude <= 199 else 0 @@ -496,7 +553,9 @@ def parse(raw_sentence): # apply E/W sign longitude = 0 - longitude if ord(dstcall[5]) >= 0x50 else longitude - parsed.update({ 'longitude': longitude }) + parsed.update({ + 'longitude': longitude + }) # parse speed and course speed = (ord(body[3]) - 28) * 10 @@ -509,8 +568,11 @@ def parse(raw_sentence): speed += -800 if speed >= 800 else 0 course += -400 if course >= 400 else 0 - speed *= 1.852 # knots * 1.852 = kmph - parsed.update({'speed': speed, 'course': course }) + speed *= 1.852 # knots * 1.852 = kmph + parsed.update({ + 'speed': speed, + 'course': course + }) # the rest of the packet can contain telemetry and comment @@ -535,7 +597,7 @@ def parse(raw_sentence): # check for optional altitude match = re.findall(r"^(.*)([!-{]{3})\}(.*)$", body) if match: - body,altitude,extra = match[0] + body, altitude, extra = match[0] altitude = base91(altitude) - 10000 parsed.update({'altitude': altitude}) @@ -552,7 +614,10 @@ def parse(raw_sentence): elif packet_type == '>': logger.debug("Packet is just a status message") - parsed.update({'format': 'status', 'comment': body }) + parsed.update({ + 'format': 'status', + 'comment': body + }) # MESSAGE PACKET # @@ -571,39 +636,49 @@ def parse(raw_sentence): # the while loop is used to easily break out once a match is found while True: # try to match bulletin - match = re.findall(r"^BLN([0-9])([a-zA-Z0-9 \-]{5}):(.{0,67})", body) + match = re.findall(r"^BLN([0-9])([a-zA-Z0-9 \-]{5}):(.{0,67})", body) if match: bid, identifier, text = match[0] identifier = identifier.rstrip(' ') mformat = 'bulletin' if identifier == "" else 'group-bulletin' - parsed.update({'format':mformat, 'message_text':text, 'bid':bid, 'identifier': identifier}) + parsed.update({ + 'format': mformat, + 'message_text': text, + 'bid': bid, + 'identifier': identifier + }) break # try to match announcement - match = re.findall(r"^BLN([A-Z])([a-zA-Z0-9 \-]{5}):(.{0,67})", body) + match = re.findall(r"^BLN([A-Z])([a-zA-Z0-9 \-]{5}):(.{0,67})", body) if match: aid, identifier, text = match[0] identifier = identifier.rstrip(' ') - parsed.update({'format':'announcement', 'message_text':text, 'aid':aid, 'identifier': identifier}) + parsed.update({ + 'format': 'announcement', + 'message_text': text, + 'aid': aid, + 'identifier': identifier + }) break # validate addresse - match = re.findall(r"^([a-zA-Z0-9 \-]{9}):(.*)$", body) + match = re.findall(r"^([a-zA-Z0-9 \-]{9}):(.*)$", body) if not match: raise ParseError("invalid addresse in message", raw_sentence) - addresse,body = match[0] + addresse, body = match[0] parsed.update({'addresse': addresse.rstrip(' ')}) # check if it's a telemetry configuration message - match = re.findall(r"^(PARM|UNIT|EQNS|BITS)\.(.*)$", body) + match = re.findall(r"^(PARM|UNIT|EQNS|BITS)\.(.*)$", body) if match: logger.debug("Attempting to parse telemetry-message packet") - form,body = match[0] + form, body = match[0] parsed.update({'format': 'telemetry-message'}) @@ -617,14 +692,16 @@ def parse(raw_sentence): defvals = [''] * 13 defvals[:len(vals)] = vals - parsed.update({ 't%s' % form : defvals }) + parsed.update({ + 't%s' % form: defvals + }) elif form == "EQNS": eqns = body.split(',')[:15] - teqns = [0,1,0] * 5 + teqns = [0, 1, 0] * 5 - for idx,val in zip(range(len(eqns)), eqns): + for idx, val in enumerate(eqns): if not re.match("^([-]?\d*\.?\d+|)$", val): - raise ParseError("value at %d is not a number in %s" % (idx+1,form), raw_sentence) + raise ParseError("value at %d is not a number in %s" % (idx+1, form), raw_sentence) else: try: val = int(val) @@ -636,7 +713,9 @@ def parse(raw_sentence): # group values in 5 list of 3 teqns = [teqns[i*3:(i+1)*3] for i in range(5)] - parsed.update({ 't%s' % form : teqns }) + parsed.update({ + 't%s' % form: teqns + }) elif form == "BITS": match = re.findall(r"^([01]{8}),(.{0,23})$", body) if not match: @@ -644,29 +723,35 @@ def parse(raw_sentence): bits, title = match[0] - parsed.update({ 't%s' % form : bits, 'title': title }) + parsed.update({ + 't%s' % form: bits, + 'title': title + }) # regular message else: logger.debug("Packet is just a regular message") parsed.update({'format': 'message'}) - match = re.findall(r"^(ack|rej)\{([0-9]{1,5})$", body) + match = re.findall(r"^(ack|rej)\{([0-9]{1,5})$", body) if match: response, number = match[0] - parsed.update({'response': response, 'msgNo': number }) + parsed.update({ + 'response': response, + 'msgNo': number + }) else: body = body[0:70] - match = re.findall(r"\{([0-9]{1,5})$", body) + match = re.findall(r"\{([0-9]{1,5})$", body) if match: msgid = match[0] body = body[:len(body) - 1 - len(msgid)] - parsed.update( {'msgNo': int(msgid)} ) + parsed.update({'msgNo': int(msgid)}) - parsed.update({'message_text': body }) + parsed.update({'message_text': body}) break @@ -680,7 +765,7 @@ def parse(raw_sentence): # ./YYYYXXXX$csT Compressed (Used in any !=/@ format) elif packet_type in '!=/@': - parsed.update({ "messagecapable": packet_type in '@=' }) + parsed.update({"messagecapable": packet_type in '@='}) if len(body) == 0 and 'timestamp' in parsed: raise ParseError("invalid position report format", raw_sentence) @@ -692,7 +777,7 @@ def parse(raw_sentence): if len(body) < 13: raise ParseError("Invalid compressed packet (less than 13 characters)", raw_sentence) - parsed.update({ 'format': 'compressed' }) + parsed.update({'format': 'compressed'}) packet = body[:13] extra = body[13:] @@ -706,7 +791,7 @@ def parse(raw_sentence): # parse csT # converts the relevant characters from base91 - c1,s1,ctype = [ord(x) - 33 for x in packet[10:13]] + c1, s1, ctype = [ord(x) - 33 for x in packet[10:13]] if c1 == -1: parsed.update({'gpsfixstatus': 1 if ctype & 0x20 == 0x20 else 0}) @@ -716,28 +801,28 @@ def parse(raw_sentence): elif ctype & 0x18 == 0x10: parsed.update({'altitude': (1.002 ** (c1 * 91 + s1)) * 0.3048}) elif c1 >= 0 and c1 <= 89: - parsed.update({'course': 360 if c1 == 0 else c1 * 4 }) - parsed.update({'speed': (1.08 ** s1 - 1) * 1.852 }) # mul = convert knts to kmh + parsed.update({'course': 360 if c1 == 0 else c1 * 4}) + parsed.update({'speed': (1.08 ** s1 - 1) * 1.852}) # mul = convert knts to kmh elif c1 == 90: - parsed.update({'radiorange': (2 * 1.08 ** s1) * 1.609344 }) # mul = convert mph to kmh + parsed.update({'radiorange': (2 * 1.08 ** s1) * 1.609344}) # mul = convert mph to kmh # normal position report else: logger.debug("Attempting to parse as normal position report") - parsed.update({ 'format': 'uncompressed' }) + parsed.update({'format': 'uncompressed'}) try: ( - lat_deg, - lat_min, - lat_dir, - symbol_table, - lon_deg, - lon_min, - lon_dir, - symbol, - extra + lat_deg, + lat_min, + lat_dir, + symbol_table, + lon_deg, + lon_min, + lon_dir, + symbol, + extra ) = re.match(r"^(\d{2})([0-9 ]{2}\.[0-9 ]{2})([NnSs])([\/\\0-9A-Z])(\d{3})([0-9 ]{2}\.[0-9 ]{2})([EeWw])([\x21-\x7e])(.*)$", body).groups() # TODO: position ambiguity @@ -748,19 +833,22 @@ def parse(raw_sentence): raise ParseError("latitude is out of range (0-90 degrees)", raw_sentence) if int(lon_deg) > 179 or int(lon_deg) < 0: raise ParseError("longitutde is out of range (0-180 degrees)", raw_sentence) - #if float(lat_min) >= 60: - # raise ParseError("latitude minutes are out of range (0-60)", raw_sentence) - #if float(lon_min) >= 60: - # raise ParseError("longitude minutes are out of range (0-60)", raw_sentence) - # the above is commented out intentionally - # apperantly aprs.fi doesn't bound check minutes - # and there are actual packets that have >60min - # i don't even know why that's the case + """ + f float(lat_min) >= 60: + raise ParseError("latitude minutes are out of range (0-60)", raw_sentence) + if float(lon_min) >= 60: + raise ParseError("longitude minutes are out of range (0-60)", raw_sentence) + + the above is commented out intentionally + apperantly aprs.fi doesn't bound check minutes + and there are actual packets that have >60min + i don't even know why that's the case + """ # convert coordinates from DDMM.MM to decimal - latitude = int(lat_deg) + ( float(lat_min) / 60.0 ) - longitude = int(lon_deg) + ( float(lon_min) / 60.0 ) + latitude = int(lat_deg) + (float(lat_min) / 60.0) + longitude = int(lon_deg) + (float(lon_min) / 60.0) latitude *= -1 if lat_dir in 'Ss' else 1 longitude *= -1 if lon_dir in 'Ww' else 1 @@ -771,7 +859,10 @@ def parse(raw_sentence): # include symbol in the result - parsed.update({ 'symbol': symbol, 'symbol_table': symbol_table }) + parsed.update({ + 'symbol': symbol, + 'symbol_table': symbol_table + }) # include longitude and latitude in the result @@ -781,14 +872,14 @@ def parse(raw_sentence): # try CRS/SPD/ - match = re.findall(r"^([0-9]{3})/([0-9]{3})", extra) + match = re.findall(r"^([0-9]{3})/([0-9]{3})", extra) if match: cse, spd = match[0] extra = extra[7:] - parsed.update({'course': int(cse), 'speed': int(spd)*1.852}) # knots to kms + parsed.update({'course': int(cse), 'speed': int(spd)*1.852}) # knots to kms # try BRG/NRQ/ - match = re.findall(r"^([0-9]{3})/([0-9]{3})", extra) + match = re.findall(r"^([0-9]{3})/([0-9]{3})", extra) if match: brg, nrq = match[0] extra = extra[7:] @@ -800,15 +891,15 @@ def parse(raw_sentence): match = re.findall(r"^(.*?)/A=(\-\d{5}|\d{6})(.*)$", extra) if match: - extra,altitude,post = match[0] - extra += post # glue front and back part together, DONT ASK + extra, altitude, post = match[0] + extra += post # glue front and back part together, DONT ASK - parsed.update({ 'altitude': int(altitude)*0.3048 }) + parsed.update({'altitude': int(altitude)*0.3048}) # try parse comment telemetry match = re.findall(r"^(.*?)\|([!-{]{2,14})\|(.*)$", extra) if match and len(match[0][2]) % 2 == 0: - extra,telemetry,post = match[0] + extra, telemetry, post = match[0] extra += post temp = [] @@ -839,32 +930,56 @@ def parse(raw_sentence): # 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)) + 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)) + 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