Merge pull request #10 from rossengeorgiev/weather-parsing
Parsing of weather reports
This commit is contained in:
commit
ca8e3f84a9
|
|
@ -162,10 +162,9 @@ def parse(packet):
|
|||
# \ - unused
|
||||
# ] - unused
|
||||
# ^ - unused
|
||||
# _ - positionless weather report
|
||||
# { - user defined
|
||||
# } - 3rd party traffic
|
||||
if packet_type in '#$%)*,<?T[_{}':
|
||||
if packet_type in '#$%)*,<?T[{}':
|
||||
raise UnknownFormat("format is not supported", packet)
|
||||
|
||||
# STATUS PACKET
|
||||
|
|
@ -197,6 +196,23 @@ def parse(packet):
|
|||
body, result = _parse_message(body)
|
||||
parsed.update(result)
|
||||
|
||||
# POSITIONLESS WEATHER REPORT
|
||||
elif packet_type == '_':
|
||||
logger.debug("Attempting to parse as positionless weather report")
|
||||
|
||||
match = re.match("^(\d{8})c[\. \d]{3}s[\. \d]{3}g[\. \d]{3}t[\. \d]{3}", body)
|
||||
if not match:
|
||||
raise ParseError("invalid positionless weather report format")
|
||||
|
||||
comment, weather = _parse_weather_data(body[8:])
|
||||
|
||||
parsed.update({
|
||||
'format': 'wx',
|
||||
'wx_raw_timestamp': match.group(1),
|
||||
'comment': comment.strip(' '),
|
||||
'weather': weather,
|
||||
})
|
||||
|
||||
# postion report (regular or compressed)
|
||||
elif (packet_type in '!=/@;' or
|
||||
0 <= body.find('!') < 40): # page 28 of spec (PDF)
|
||||
|
|
@ -243,10 +259,19 @@ def parse(packet):
|
|||
logger.debug("Parsed as normal position report")
|
||||
else:
|
||||
raise ParseError("invalid format")
|
||||
|
||||
# decode comment
|
||||
body, result = _parse_comment(body)
|
||||
parsed.update(result)
|
||||
# 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({
|
||||
|
|
@ -396,9 +421,8 @@ def _parse_timestamp(body, packet_type=''):
|
|||
|
||||
def _parse_comment(body):
|
||||
parsed = {}
|
||||
|
||||
# attempt to parse remaining part of the packet (comment field)
|
||||
# try CRS/SPD/
|
||||
# try CRS/SPD
|
||||
match = re.findall(r"^([0-9]{3})/([0-9]{3})", body)
|
||||
if match:
|
||||
cse, spd = match[0]
|
||||
|
|
@ -926,3 +950,55 @@ def _parse_normal(body):
|
|||
})
|
||||
|
||||
return (body, parsed)
|
||||
|
||||
|
||||
def _parse_weather_data(body):
|
||||
wind_multiplier = 0.44704
|
||||
rain_multiplier = 0.254
|
||||
|
||||
key_map = {
|
||||
'g': 'wind_gust',
|
||||
'c': 'wind_direction',
|
||||
't': 'temperature',
|
||||
'S': 'wind_speed',
|
||||
'r': 'rain_1h',
|
||||
'p': 'rain_24h',
|
||||
'P': 'rain_since_midnight',
|
||||
'h': 'humidity',
|
||||
'b': 'pressure',
|
||||
'l': 'luminosity',
|
||||
'L': 'luminosity',
|
||||
's': 'snow',
|
||||
'#': 'rain_raw',
|
||||
}
|
||||
val_map = {
|
||||
'g': lambda x: int(x) * wind_multiplier,
|
||||
'c': lambda x: int(x),
|
||||
'S': lambda x: int(x) * wind_multiplier,
|
||||
't': lambda x: (float(x) - 32) / 1.8,
|
||||
'r': 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),
|
||||
'b': lambda x: float(x) / 10,
|
||||
'l': lambda x: int(x) + 1000,
|
||||
'L': lambda x: int(x),
|
||||
's': lambda x: float(x) * 25.4,
|
||||
'#': lambda x: int(x),
|
||||
}
|
||||
|
||||
parsed = {}
|
||||
|
||||
# parse weather data
|
||||
body = re.sub(r"^([0-9]{3})/([0-9]{3})", "c\\1s\\2", body)
|
||||
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)
|
||||
data = map(lambda x: (key_map[x[0]] , val_map[x[0]](x[1:])), data)
|
||||
|
||||
parsed.update(dict(data))
|
||||
|
||||
# strip weather data
|
||||
body = re.sub(r"([cSgtrpPlLs#][0-9\-\. ]{3}|h[0-9\. ]{2}|b[0-9\. ]{5})", '', body)
|
||||
|
||||
return (body, parsed)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,260 @@
|
|||
import unittest2 as unittest
|
||||
|
||||
from aprslib.parsing import _parse_weather_data
|
||||
from aprslib.parsing import parse
|
||||
|
||||
wind_multiplier = 0.44704
|
||||
mm_multiplier = 0.254
|
||||
|
||||
class ParseCommentWeather(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.maxDiff = None
|
||||
|
||||
def test_wind(self):
|
||||
# misc value
|
||||
expected = "", {
|
||||
"wind_speed": (9 * wind_multiplier),
|
||||
"wind_direction": 9
|
||||
}
|
||||
result = _parse_weather_data("009/009")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# Daft result but possible
|
||||
expected = "", {
|
||||
"wind_speed": (999 * wind_multiplier),
|
||||
"wind_direction": 999
|
||||
}
|
||||
result = _parse_weather_data("999/999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
#Positionless packet
|
||||
expected = "", {
|
||||
"wind_speed": float(9 * wind_multiplier),
|
||||
"wind_direction": 9
|
||||
}
|
||||
result = _parse_weather_data("c009s009")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# Daft result but possible
|
||||
expected = "", {
|
||||
"wind_speed": (999 * wind_multiplier),
|
||||
"wind_direction": 999
|
||||
}
|
||||
result = _parse_weather_data("c999s999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_temp(self):
|
||||
# Min
|
||||
expected = "", {
|
||||
"temperature": float((-99.0 - 32) / 1.8)
|
||||
}
|
||||
result = _parse_weather_data("t-99")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# Misc
|
||||
expected = "", {
|
||||
"temperature": -40.0
|
||||
}
|
||||
result = _parse_weather_data("t-40")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# Zero F
|
||||
expected = "", {
|
||||
"temperature": -17.77777777777778
|
||||
}
|
||||
result = _parse_weather_data("t000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# Daft, but possible
|
||||
expected = "", {
|
||||
"temperature": 537.2222222222222
|
||||
}
|
||||
result = _parse_weather_data("t999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_rain_1h(self):
|
||||
expected = "", {
|
||||
"rain_1h": 0.0
|
||||
}
|
||||
result = _parse_weather_data("r000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"rain_1h": float(999 * mm_multiplier)
|
||||
}
|
||||
result = _parse_weather_data("r999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_rain_24h(self):
|
||||
expected = "", {
|
||||
"rain_24h": 0.0
|
||||
}
|
||||
result = _parse_weather_data("p000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"rain_24h": float(999 * mm_multiplier)
|
||||
}
|
||||
result = _parse_weather_data("p999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_rain_since_midnight(self):
|
||||
expected = "", {
|
||||
"rain_since_midnight": 0.0
|
||||
}
|
||||
result = _parse_weather_data("P000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"rain_since_midnight": float(999 * mm_multiplier)
|
||||
}
|
||||
result = _parse_weather_data("P999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_humidity(self):
|
||||
expected = "", {
|
||||
"humidity": 0.0
|
||||
}
|
||||
result = _parse_weather_data("h00")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"humidity": 99
|
||||
}
|
||||
result = _parse_weather_data("h99")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_pressure(self):
|
||||
expected = "", {
|
||||
"pressure": 0.0
|
||||
}
|
||||
result = _parse_weather_data("b00000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"pressure": 9999.9
|
||||
}
|
||||
result = _parse_weather_data("b99999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_luminosity(self):
|
||||
expected = "", {
|
||||
"luminosity": 000
|
||||
}
|
||||
result = _parse_weather_data("L000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"luminosity": 999
|
||||
}
|
||||
result = _parse_weather_data("L999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"luminosity": 1123
|
||||
}
|
||||
result = _parse_weather_data("l123")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"luminosity": 1999
|
||||
}
|
||||
result = _parse_weather_data("l999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_snow(self):
|
||||
expected = "", {
|
||||
"snow": 000
|
||||
}
|
||||
result = _parse_weather_data("s...s000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"snow": float(5.5 * 25.4)
|
||||
}
|
||||
result = _parse_weather_data("s...s5.5")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"snow": float(999 * 25.4)
|
||||
}
|
||||
result = _parse_weather_data("s...s999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_rain_raw(self):
|
||||
expected = "", {
|
||||
"rain_raw": 000
|
||||
}
|
||||
result = _parse_weather_data("#000")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
expected = "", {
|
||||
"rain_raw": 999
|
||||
}
|
||||
result = _parse_weather_data("#999")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# Not possible in real world (rain and snow measurements)
|
||||
def test_positionless_packet(self):
|
||||
|
||||
expected = {
|
||||
'comment': 'wRSW',
|
||||
'format': 'wx',
|
||||
'from': 'A',
|
||||
'path': [],
|
||||
'raw': 'A>B:_10090556c220s004g005t077r010p020P030h50b09900s5.5wRSW',
|
||||
'to': 'B',
|
||||
'via': '',
|
||||
'wx_raw_timestamp': '10090556',
|
||||
"weather": {
|
||||
"pressure": 990.0,
|
||||
"humidity": 50,
|
||||
"rain_1h": float(010.0 * mm_multiplier),
|
||||
"rain_24h": float(020.0 * mm_multiplier),
|
||||
"rain_since_midnight": float(030.0 * mm_multiplier),
|
||||
"snow": float(5.5 * 25.4),
|
||||
"temperature": float((77.0 - 32) / 1.8),
|
||||
"wind_direction": 220,
|
||||
"wind_gust": 5.0 * wind_multiplier,
|
||||
"wind_speed": 4.0 * wind_multiplier
|
||||
}
|
||||
}
|
||||
|
||||
packet = "A>B:_10090556c220s004g005t077r010p020P030h50b09900s5.5wRSW"
|
||||
|
||||
self.assertEqual(expected, parse(packet))
|
||||
|
||||
packet2 = "A>B:_10090556c220s112g t r h b wRSW"
|
||||
expected['raw'] = packet2
|
||||
expected['weather'] = {
|
||||
"wind_direction": 220,
|
||||
"wind_speed": 112 * wind_multiplier
|
||||
}
|
||||
|
||||
self.assertEqual(expected, parse(packet2))
|
||||
|
||||
packet3 = "A>B:_10090556c220s112g...t...r...p...P...b.....wRSW"
|
||||
expected['raw'] = packet3
|
||||
expected['weather'] = {
|
||||
"wind_direction": 220,
|
||||
"wind_speed": 112 * wind_multiplier
|
||||
}
|
||||
|
||||
self.assertEqual(expected, parse(packet3))
|
||||
|
||||
def test_position_packet(self):
|
||||
expected = "eCumulusWMR100", {
|
||||
"pressure": 1029.4,
|
||||
"humidity": 19,
|
||||
"rain_since_midnight": 0.0,
|
||||
"temperature": (48.0 - 32) / 1.8,
|
||||
"wind_direction": 319,
|
||||
"wind_gust": 4.0 * wind_multiplier,
|
||||
"wind_speed": 1 * wind_multiplier
|
||||
}
|
||||
|
||||
result = _parse_weather_data("319/001g004t048r...p P000h19b10294eCumulusWMR100")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue