import logging import re from aprslib import base91 from aprslib.exceptions import ParseError from aprslib.parsing import logger from aprslib.parsing.common import _parse_timestamp, _parse_comment from aprslib.parsing.weather import _parse_weather_data __all__ = [ '_parse_position', '_parse_compressed', '_parse_normal', ] def _parse_position(packet_type, body): parsed = {} if packet_type not in '!=/@;': prefix, body = body.split('!', 1) packet_type = '!' if packet_type == ';': logger.debug("Attempting to parse object report format") match = re.findall(r"^([ -~]{9})(\*|_)", body) if match: name, flag = match[0] parsed.update({ 'object_name': name, 'alive': flag == '*', }) body = body[10:] else: raise ParseError("invalid format") else: parsed.update({"messagecapable": packet_type in '@='}) # decode timestamp if packet_type in "/@;": body, result = _parse_timestamp(body, packet_type) parsed.update(result) if len(body) == 0 and 'timestamp' in parsed: raise ParseError("invalid position report format", packet) # decode body body, result = _parse_compressed(body) parsed.update(result) if len(result) > 0: logger.debug("Parsed as compressed position report") else: body, result = _parse_normal(body) parsed.update(result) if len(result) > 0: logger.debug("Parsed as normal position report") else: raise ParseError("invalid format") # check comment for weather information # Page 62 of the spec if parsed['symbol'] == '_': logger.debug("Attempting to parse weather report from comment") body, result = _parse_weather_data(body) parsed.update({ 'comment': body.strip(' '), 'weather': result, }) else: # decode comment body, result = _parse_comment(body) parsed.update(result) if packet_type == ';': parsed.update({ 'object_format': parsed['format'], 'format': 'object', }) return ('', parsed) def _parse_compressed(body): parsed = {} if re.match(r"^[\/\\A-Za-j][!-|]{8}[!-{}][ -|]{3}", body): logger.debug("Attempting to parse as compressed position report") if len(body) < 13: raise ParseError("Invalid compressed packet (less than 13 characters)") parsed.update({'format': 'compressed'}) compressed = body[:13] body = body[13:] symbol_table = compressed[0] symbol = compressed[9] try: latitude = 90 - (base91.to_decimal(compressed[1:5]) / 380926.0) longitude = -180 + (base91.to_decimal(compressed[5:9]) / 190463.0) except ValueError: raise ParseError("invalid characters in latitude/longitude encoding") # parse csT # converts the relevant characters from base91 c1, s1, ctype = [ord(x) - 33 for x in compressed[10:13]] if c1 == -1: parsed.update({'gpsfixstatus': 1 if ctype & 0x20 == 0x20 else 0}) if -1 in [c1, s1]: pass 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 elif c1 == 90: parsed.update({'radiorange': (2 * 1.08 ** s1) * 1.609344}) # mul = convert mph to kmh parsed.update({ 'symbol': symbol, 'symbol_table': symbol_table, 'latitude': latitude, 'longitude': longitude, }) return (body, parsed) def _parse_normal(body): parsed = {} match = re.findall(r"^(\d{2})([0-9 ]{2}\.[0-9 ]{2})([NnSs])([\/\\0-9A-Z])" r"(\d{3})([0-9 ]{2}\.[0-9 ]{2})([EeWw])([\x21-\x7e])(.*)$", body) if match: parsed.update({'format': 'uncompressed'}) ( lat_deg, lat_min, lat_dir, symbol_table, lon_deg, lon_min, lon_dir, symbol, body ) = match[0] # position ambiguity posambiguity = lat_min.count(' ') if posambiguity != lon_min.count(' '): raise ParseError("latitude and longitude ambiguity mismatch") parsed.update({'posambiguity': posambiguity}) # we center the position inside the ambiguity box if posambiguity >= 4: lat_min = "30" lon_min = "30" else: lat_min = lat_min.replace(' ', '5', 1) lon_min = lon_min.replace(' ', '5', 1) # validate longitude and latitude if int(lat_deg) > 89 or int(lat_deg) < 0: raise ParseError("latitude is out of range (0-90 degrees)") if int(lon_deg) > 179 or int(lon_deg) < 0: raise ParseError("longitutde is out of range (0-180 degrees)") """ f float(lat_min) >= 60: raise ParseError("latitude minutes are out of range (0-60)") if float(lon_min) >= 60: raise ParseError("longitude minutes are out of range (0-60)") 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 *= -1 if lat_dir in 'Ss' else 1 longitude *= -1 if lon_dir in 'Ww' else 1 parsed.update({ 'symbol': symbol, 'symbol_table': symbol_table, 'latitude': latitude, 'longitude': longitude, }) return (body, parsed)