Ejemplo n.º 1
0
def parse_telemetry_config(body):
    parsed = {}

    match = re.findall(r"^(PARM|UNIT|EQNS|BITS)\.(.*)$", body)
    if match:
        logger.debug("Attempting to parse telemetry-message packet")
        form, body = match[0]

        parsed.update({'format': 'telemetry-message'})

        if form in ["PARM", "UNIT"]:
            vals = body.rstrip().split(',')[:13]

            for val in vals:
                if not re.match(r"^(.{1,20}|)$", val):
                    raise ParseError("incorrect format of %s (name too long?)" % form)

            defvals = [''] * 13
            defvals[:len(vals)] = vals

            parsed.update({
                't%s' % form: defvals
                })
        elif form == "EQNS":
            eqns = body.rstrip().split(',')[:15]
            teqns = [0, 1, 0] * 5

            for idx, val in enumerate(eqns):
                if not re.match(r"^([-]?\d*\.?\d+|)$", val):
                    raise ParseError("value at %d is not a number in %s" % (idx+1, form))
                else:
                    try:
                        val = int(val)
                    except:
                        val = float(val) if val != "" else 0

                    teqns[idx] = val

            # 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
                })
        elif form == "BITS":
            match = re.findall(r"^([01]{8}),(.{0,23})$", body.rstrip())
            if not match:
                raise ParseError("incorrect format of %s (title too long?)" % form)

            bits, title = match[0]

            parsed.update({
                't%s' % form: bits,
                'title': title.strip(' ')
                })

    return (body, parsed)
Ejemplo n.º 2
0
def parse_telemetry_config(body):
    parsed = {}

    match = re.findall(r"^(PARM|UNIT|EQNS|BITS)\.(.*)$", body)
    if match:
        logger.debug("Attempting to parse telemetry-message packet")
        form, body = match[0]

        parsed.update({'format': 'telemetry-message'})

        if form in ["PARM", "UNIT"]:
            vals = body.split(',')[:13]

            for val in vals:
                if not re.match(r"^(.{1,20}|)$", val):
                    raise ParseError("incorrect format of %s (name too long?)" % form)

            defvals = [''] * 13
            defvals[:len(vals)] = vals

            parsed.update({
                't%s' % form: defvals
                })
        elif form == "EQNS":
            eqns = body.split(',')[:15]
            teqns = [0, 1, 0] * 5

            for idx, val in enumerate(eqns):
                if not re.match(r"^([-]?\d*\.?\d+|)$", val):
                    raise ParseError("value at %d is not a number in %s" % (idx+1, form))
                else:
                    try:
                        val = int(val)
                    except:
                        val = float(val) if val != "" else 0

                    teqns[idx] = val

            # 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
                })
        elif form == "BITS":
            match = re.findall(r"^([01]{8}),(.{0,23})$", body)
            if not match:
                raise ParseError("incorrect format of %s (title too long?)" % form)

            bits, title = match[0]

            parsed.update({
                't%s' % form: bits,
                'title': title.strip(' ')
                })

    return (body, parsed)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
def parse_timestamp(body, packet_type=''):
    parsed = {}

    match = re.findall(r"^((\d{6})(.))$", body[0:7])
    if match:
        rawts, ts, form = match[0]
        utc = datetime.utcnow()

        timestamp = 0

        if packet_type == '>' and form != 'z':
            pass
        else:
            body = body[7:]

            try:
                # zulu hhmmss format
                if form == 'h':
                    timestamp = "%d%02d%02d%s" % (utc.year, utc.month, utc.day,
                                                  ts)
                # zulu ddhhmm format
                # '/' local ddhhmm format
                elif form in 'z/':
                    timestamp = "%d%02d%s%02d" % (utc.year, utc.month, ts, 0)
                else:
                    timestamp = "19700101000000"

                td = utc.strptime(timestamp, "%Y%m%d%H%M%S") - datetime(
                    1970, 1, 1)
                timestamp = int(
                    (td.microseconds +
                     (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6)
            except Exception as exp:
                timestamp = 0
                logger.debug(exp)

        parsed.update({
            'raw_timestamp': rawts,
            'timestamp': int(timestamp),
        })

    return (body, parsed)
Ejemplo n.º 6
0
def parse_timestamp(body, packet_type=''):
    parsed = {}

    match = re.findall(r"^((\d{6})(.))$", body[0:7])
    if match:
        rawts, ts, form = match[0]
        utc = datetime.utcnow()

        timestamp = 0

        if packet_type == '>' and form != 'z':
            pass
        else:
            body = body[7:]

            try:
                # zulu hhmmss format
                if form == 'h':
                    timestamp = "%d%02d%02d%s" % (utc.year, utc.month, utc.day, ts)
                # zulu ddhhmm format
                # '/' local ddhhmm format
                elif form in 'z/':
                    timestamp = "%d%02d%s%02d" % (utc.year, utc.month, ts, 0)
                else:
                    timestamp = "19700101000000"

                timestamp = utc.strptime(timestamp, "%Y%m%d%H%M%S")
                timestamp = time.mktime(timestamp.timetuple())
            except Exception as exp:
                timestamp = 0
                logger.debug(exp)

        parsed.update({
            'raw_timestamp': rawts,
            'timestamp': int(timestamp),
            })

    return (body, parsed)
Ejemplo n.º 7
0
def parse_position(packet_type, body):
    parsed = {}

    if packet_type not in '!=/@;':
        _, 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
        parse_comment(body, parsed)

    if packet_type == ';':
        parsed.update({
            'object_format': parsed['format'],
            'format': 'object',
            })

    return ('', parsed)
Ejemplo n.º 8
0
def parse_message(body):
    parsed = {}

    # 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-z0-9_ \-]{5}):(.{0,67})", body, re.I)
        if match:
            bid, identifier, text = match[0]
            identifier = identifier.rstrip(' ')

            mformat = 'bulletin' if identifier == "" else 'group-bulletin'

            parsed.update({
                'format': mformat,
                'message_text': text.strip(' '),
                'bid': bid,
                'identifier': identifier
                })
            break

        # try to match announcement
        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.strip(' '),
                'aid': aid,
                'identifier': identifier
                })
            break

        # validate addresse
        match = re.findall(r"^([a-zA-Z0-9_ \-]{9}):(.*)$", body)
        if not match:
            break

        addresse, body = match[0]

        parsed.update({'addresse': addresse.rstrip(' ')})

        # check if it's a telemetry configuration message
        body, result = parse_telemetry_config(body)
        if result:
            parsed.update(result)
            break

        # 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)
            if match:
                response, number = match[0]

                parsed.update({
                    'response': response,
                    'msgNo': number
                    })
            else:
                body = body[0:70]

                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({'message_text': body.strip(' ')})

        break

    return ('', parsed)
