aprs-python/aprslib/parsing/common.py

180 lines
4.8 KiB
Python

import re
import time
from datetime import datetime
from aprslib import base91
from aprslib.exceptions import ParseError
from aprslib.parsing import logger
from aprslib.parsing.telemetry import _parse_comment_telemetry
__all__ = [
'_validate_callsign',
'_parse_header',
'_parse_timestamp',
'_parse_comment',
]
def _validate_callsign(callsign, prefix=""):
prefix = '%s: ' % prefix if bool(prefix) else ''
match = re.findall(r"^([A-Z0-9]{1,6})(-(\d{1,2}))?$", callsign)
if not match:
raise ParseError("%sinvalid callsign" % prefix)
callsign, x, ssid = match[0]
if bool(ssid) and int(ssid) > 15:
raise ParseError("%sssid not in 0-15 range" % prefix)
def _parse_header(head):
"""
Parses the header part of packet
Returns a dict
"""
# CALL1>CALL2,CALL3,CALL4,CALL5:
# |from-|--to-|------path-------|
#
try:
(fromcall, path) = head.split('>', 1)
except:
raise ParseError("invalid packet header")
# looking at aprs.fi, the rules for from/src callsign
# are a lot looser, causing a lot of packets to fail
# this check.
#
# if len(fromcall) == 0:
# raise ParseError("no fromcallsign in header")
# _validate_callsign(fromcall, "fromcallsign")
if (not 1 <= len(fromcall) <= 9 or
not re.findall(r"^[a-z0-9]{0,9}(\-[a-z0-9]{1,8})?$", fromcall, re.I)):
raise ParseError("fromcallsign is invalid")
path = path.split(',')
if len(path) < 1 or len(path[0]) == 0:
raise ParseError("no tocallsign in header")
tocall = path[0]
path = path[1:]
_validate_callsign(tocall, "tocallsign")
for digi in path:
if not re.findall(r"^[A-Z0-9\-]{1,9}\*?$", digi, re.I):
raise ParseError("invalid callsign in path")
parsed = {
'from': fromcall,
'to': tocall,
'path': path,
}
# viacall is the callsign that gated the packet to the net
# it's located behind the q-contructed
#
# CALL1>CALL2,CALL3,qAR,CALL5:
# .....................|-via-|
#
viacall = ""
if len(path) >= 2 and re.match(r"^q..$", path[-2]):
viacall = path[-1]
parsed.update({'via': viacall})
return parsed
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())
parsed.update({'raw_timestamp': rawts})
except Exception as exp:
timestamp = 0
logger.debug(exp)
parsed.update({'timestamp': int(timestamp)})
return (body, parsed)
def _parse_comment(body):
parsed = {}
# attempt to parse remaining part of the packet (comment field)
# try CRS/SPD
match = re.findall(r"^([0-9]{3})/([0-9]{3})", body)
if match:
cse, spd = match[0]
body = body[7:]
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})", body)
if match:
brg, nrq = match[0]
body = body[7:]
parsed.update({'bearing': int(brg), 'nrq': int(nrq)})
else:
match = re.findall(r"^(PHG(\d[\x30-\x7e]\d\d[0-9A-Z]?))\/", body)
if match:
ext, phg = match[0]
body = body[len(ext):]
parsed.update({'phg': phg})
else:
match = re.findall(r"^(RNG(\d{4}))\/", body)
if match:
ext, rng = match[0]
body = body[len(ext):]
parsed.update({'rng': int(rng) * 1.609344}) # miles to km
# try find altitude in comment /A=dddddd
match = re.findall(r"^(.*?)/A=(\-\d{5}|\d{6})(.*)$", body)
if match:
body, altitude, post = match[0]
body += post # glue front and back part together, DONT ASK
parsed.update({'altitude': int(altitude)*0.3048})
body, telemetry = _parse_comment_telemetry(body)
parsed.update(telemetry)
if len(body) > 0 and body[0] == "/":
body = body[1:]
parsed.update({'comment': body.strip(' ')})
return ('', parsed)