Compare commits
41 Commits
actions-te
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
cd3724696d | |
|
|
32c7577fb8 | |
|
|
c2a0f18ce0 | |
|
|
ef88e3493a | |
|
|
5347b43106 | |
|
|
2a27dc4416 | |
|
|
2b139d1857 | |
|
|
6b80e9e3ef | |
|
|
5e79c81035 | |
|
|
c9f4b4f779 | |
|
|
1b7da6566c | |
|
|
8674bbd143 | |
|
|
ecb9ce71fb | |
|
|
3d316742e6 | |
|
|
0d5f9fbf22 | |
|
|
d40d7a48a1 | |
|
|
e17efe2a29 | |
|
|
303fe826bd | |
|
|
8928424a9c | |
|
|
171ce425b4 | |
|
|
90f05c9a4a | |
|
|
bee37df76d | |
|
|
a1deb2ffa3 | |
|
|
8d1822ec2d | |
|
|
71a97f3438 | |
|
|
bbe1f4dc61 | |
|
|
2e9c4a9c56 | |
|
|
dc90983cb6 | |
|
|
a3e205f001 | |
|
|
cf32c15a44 | |
|
|
09db998143 | |
|
|
3c82d6463e | |
|
|
e79aafe29a | |
|
|
3a12e45342 | |
|
|
a14da1d78d | |
|
|
4557e44e02 | |
|
|
4d47818791 | |
|
|
e1eda0e53a | |
|
|
6b96f01db2 | |
|
|
2e22a3c985 | |
|
|
3659faa509 |
|
|
@ -3,3 +3,5 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
dist
|
dist
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
env3
|
||||||
|
env2
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
checks:
|
|
||||||
python:
|
|
||||||
code_rating: true
|
|
||||||
filter:
|
|
||||||
excluded_paths:
|
|
||||||
- 'tests/*'
|
|
||||||
tools:
|
|
||||||
external_code_coverage:
|
|
||||||
timeout: 300
|
|
||||||
runs: 9
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
sonar.sources=aprslib
|
||||||
|
sonar.tests=tests
|
||||||
|
sonar.sourceEncoding=UTF-8
|
||||||
1
Makefile
1
Makefile
|
|
@ -41,5 +41,4 @@ dist: clean
|
||||||
python setup.py bdist_wheel --universal
|
python setup.py bdist_wheel --universal
|
||||||
|
|
||||||
upload: dist
|
upload: dist
|
||||||
python setup.py register -r pypi
|
|
||||||
twine upload -r pypi dist/*
|
twine upload -r pypi dist/*
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ from datetime import date as _date
|
||||||
__date__ = str(_date.today())
|
__date__ = str(_date.today())
|
||||||
del _date
|
del _date
|
||||||
|
|
||||||
__version__ = "0.6.47"
|
__version__ = "0.7.2"
|
||||||
version_info = (0, 6, 47)
|
version_info = (0, 7, 2)
|
||||||
__author__ = "Rossen Georgiev"
|
__author__ = "Rossen Georgiev"
|
||||||
__all__ = ['IS', 'parse', 'passcode']
|
__all__ = ['IS', 'parse', 'passcode']
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -333,11 +333,13 @@ class IS(object):
|
||||||
self.logger.error("socket.recv(): returned empty")
|
self.logger.error("socket.recv(): returned empty")
|
||||||
raise ConnectionDrop("connection dropped")
|
raise ConnectionDrop("connection dropped")
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
self.logger.error("socket error on recv(): %s" % str(e))
|
# ignore error when blocking=false, and we attempt to read empty socket
|
||||||
if "Resource temporarily unavailable" in str(e):
|
if ("Resource temporarily unavailable" in str(e)
|
||||||
if not blocking:
|
and not blocking
|
||||||
if len(self.buf) == 0:
|
and len(self.buf) == 0):
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
self.logger.error("socket error on recv(): %s" % str(e))
|
||||||
|
|
||||||
self.buf += short_buf
|
self.buf += short_buf
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,28 @@ from aprslib.parsing.position import *
|
||||||
from aprslib.parsing.mice import *
|
from aprslib.parsing.mice import *
|
||||||
from aprslib.parsing.message import *
|
from aprslib.parsing.message import *
|
||||||
from aprslib.parsing.telemetry import *
|
from aprslib.parsing.telemetry import *
|
||||||
|
from aprslib.parsing.thirdparty import *
|
||||||
from aprslib.parsing.weather import *
|
from aprslib.parsing.weather import *
|
||||||
|
|
||||||
|
unsupported_formats = {
|
||||||
|
'#':'raw weather report',
|
||||||
|
'$':'raw gps',
|
||||||
|
'%':'agrelo',
|
||||||
|
'&':'reserved',
|
||||||
|
'(':'unused',
|
||||||
|
')':'item report',
|
||||||
|
'*':'complete weather report',
|
||||||
|
'+':'reserved',
|
||||||
|
'-':'unused',
|
||||||
|
'.':'reserved',
|
||||||
|
'<':'station capabilities',
|
||||||
|
'?':'general query format',
|
||||||
|
'T':'telemetry report',
|
||||||
|
'[':'maidenhead locator beacon',
|
||||||
|
'\\':'unused',
|
||||||
|
']':'unused',
|
||||||
|
'^':'unused',
|
||||||
|
}
|
||||||
|
|
||||||
def _unicode_packet(packet):
|
def _unicode_packet(packet):
|
||||||
# attempt utf-8
|
# attempt utf-8
|
||||||
|
|
@ -138,28 +158,13 @@ def parse(packet):
|
||||||
def _try_toparse_body(packet_type, body, parsed):
|
def _try_toparse_body(packet_type, body, parsed):
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
# NOT SUPPORTED FORMATS
|
if packet_type in unsupported_formats:
|
||||||
#
|
raise UnknownFormat("Format is not supported: '{}' {}".format(packet_type, unsupported_formats[packet_type]))
|
||||||
# # - raw weather report
|
|
||||||
# $ - raw gps
|
# 3rd party traffic
|
||||||
# % - agrelo
|
elif packet_type == '}':
|
||||||
# & - reserved
|
logger.debug("Packet is third-party")
|
||||||
# ( - unused
|
body, result = parse_thirdparty(body)
|
||||||
# ) - item report
|
|
||||||
# * - complete weather report
|
|
||||||
# + - reserved
|
|
||||||
# - - unused
|
|
||||||
# . - reserved
|
|
||||||
# < - station capabilities
|
|
||||||
# ? - general query format
|
|
||||||
# T - telemetry report
|
|
||||||
# [ - maidenhead locator beacon
|
|
||||||
# \ - unused
|
|
||||||
# ] - unused
|
|
||||||
# ^ - unused
|
|
||||||
# } - 3rd party traffic
|
|
||||||
if packet_type in '#$%)*<?T[}':
|
|
||||||
raise UnknownFormat("format is not supported")
|
|
||||||
|
|
||||||
# user defined
|
# user defined
|
||||||
elif packet_type == ',':
|
elif packet_type == ',':
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import re
|
import re
|
||||||
|
from math import sqrt
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from aprslib import base91
|
from aprslib import base91
|
||||||
from aprslib.exceptions import ParseError
|
from aprslib.exceptions import ParseError
|
||||||
|
|
@ -133,17 +134,26 @@ def parse_comment(body, parsed):
|
||||||
|
|
||||||
def parse_data_extentions(body):
|
def parse_data_extentions(body):
|
||||||
parsed = {}
|
parsed = {}
|
||||||
match = re.findall(r"^([0-9 .]{3})/([0-9 .]{3})", body)
|
|
||||||
|
|
||||||
|
# course speed bearing nrq
|
||||||
|
# Page 27 of the spec
|
||||||
|
# format: 111/222/333/444text
|
||||||
|
match = re.findall(r"^([0-9 \.]{3})/([0-9 \.]{3})", body)
|
||||||
if match:
|
if match:
|
||||||
cse, spd = match[0]
|
cse, spd = match[0]
|
||||||
body = body[7:]
|
body = body[7:]
|
||||||
parsed.update({'course': int(cse) if cse.isdigit() and 1 <= int(cse) <= 360 else 0})
|
if cse.isdigit() and cse != "000":
|
||||||
if spd.isdigit():
|
parsed.update({'course': int(cse) if 1 <= int(cse) <= 360 else 0})
|
||||||
|
if spd.isdigit() and spd != "000":
|
||||||
parsed.update({'speed': int(spd)*1.852})
|
parsed.update({'speed': int(spd)*1.852})
|
||||||
|
|
||||||
match = re.findall(r"^/([0-9 .]{3})/([0-9 .]{3})", body)
|
# DF Report format
|
||||||
|
# Page 29 of teh spec
|
||||||
|
match = re.findall(r"^/([0-9 \.]{3})/([0-9 \.]{3})", body)
|
||||||
if match:
|
if match:
|
||||||
|
# cse=000 means stations is fixed, Page 29 of the spec
|
||||||
|
if cse == '000':
|
||||||
|
parsed.update({'course': 0})
|
||||||
brg, nrq = match[0]
|
brg, nrq = match[0]
|
||||||
body = body[8:]
|
body = body[8:]
|
||||||
if brg.isdigit():
|
if brg.isdigit():
|
||||||
|
|
@ -151,17 +161,45 @@ def parse_data_extentions(body):
|
||||||
if nrq.isdigit():
|
if nrq.isdigit():
|
||||||
parsed.update({'nrq': int(nrq)})
|
parsed.update({'nrq': int(nrq)})
|
||||||
else:
|
else:
|
||||||
match = re.findall(r"^(PHG(\d[\x30-\x7e]\d\d[0-9A-Z]?))", body)
|
# PHG format: PHGabcd....
|
||||||
|
# RHGR format: RHGabcdr/....
|
||||||
|
match = re.findall(r"^(PHG(\d[\x30-\x7e]\d\d)([0-9A-Z]\/)?)", body)
|
||||||
if match:
|
if match:
|
||||||
ext, phg = match[0]
|
ext, phg, phgr = match[0]
|
||||||
body = body[len(ext):]
|
body = body[len(ext):]
|
||||||
parsed.update({'phg': phg})
|
parsed.update({
|
||||||
|
'phg': phg,
|
||||||
|
'phg_power': int(phg[0]) ** 2, # watts
|
||||||
|
'phg_height': (10 * (2 ** (ord(phg[1]) - 0x30))) * 0.3048, # in meters
|
||||||
|
'phg_gain': 10 ** (int(phg[2]) / 10.0), # dB
|
||||||
|
})
|
||||||
|
|
||||||
|
phg_dir = int(phg[3])
|
||||||
|
if phg_dir == 0:
|
||||||
|
phg_dir = 'omni'
|
||||||
|
elif phg_dir == 9:
|
||||||
|
phg_dir = 'invalid'
|
||||||
|
else:
|
||||||
|
phg_dir = 45 * phg_dir
|
||||||
|
|
||||||
|
parsed['phg_dir'] = phg_dir
|
||||||
|
# range in km
|
||||||
|
parsed['phg_range'] = sqrt(2 * (parsed['phg_height'] / 0.3048)
|
||||||
|
* sqrt((parsed['phg_power'] / 10.0)
|
||||||
|
* (parsed['phg_gain'] / 2.0)
|
||||||
|
)
|
||||||
|
) * 1.60934
|
||||||
|
|
||||||
|
if phgr:
|
||||||
|
# PHG rate per hour
|
||||||
|
parsed['phg'] += phgr[0]
|
||||||
|
parsed.update({'phg_rate': int(phgr[0], 16)}) # as decimal
|
||||||
else:
|
else:
|
||||||
match = re.findall(r"^RNG(\d{4})", body)
|
match = re.findall(r"^RNG(\d{4})", body)
|
||||||
if match:
|
if match:
|
||||||
rng = match[0]
|
rng = match[0]
|
||||||
body = body[7:]
|
body = body[7:]
|
||||||
parsed.update({'rng': int(rng) * 1.609344}) # miles to km
|
parsed.update({'rng': int(rng) * 1.609344}) # miles to km
|
||||||
|
|
||||||
return body, parsed
|
return body, parsed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,28 +69,66 @@ def parse_message(body):
|
||||||
break
|
break
|
||||||
|
|
||||||
# regular message
|
# regular message
|
||||||
else:
|
# ---------------------------
|
||||||
logger.debug("Packet is just a regular message")
|
logger.debug("Packet is just a regular message")
|
||||||
parsed.update({'format': 'message'})
|
parsed.update({'format': 'message'})
|
||||||
|
|
||||||
match = re.search(r"^(ack|rej)([A-Za-z0-9]{1,5})$", body)
|
# APRS supports two different message formats:
|
||||||
if match:
|
# - the standard format which is described in 'aprs101.pdf':
|
||||||
parsed.update({
|
# http://www.aprs.org/doc/APRS101.PDF
|
||||||
'response': match.group(1),
|
# - an addendum from 1999 which introduces a new format:
|
||||||
'msgNo': match.group(2),
|
# http://www.aprs.org/aprs11/replyacks.txt
|
||||||
})
|
#
|
||||||
else:
|
# A message (ack/rej as well as a standard msg text body) can either have:
|
||||||
body = body[0:70]
|
# - 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)
|
||||||
|
|
||||||
match = re.search(r"{([A-Za-z0-9]{1,5})$", body)
|
# ack / rej
|
||||||
if match:
|
# ---------------------------
|
||||||
msgNo = match.group(1)
|
# NEW REPLAY-ACK
|
||||||
body = body[:len(body) - 1 - len(msgNo)]
|
# 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
|
||||||
|
|
||||||
parsed.update({'msgNo': msgNo})
|
# 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
|
||||||
|
|
||||||
parsed.update({'message_text': body.strip(' ')})
|
# 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
|
break
|
||||||
|
|
||||||
return ('', parsed)
|
return ('', parsed)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import re
|
||||||
from aprslib import base91
|
from aprslib import base91
|
||||||
from aprslib.exceptions import ParseError
|
from aprslib.exceptions import ParseError
|
||||||
from aprslib.parsing import logger
|
from aprslib.parsing import logger
|
||||||
from aprslib.parsing.common import parse_timestamp, parse_comment
|
from aprslib.parsing.common import parse_timestamp, parse_comment, parse_data_extentions
|
||||||
from aprslib.parsing.weather import parse_weather_data
|
from aprslib.parsing.weather import parse_weather_data
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -60,6 +60,11 @@ def parse_position(packet_type, body):
|
||||||
# check comment for weather information
|
# check comment for weather information
|
||||||
# Page 62 of the spec
|
# Page 62 of the spec
|
||||||
if parsed['symbol'] == '_':
|
if parsed['symbol'] == '_':
|
||||||
|
# attempt to parse winddir/speed
|
||||||
|
# Page 92 of the spec
|
||||||
|
body, result = parse_data_extentions(body)
|
||||||
|
parsed.update(result)
|
||||||
|
|
||||||
logger.debug("Attempting to parse weather report from comment")
|
logger.debug("Attempting to parse weather report from comment")
|
||||||
body, result = parse_weather_data(body)
|
body, result = parse_weather_data(body)
|
||||||
parsed.update({
|
parsed.update({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import re
|
||||||
|
from aprslib.parsing.__init__ import parse
|
||||||
|
from aprslib.exceptions import UnknownFormat
|
||||||
|
from aprslib.exceptions import ParseError
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'parse_thirdparty',
|
||||||
|
]
|
||||||
|
|
||||||
|
def parse_thirdparty(body):
|
||||||
|
parsed = {'format':'thirdparty'}
|
||||||
|
|
||||||
|
# Parse sub-packet
|
||||||
|
try:
|
||||||
|
subpacket = parse(body)
|
||||||
|
except (UnknownFormat,ParseError) as ukf:
|
||||||
|
raise
|
||||||
|
|
||||||
|
parsed.update({'subpacket':subpacket})
|
||||||
|
|
||||||
|
return('',parsed)
|
||||||
|
|
@ -14,7 +14,7 @@ key_map = {
|
||||||
'g': 'wind_gust',
|
'g': 'wind_gust',
|
||||||
'c': 'wind_direction',
|
'c': 'wind_direction',
|
||||||
't': 'temperature',
|
't': 'temperature',
|
||||||
'S': 'wind_speed',
|
's': 'wind_speed',
|
||||||
'r': 'rain_1h',
|
'r': 'rain_1h',
|
||||||
'p': 'rain_24h',
|
'p': 'rain_24h',
|
||||||
'P': 'rain_since_midnight',
|
'P': 'rain_since_midnight',
|
||||||
|
|
@ -22,22 +22,22 @@ key_map = {
|
||||||
'b': 'pressure',
|
'b': 'pressure',
|
||||||
'l': 'luminosity',
|
'l': 'luminosity',
|
||||||
'L': 'luminosity',
|
'L': 'luminosity',
|
||||||
's': 'snow',
|
'S': 'snow',
|
||||||
'#': 'rain_raw',
|
'#': 'rain_raw',
|
||||||
}
|
}
|
||||||
val_map = {
|
val_map = {
|
||||||
'g': lambda x: int(x) * wind_multiplier,
|
'g': lambda x: int(x) * wind_multiplier,
|
||||||
'c': lambda x: int(x),
|
'c': lambda x: int(x),
|
||||||
'S': lambda x: int(x) * wind_multiplier,
|
's': lambda x: int(x) * wind_multiplier,
|
||||||
't': lambda x: (float(x) - 32) / 1.8,
|
't': lambda x: (float(x) - 32) / 1.8,
|
||||||
'r': lambda x: int(x) * rain_multiplier,
|
'r': lambda x: int(x) * rain_multiplier,
|
||||||
'p': lambda x: int(x) * rain_multiplier,
|
'p': lambda x: int(x) * rain_multiplier,
|
||||||
'P': lambda x: int(x) * rain_multiplier,
|
'P': lambda x: int(x) * rain_multiplier,
|
||||||
'h': lambda x: int(x),
|
'h': lambda x: 100 if int(x) == 0 else int(x),
|
||||||
'b': lambda x: float(x) / 10,
|
'b': lambda x: float(x) / 10,
|
||||||
'l': lambda x: int(x) + 1000,
|
'l': lambda x: int(x) + 1000,
|
||||||
'L': lambda x: int(x),
|
'L': lambda x: int(x),
|
||||||
's': lambda x: float(x) * 25.4,
|
'S': lambda x: float(x) * 25.4,
|
||||||
'#': lambda x: int(x),
|
'#': lambda x: int(x),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,20 +46,24 @@ def parse_weather_data(body):
|
||||||
|
|
||||||
# parse weather data
|
# parse weather data
|
||||||
body = re.sub(r"^([0-9]{3})/([0-9]{3})", "c\\1s\\2", body)
|
body = re.sub(r"^([0-9]{3})/([0-9]{3})", "c\\1s\\2", body)
|
||||||
body = body.replace('s', 'S', 1)
|
#body = body.replace('s', 'S', 1)
|
||||||
|
|
||||||
data = re.findall(r"([cSgtrpPlLs#]\d{3}|t-\d{2}|h\d{2}|b\d{5}|s\.\d{2}|s\d\.\d)", body)
|
# match as many parameters from the start, rest is comment
|
||||||
data = map(lambda x: (key_map[x[0]] , val_map[x[0]](x[1:])), data)
|
data = re.match(r"^([csgtrpPlLS#][0-9\-\. ]{3}|h[0-9\. ]{2}|b[0-9\. ]{5})+", body)
|
||||||
|
|
||||||
parsed.update(dict(data))
|
if data:
|
||||||
|
data = data.group()
|
||||||
# strip weather data
|
# split out data from comment
|
||||||
body = re.sub(r"([cSgtrpPlLs#][0-9\-\. ]{3}|h[0-9\. ]{2}|b[0-9\. ]{5})", '', body)
|
body = body[len(data):]
|
||||||
|
# parse all weather parameters
|
||||||
|
data = re.findall(r"([csgtrpPlLS#]\d{3}|t-\d{2}|h\d{2}|b\d{5}|s\.\d{2}|s\d\.\d)", data)
|
||||||
|
data = map(lambda x: (key_map[x[0]] , val_map[x[0]](x[1:])), data)
|
||||||
|
parsed.update(dict(data))
|
||||||
|
|
||||||
return (body, parsed)
|
return (body, parsed)
|
||||||
|
|
||||||
def parse_weather(body):
|
def parse_weather(body):
|
||||||
match = re.match("^(\d{8})c[\. \d]{3}s[\. \d]{3}g[\. \d]{3}t[\. \d]{3}", body)
|
match = re.match(r"^(\d{8})c[\. \d]{3}s[\. \d]{3}g[\. \d]{3}t[\. \d]{3}", body)
|
||||||
if not match:
|
if not match:
|
||||||
raise ParseError("invalid positionless weather report format")
|
raise ParseError("invalid positionless weather report format")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
twine
|
||||||
|
wheel
|
||||||
mox3
|
mox3
|
||||||
|
|
||||||
coverage>=5.0; python_version == '2.7' or python_version >= '3.5'
|
coverage>=5.0; python_version == '2.7' or python_version >= '3.5'
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -65,8 +65,8 @@ class ParseTestCase(unittest.TestCase):
|
||||||
self.fail("empty status packet shouldn't raise exception")
|
self.fail("empty status packet shouldn't raise exception")
|
||||||
|
|
||||||
def test_unsupported_formats_raising(self):
|
def test_unsupported_formats_raising(self):
|
||||||
with self.assertRaises(UnknownFormat):
|
for packet_type in parsing.unsupported_formats:
|
||||||
for packet_type in '#$%)*,<?T[_{}':
|
with self.assertRaises(UnknownFormat):
|
||||||
packet = "A>B:%saaa" % packet_type
|
packet = "A>B:%saaa" % packet_type
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -256,30 +256,47 @@ class DataExtentionsTC(unittest.TestCase):
|
||||||
'speed': 100*1.852,
|
'speed': 100*1.852,
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_empty_course_speed(self):
|
def test_course_speed_spaces(self):
|
||||||
body = " / /text"
|
body = " / /text"
|
||||||
remaining, parsed = parse_data_extentions(body)
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
self.assertEqual(remaining, '/text')
|
self.assertEqual(remaining, '/text')
|
||||||
self.assertEqual(parsed, {
|
self.assertEqual(parsed, {})
|
||||||
'course': 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
def test_course_speed_dots(self):
|
||||||
body = ".../.../text"
|
body = ".../.../text"
|
||||||
remaining, parsed = parse_data_extentions(body)
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
self.assertEqual(remaining, '/text')
|
self.assertEqual(remaining, '/text')
|
||||||
self.assertEqual(parsed, {
|
self.assertEqual(parsed, {})
|
||||||
'course': 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
def test_course_speed_zeros(self):
|
||||||
|
body = "000/000/text"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, '/text')
|
||||||
|
self.assertEqual(parsed, {})
|
||||||
|
|
||||||
|
def test_course_speed_valid_chars_but_invalid_values(self):
|
||||||
body = "22./33 /text"
|
body = "22./33 /text"
|
||||||
remaining, parsed = parse_data_extentions(body)
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
self.assertEqual(remaining, '/text')
|
self.assertEqual(remaining, '/text')
|
||||||
self.assertEqual(parsed, {
|
self.assertEqual(parsed, {})
|
||||||
'course': 0,
|
|
||||||
})
|
def test_course_speed_invalid_chars_spd(self):
|
||||||
|
body = "222/33a/text"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, '222/33a/text')
|
||||||
|
self.assertEqual(parsed, {})
|
||||||
|
|
||||||
|
def test_course_speed_invalid_chars_cse(self):
|
||||||
|
body = "22a/333/text"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, '22a/333/text')
|
||||||
|
self.assertEqual(parsed, {})
|
||||||
|
|
||||||
def test_empty_bearing_nrq(self):
|
def test_empty_bearing_nrq(self):
|
||||||
body = "111/100/ /...text"
|
body = "111/100/ /...text"
|
||||||
|
|
@ -300,6 +317,17 @@ class DataExtentionsTC(unittest.TestCase):
|
||||||
'speed': 100*1.852,
|
'speed': 100*1.852,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_course_speed_bearing_nrq_empty_cse_speed(self):
|
||||||
|
body = "000/000/234/345text"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, 'text')
|
||||||
|
self.assertEqual(parsed, {
|
||||||
|
'course': 0,
|
||||||
|
'bearing': 234,
|
||||||
|
'nrq': 345,
|
||||||
|
})
|
||||||
|
|
||||||
def test_course_speed_bearing_nrq(self):
|
def test_course_speed_bearing_nrq(self):
|
||||||
body = "123/100/234/345text"
|
body = "123/100/234/345text"
|
||||||
remaining, parsed = parse_data_extentions(body)
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
@ -312,23 +340,78 @@ class DataExtentionsTC(unittest.TestCase):
|
||||||
'nrq': 345,
|
'nrq': 345,
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_PHG(self):
|
def test_PHGR_1(self):
|
||||||
body = "PHG1234Atext"
|
body = "PHG51325/text"
|
||||||
remaining, parsed = parse_data_extentions(body)
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
self.assertEqual(remaining, 'text')
|
self.assertEqual(remaining, 'text')
|
||||||
self.assertEqual(parsed, {
|
self.assertEqual(parsed, {
|
||||||
'phg': '1234A',
|
'phg': '51325',
|
||||||
|
'phg_rate': 5,
|
||||||
|
'phg_power': 25,
|
||||||
|
'phg_height': 6.096,
|
||||||
|
'phg_gain': 1.9952623149688795,
|
||||||
|
'phg_dir': 90,
|
||||||
|
'phg_range': 12.791023731208883,
|
||||||
})
|
})
|
||||||
|
|
||||||
body = "PHG1234text"
|
def test_PHGR_2(self):
|
||||||
|
body = "PHG5132F/text"
|
||||||
remaining, parsed = parse_data_extentions(body)
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
self.assertEqual(remaining, 'text')
|
self.assertEqual(remaining, 'text')
|
||||||
self.assertEqual(parsed, {
|
self.assertEqual(parsed, {
|
||||||
'phg': '1234',
|
'phg': '5132F',
|
||||||
|
'phg_rate': 15,
|
||||||
|
'phg_power': 25,
|
||||||
|
'phg_height': 6.096,
|
||||||
|
'phg_gain': 1.9952623149688795,
|
||||||
|
'phg_dir': 90,
|
||||||
|
'phg_range': 12.791023731208883,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_PHG_1(self):
|
||||||
|
body = "PHG5132Atext"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, 'Atext')
|
||||||
|
self.assertEqual(parsed, {
|
||||||
|
'phg': '5132',
|
||||||
|
'phg_power': 25,
|
||||||
|
'phg_height': 6.096,
|
||||||
|
'phg_gain': 1.9952623149688795,
|
||||||
|
'phg_dir': 90,
|
||||||
|
'phg_range': 12.791023731208883,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_PHG_2(self):
|
||||||
|
body = "PHG5132text"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, 'text')
|
||||||
|
self.assertEqual(parsed, {
|
||||||
|
'phg': '5132',
|
||||||
|
'phg_power': 25,
|
||||||
|
'phg_height': 6.096,
|
||||||
|
'phg_gain': 1.9952623149688795,
|
||||||
|
'phg_dir': 90,
|
||||||
|
'phg_range': 12.791023731208883,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_PHG_dir_omni(self):
|
||||||
|
body = "PHG0000text"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, 'text')
|
||||||
|
self.assertEqual(parsed['phg_dir'], 'omni')
|
||||||
|
|
||||||
|
def test_PHG_dir_invalid(self):
|
||||||
|
body = "PHG0009text"
|
||||||
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
||||||
|
self.assertEqual(remaining, 'text')
|
||||||
|
self.assertEqual(parsed['phg_dir'], 'invalid')
|
||||||
|
|
||||||
def test_range(self):
|
def test_range(self):
|
||||||
body = "RNG1000text"
|
body = "RNG1000text"
|
||||||
remaining, parsed = parse_data_extentions(body)
|
remaining, parsed = parse_data_extentions(body)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import unittest
|
||||||
|
from aprslib.parsing.message import parse_message
|
||||||
|
|
||||||
|
# ack/rej assertion tests
|
||||||
|
class AckRejTests(unittest.TestCase):
|
||||||
|
|
||||||
|
# errorneus rej
|
||||||
|
def test_errorneus_rej(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :red12345")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'message_text': 'red12345',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# reject with "old" msgno
|
||||||
|
def test_reject_old_msgno(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :rej123")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'msgNo': '123',
|
||||||
|
'response': 'rej'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# ack with new msgNo but no ackMsgNo
|
||||||
|
def test_ack_new_msgno_but_no_ack_msgno(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :ackAB}")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'response': 'ack',
|
||||||
|
'msgNo': 'AB',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# ack with new msgNo and ackMsgNo
|
||||||
|
def test_ack_new_msgno_and_ackmsgno(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :ackAB}CD")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'response': 'ack',
|
||||||
|
'msgNo': 'AB',
|
||||||
|
'ackMsgNo': 'CD',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# message text body tests
|
||||||
|
class MessageTestBodyTests(unittest.TestCase):
|
||||||
|
|
||||||
|
# message body without msg no
|
||||||
|
def test_message_without_msgno(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :HelloWorld ")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'message_text': 'HelloWorld',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# message body with msg no - old format
|
||||||
|
def test_message_body_with_no_msgno_oldformat(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :HelloWorld {ABCDE")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'message_text': 'HelloWorld',
|
||||||
|
'msgNo': 'ABCDE',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# message body with msgNo (new format) and ackMsgNo missing
|
||||||
|
def test_message_body_with_msgno_and_ackmsgno_missing_newformat(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :HelloWorld {AB}")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'message_text': 'HelloWorld',
|
||||||
|
'msgNo': 'AB',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# message body with msgNo and ackMsgNo (new format)
|
||||||
|
def test_message_body_with_msgno_and_ackmsgno_newformat(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :HelloWorld {AB}CD")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'message_text': 'HelloWorld',
|
||||||
|
'msgNo': 'AB',
|
||||||
|
'ackMsgNo': 'CD',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
# message body with really long message
|
||||||
|
def test_message_body_with_long_message(self):
|
||||||
|
unparsed, result = parse_message("WXBOT :00000000001111111111222222222233333333334444444444555555555566666666667777777777")
|
||||||
|
expected = {
|
||||||
|
'format': 'message',
|
||||||
|
'addresse': 'WXBOT',
|
||||||
|
'message_text': '00000000001111111111222222222233333333334444444444555555555566666666667777777777',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(unparsed, '')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import unittest
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from aprslib.parsing import parse_position
|
||||||
|
|
||||||
|
class ParsePositionDataExtAndWeather(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.maxDiff = None
|
||||||
|
|
||||||
|
def timestamp_from_partial(self, day, hour, minute):
|
||||||
|
now = datetime.now()
|
||||||
|
corrected = now.replace(day=day, hour=hour, minute=minute, second=0, microsecond=0)
|
||||||
|
return int((corrected - datetime(1970, 1, 1)).total_seconds())
|
||||||
|
|
||||||
|
def test_position_packet_only_weather_valid(self):
|
||||||
|
packet_type = '@'
|
||||||
|
packet = "092345z4903.50N/07201.75W_g000t066r000p000...dUII"
|
||||||
|
expected = {
|
||||||
|
'messagecapable': True,
|
||||||
|
'raw_timestamp': '092345z',
|
||||||
|
'timestamp': self.timestamp_from_partial(9, 23, 45),
|
||||||
|
'format': 'uncompressed',
|
||||||
|
'posambiguity': 0,
|
||||||
|
'symbol': '_',
|
||||||
|
'symbol_table': '/',
|
||||||
|
'latitude': 49.05833333333333,
|
||||||
|
'longitude': -72.02916666666667,
|
||||||
|
'comment': '...dUII',
|
||||||
|
'weather': {
|
||||||
|
'wind_gust': 0.0,
|
||||||
|
'temperature': 18.88888888888889,
|
||||||
|
'rain_1h': 0.0,
|
||||||
|
'rain_24h': 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, result = parse_position(packet_type, packet)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_position_packet_data_ext_and_weather_valid(self):
|
||||||
|
packet_type = '@'
|
||||||
|
packet = "092345z4903.50N/07201.75W_090/001g000t066r000p000...dUII"
|
||||||
|
expected = {
|
||||||
|
'messagecapable': True,
|
||||||
|
'raw_timestamp': '092345z',
|
||||||
|
'timestamp': self.timestamp_from_partial(9, 23, 45),
|
||||||
|
'format': 'uncompressed',
|
||||||
|
'posambiguity': 0,
|
||||||
|
'symbol': '_',
|
||||||
|
'symbol_table': '/',
|
||||||
|
'latitude': 49.05833333333333,
|
||||||
|
'longitude': -72.02916666666667,
|
||||||
|
'course': 90,
|
||||||
|
'speed': 1*1.852,
|
||||||
|
'comment': '...dUII',
|
||||||
|
'weather': {
|
||||||
|
'wind_gust': 0.0,
|
||||||
|
'temperature': 18.88888888888889,
|
||||||
|
'rain_1h': 0.0,
|
||||||
|
'rain_24h': 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, result = parse_position(packet_type, packet)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_position_packet_optional_speed(self):
|
||||||
|
packet_type = '@'
|
||||||
|
packet = "092345z4903.50N/07201.75W_090/...g000t066r000p000...dUII"
|
||||||
|
expected = {
|
||||||
|
'messagecapable': True,
|
||||||
|
'raw_timestamp': '092345z',
|
||||||
|
'timestamp': self.timestamp_from_partial(9, 23, 45),
|
||||||
|
'format': 'uncompressed',
|
||||||
|
'posambiguity': 0,
|
||||||
|
'symbol': '_',
|
||||||
|
'symbol_table': '/',
|
||||||
|
'latitude': 49.05833333333333,
|
||||||
|
'longitude': -72.02916666666667,
|
||||||
|
'course': 90,
|
||||||
|
'comment': '...dUII',
|
||||||
|
'weather': {
|
||||||
|
'wind_gust': 0.0,
|
||||||
|
'temperature': 18.88888888888889,
|
||||||
|
'rain_1h': 0.0,
|
||||||
|
'rain_24h': 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, result = parse_position(packet_type, packet)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_position_packet_optional_course(self):
|
||||||
|
packet_type = '@'
|
||||||
|
packet = "092345z4903.50N/07201.75W_ /001g000t066r000p000...dUII"
|
||||||
|
expected = {
|
||||||
|
'messagecapable': True,
|
||||||
|
'raw_timestamp': '092345z',
|
||||||
|
'timestamp': self.timestamp_from_partial(9, 23, 45),
|
||||||
|
'format': 'uncompressed',
|
||||||
|
'posambiguity': 0,
|
||||||
|
'symbol': '_',
|
||||||
|
'symbol_table': '/',
|
||||||
|
'latitude': 49.05833333333333,
|
||||||
|
'longitude': -72.02916666666667,
|
||||||
|
'speed': 1*1.852,
|
||||||
|
'comment': '...dUII',
|
||||||
|
'weather': {
|
||||||
|
'wind_gust': 0.0,
|
||||||
|
'temperature': 18.88888888888889,
|
||||||
|
'rain_1h': 0.0,
|
||||||
|
'rain_24h': 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, result = parse_position(packet_type, packet)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_position_packet_optional_speed_and_course(self):
|
||||||
|
packet_type = '@'
|
||||||
|
packet = "092345z4903.50N/07201.75W_.../...g000t066r000p000...dUII"
|
||||||
|
expected = {
|
||||||
|
'messagecapable': True,
|
||||||
|
'raw_timestamp': '092345z',
|
||||||
|
'timestamp': self.timestamp_from_partial(9, 23, 45),
|
||||||
|
'format': 'uncompressed',
|
||||||
|
'posambiguity': 0,
|
||||||
|
'symbol': '_',
|
||||||
|
'symbol_table': '/',
|
||||||
|
'latitude': 49.05833333333333,
|
||||||
|
'longitude': -72.02916666666667,
|
||||||
|
'comment': '...dUII',
|
||||||
|
'weather': {
|
||||||
|
'wind_gust': 0.0,
|
||||||
|
'temperature': 18.88888888888889,
|
||||||
|
'rain_1h': 0.0,
|
||||||
|
'rain_24h': 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, result = parse_position(packet_type, packet)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
def test_position_packet_optional_course(self):
|
||||||
|
packet_type = '@'
|
||||||
|
packet = "092345z4903.50N/07201.75W_ /001g000t066r000p000...dUII"
|
||||||
|
expected = {
|
||||||
|
'messagecapable': True,
|
||||||
|
'raw_timestamp': '092345z',
|
||||||
|
'timestamp': self.timestamp_from_partial(9, 23, 45),
|
||||||
|
'format': 'uncompressed',
|
||||||
|
'posambiguity': 0,
|
||||||
|
'symbol': '_',
|
||||||
|
'symbol_table': '/',
|
||||||
|
'latitude': 49.05833333333333,
|
||||||
|
'longitude': -72.02916666666667,
|
||||||
|
'speed': 1*1.852,
|
||||||
|
'comment': '...dUII',
|
||||||
|
'weather': {
|
||||||
|
'wind_gust': 0.0,
|
||||||
|
'temperature': 18.88888888888889,
|
||||||
|
'rain_1h': 0.0,
|
||||||
|
'rain_24h': 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, result = parse_position(packet_type, packet)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
@ -113,11 +113,17 @@ class ParseCommentWeather(unittest.TestCase):
|
||||||
|
|
||||||
def test_humidity(self):
|
def test_humidity(self):
|
||||||
expected = "", {
|
expected = "", {
|
||||||
"humidity": 0.0
|
"humidity": 100
|
||||||
}
|
}
|
||||||
result = parse_weather_data("h00")
|
result = parse_weather_data("h00")
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
expected = "", {
|
||||||
|
"humidity": 1
|
||||||
|
}
|
||||||
|
result = parse_weather_data("h01")
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
expected = "", {
|
expected = "", {
|
||||||
"humidity": 99
|
"humidity": 99
|
||||||
}
|
}
|
||||||
|
|
@ -198,11 +204,11 @@ class ParseCommentWeather(unittest.TestCase):
|
||||||
def test_positionless_packet(self):
|
def test_positionless_packet(self):
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'comment': 'wRSW',
|
'comment': '.ABS1.2CDF',
|
||||||
'format': 'wx',
|
'format': 'wx',
|
||||||
'from': 'A',
|
'from': 'A',
|
||||||
'path': [],
|
'path': [],
|
||||||
'raw': 'A>B:_10090556c220s004g005t077r010p020P030h50b09900s5.5wRSW',
|
'raw': 'A>B:_10090556c220s004g005t077r010p020P030h50b09900s5.5.ABS1.2CDF',
|
||||||
'to': 'B',
|
'to': 'B',
|
||||||
'via': '',
|
'via': '',
|
||||||
'wx_raw_timestamp': '10090556',
|
'wx_raw_timestamp': '10090556',
|
||||||
|
|
@ -220,11 +226,11 @@ class ParseCommentWeather(unittest.TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packet = "A>B:_10090556c220s004g005t077r010p020P030h50b09900s5.5wRSW"
|
packet = "A>B:_10090556c220s004g005t077r010p020P030h50b09900s5.5.ABS1.2CDF"
|
||||||
|
|
||||||
self.assertEqual(expected, parse(packet))
|
self.assertEqual(expected, parse(packet))
|
||||||
|
|
||||||
packet2 = "A>B:_10090556c220s112g t r h b wRSW"
|
packet2 = "A>B:_10090556c220s112g t r h b .ABS1.2CDF"
|
||||||
expected['raw'] = packet2
|
expected['raw'] = packet2
|
||||||
expected['weather'] = {
|
expected['weather'] = {
|
||||||
"wind_direction": 220,
|
"wind_direction": 220,
|
||||||
|
|
@ -233,7 +239,7 @@ class ParseCommentWeather(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(expected, parse(packet2))
|
self.assertEqual(expected, parse(packet2))
|
||||||
|
|
||||||
packet3 = "A>B:_10090556c220s112g...t...r...p...P...b.....wRSW"
|
packet3 = "A>B:_10090556c220s112g...t...r...p...P...b......ABS1.2CDF"
|
||||||
expected['raw'] = packet3
|
expected['raw'] = packet3
|
||||||
expected['weather'] = {
|
expected['weather'] = {
|
||||||
"wind_direction": 220,
|
"wind_direction": 220,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from aprslib import parse
|
||||||
|
from aprslib.exceptions import ParseError, UnknownFormat
|
||||||
|
|
||||||
|
|
||||||
|
class thirdpartyTC(unittest.TestCase):
|
||||||
|
def test_empty_subpacket(self):
|
||||||
|
self.assertRaises(ParseError, parse, "A>B:}")
|
||||||
|
|
||||||
|
def test_no_body(self):
|
||||||
|
self.assertRaises(ParseError, parse, "A>B:}C>D")
|
||||||
|
|
||||||
|
def test_empty_body(self):
|
||||||
|
self.assertRaises(ParseError, parse, "A>B:}C>D:")
|
||||||
|
|
||||||
|
def testparse_header_exception(self):
|
||||||
|
self.assertRaises(ParseError, parse, "A>B:}C:asd")
|
||||||
|
|
||||||
|
def test_empty_body_of_format_that_is_not_status(self):
|
||||||
|
self.assertRaises(ParseError, parse, "A>B:}C>D:!")
|
||||||
|
|
||||||
|
try:
|
||||||
|
parse("A>B:}C>D:>")
|
||||||
|
except:
|
||||||
|
self.fail("empty status packet shouldn't raise exception")
|
||||||
|
|
||||||
|
def test_unsupported_formats_raising(self):
|
||||||
|
with self.assertRaises(UnknownFormat):
|
||||||
|
for packet_type in '#$%)*,<?T[_{':
|
||||||
|
packet = "A>B:}C>D:%saaa" % packet_type
|
||||||
|
|
||||||
|
try:
|
||||||
|
parse(packet)
|
||||||
|
except UnknownFormat as exp:
|
||||||
|
self.assertEqual(exp.packet, packet)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def test_valid_thirdparty_msg(self):
|
||||||
|
packet = "A-1>APRS,B-2,WIDE1*:}C>APU25N,TCPIP,A-1*::DEF :ack56"
|
||||||
|
result = parse(packet)
|
||||||
|
self.assertEqual(result['via'],'')
|
||||||
|
self.assertEqual(result['to'],'APRS')
|
||||||
|
self.assertEqual(result['from'],'A-1')
|
||||||
|
self.assertEqual(result['format'],'thirdparty')
|
||||||
|
self.assertEqual(result['raw'],packet)
|
||||||
|
self.assertEqual(result['path'],['B-2', 'WIDE1*'])
|
||||||
|
self.assertEqual(result['subpacket']['raw'],packet[21:])
|
||||||
|
self.assertEqual(result['subpacket']['via'],'')
|
||||||
|
self.assertEqual(result['subpacket']['msgNo'],'56')
|
||||||
|
self.assertEqual(result['subpacket']['from'],'C')
|
||||||
|
self.assertEqual(result['subpacket']['path'],['TCPIP', 'A-1*'])
|
||||||
|
self.assertEqual(result['subpacket']['response'],'ack')
|
||||||
|
self.assertEqual(result['subpacket']['format'],'message')
|
||||||
|
self.assertEqual(result['subpacket']['to'],'APU25N')
|
||||||
|
self.assertEqual(result['subpacket']['addresse'],'DEF')
|
||||||
|
|
||||||
Loading…
Reference in New Issue