Ejemplo n.º 9
0
def parse_message(body):
    parsed = {}

    # 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-z0-9_ \-]{5}):(.{0,67})", body,
                           re.I)
        if match:
            bid, identifier, text = match[0]
            identifier = identifier.rstrip(' ')

            mformat = 'bulletin' if identifier == "" else 'group-bulletin'

            parsed.update({
                'format': mformat,
                'message_text': text.strip(' '),
                'bid': bid,
                'identifier': identifier
            })
            break

        # try to match announcement
        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.strip(' '),
                'aid': aid,
                'identifier': identifier
            })
            break

        # validate addresse
        match = re.findall(r"^([a-zA-Z0-9_ \-]{9}):(.*)$", body)
        if not match:
            break

        addresse, body = match[0]

        parsed.update({'addresse': addresse.rstrip(' ')})

        # check if it's a telemetry configuration message
        body, result = parse_telemetry_config(body)
        if result:
            parsed.update(result)
            break

        # regular message
        # ---------------------------
        logger.debug("Packet is just a regular message")
        parsed.update({'format': 'message'})

        # APRS supports two different message formats:
        # - the standard format which is described in 'aprs101.pdf':
        #   http://www.aprs.org/doc/APRS101.PDF
        # - an addendum from 1999 which introduces a new format:
        #   http://www.aprs.org/aprs11/replyacks.txt
        #
        # A message (ack/rej as well as a standard msg text body) can either have:
        # - no message number at all
        # - a message number in the old format (1..5 characters / digits)
        # - a message number in the new format (2 characters / digits) without trailing 'ack msg no'
        # - a message number in the new format with trailing 'free ack msg no' (2 characters / digits)

        # ack / rej
        # ---------------------------
        # NEW REPLAY-ACK
        # format: :AAAABBBBC:ackMM}AA
        match = re.findall(r"^(ack|rej)([A-Za-z0-9]{2})}([A-Za-z0-9]{2})?$",
                           body)
        if match:
            parsed['response'], parsed['msgNo'], ackMsgNo = match[0]
            if ackMsgNo:
                parsed['ackMsgNo'] = ackMsgNo
            break

        # ack/rej standard format as per aprs101.pdf chapter 14
        # format: :AAAABBBBC:ack12345
        match = re.findall(r"^(ack|rej)([A-Za-z0-9]{1,5})$", body)
        if match:
            parsed['response'], parsed['msgNo'] = match[0]
            break

        # regular message body parser
        # ---------------------------
        parsed['message_text'] = body.strip(' ')

        # check for ACKs
        # new message format: http://www.aprs.org/aprs11/replyacks.txt
        # format: :AAAABBBBC:text.....{MM}AA
        match = re.findall(r"{([A-Za-z0-9]{2})}([A-Za-z0-9]{2})?$", body)
        if match:
            msgNo, ackMsgNo = match[0]
            parsed['message_text'] = body[:len(body) - 4 -
                                          len(ackMsgNo)].strip(' ')
            parsed['msgNo'] = msgNo
            if ackMsgNo:
                parsed['ackMsgNo'] = ackMsgNo
            break

        # old message format - see aprs101.pdf.
        # search for: msgNo present
        match = re.findall(r"{([A-Za-z0-9]{1,5})$", body)
        if match:
            msgNo = match[0]
            parsed['message_text'] = body[:len(body) - 1 -
                                          len(msgNo)].strip(' ')
            parsed['msgNo'] = msgNo
            break

        # break free from the eternal 'while'
        break

    return ('', parsed)
Ejemplo n.º 10
0
def parse_position(packet_type, body):
    parsed = {}

    if packet_type not in '!=/@;':
        _, 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")

    # 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
        parse_comment(body, parsed)

    if packet_type == ';':
        parsed.update({
            'object_format': parsed['format'],
            'format': 'object',
            })

    return ('', parsed)