Syncleus/apex

View on GitHub
src/apex/aprs/util.py

Summary

Maintainability
D
2 days
Test Coverage
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Utilities for the APRS Python Module."""

# These imports are for python3 compatibility inside python2
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import logging

import apex.aprs.constants
import apex.aprs.decimal_degrees
import apex.kiss.constants

__author__ = 'Jeffrey Phillips Freeman (WI2ARD)'
__maintainer__ = 'Jeffrey Phillips Freeman (WI2ARD)'
__email__ = 'jeffrey.freeman@syncleus.com'
__license__ = 'Apache License, Version 2.0'
__copyright__ = 'Copyright 2016, Syncleus, Inc. and contributors'
__credits__ = []


def dec2dm_lat(dec):
    """Converts DecDeg to APRS Coord format.
    See: http://ember2ash.com/lat.htm

    Source: http://stackoverflow.com/questions/2056750

    Example:
        >>> test_lat = 37.7418096
        >>> aprs_lat = dec2dm_lat(test_lat)
        >>> aprs_lat
        '3744.51N'
    """
    dec_min = apex.aprs.decimal_degrees.decimal2dm(dec)

    deg = dec_min[0]
    abs_deg = abs(deg)

    if not deg == abs_deg:
        suffix = 'S'
    else:
        suffix = 'N'

    return''.join([str(abs_deg), '%.2f' % dec_min[1], suffix])


def dec2dm_lng(dec):
    """Converts DecDeg to APRS Coord format.
    See: http://ember2ash.com/lat.htm

    Example:
        >>> test_lng = -122.38833
        >>> aprs_lng = dec2dm_lng(test_lng)
        >>> aprs_lng
        '12223.30W'
    """
    dec_min = apex.aprs.decimal_degrees.decimal2dm(dec)

    deg = dec_min[0]
    abs_deg = abs(deg)

    if not deg == abs_deg:
        suffix = 'W'
    else:
        suffix = 'E'

    return ''.join([str(abs_deg), '%.2f' % dec_min[1], suffix])


def decode_frame(frame):
    """
    Breaks an ASCII APRS Frame down to it's constituent parts.

    :param frame: ASCII APRS Frame.
    :type frame: str

    :returns: Dictionary of APRS Frame parts: source, destination, path, text.
    :rtype: dict
    """
    logging.debug('frame=%s', frame)
    decoded_frame = {}
    frame_so_far = ''

    for char in frame:
        if '>' in char and 'source' not in decoded_frame:
            decoded_frame['source'] = frame_so_far
            frame_so_far = ''
        elif ':' in char and 'path' not in decoded_frame:
            decoded_frame['path'] = frame_so_far
            frame_so_far = ''
        else:
            frame_so_far = ''.join([frame_so_far, char])

    decoded_frame['text'] = frame_so_far
    decoded_frame['destination'] = decoded_frame['path'].split(',')[0]

    return decoded_frame


def encode_frame(frame):
    """
    Formats APRS frame-as-dict into APRS frame-as-string.

    :param frame: APRS frame-as-dict
    :type frame: dict

    :return: APRS frame-as-string.
    :rtype: str
    """
    formatted_frame = '>'.join([frame['source'], frame['destination']])
    if frame['path']:
        formatted_frame = ','.join([formatted_frame, format_path(frame['path'])])
    formatted_frame += ':'
    formatted_frame += frame['text']
    return formatted_frame


def format_path(path_list):
    """
    Formats path from raw APRS KISS frame.

    :param path_list: List of path elements.
    :type path_list: list

    :return: Formatted APRS path.
    :rtype: str
    """
    return ','.join(path_list)


def valid_callsign(callsign):
    """
    Validates callsign.

    :param callsign: Callsign to validate.
    :type callsign: str

    :returns: True if valid, False otherwise.
    :rtype: bool
    """
    logging.debug('callsign=%s', callsign)

    if '-' in callsign:
        if not callsign.count('-') == 1:
            return False
        else:
            callsign, ssid = callsign.split('-')
    else:
        ssid = 0

    logging.debug('callsign=%s ssid=%s', callsign, ssid)

    if (len(callsign) < 2 or len(callsign) > 6 or len(str(ssid)) < 1 or
            len(str(ssid)) > 2):
        return False

    for char in callsign:
        if not (char.isalpha() or char.isdigit()):
            return False

    if not str(ssid).isdigit():
        return False

    if int(ssid) < 0 or int(ssid) > 15:
        return False

    return True


def run_doctest():  # pragma: no cover
    """Runs doctests for this module."""
    import doctest
    import apex.aprs.util  # pylint: disable=W0406,W0621
    return doctest.testmod(apex.aprs.util)


def hash_frame(frame):
    """
    Produces an integer value that acts as a hash for the frame
    :param frame: A frame packet
    :type frame: dict
    :return: an integer representing the hash
    """
    hashing = 0
    index = 0
    frame_string_prefix = frame['source'] + '>' + frame['destination'] + ':'
    for frame_chr in frame_string_prefix:
        hashing = ord(frame_chr) << (8*(index % 4)) ^ hashing
        index += 1
    for char in frame['text']:
        hashing = ord(char) << (8*(index % 4)) ^ hashing
        index += 1
    return hashing


if __name__ == '__main__':
    run_doctest()  # pragma: no cover