Skip to content

Commit 2a27dc4

Browse files
parse_position: fix data ext and weather parsing
Fix #72
1 parent 2b139d1 commit 2a27dc4

4 files changed

Lines changed: 222 additions & 15 deletions

File tree

aprslib/parsing/common.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,24 @@ def parse_data_extentions(body):
136136
parsed = {}
137137

138138
# course speed bearing nrq
139+
# Page 27 of the spec
139140
# format: 111/222/333/444text
140-
match = re.findall(r"^([0-9 .]{3})/([0-9 .]{3})", body)
141+
match = re.findall(r"^([0-9 \.]{3})/([0-9 \.]{3})", body)
141142
if match:
142143
cse, spd = match[0]
143144
body = body[7:]
144-
parsed.update({'course': int(cse) if cse.isdigit() and 1 <= int(cse) <= 360 else 0})
145-
if spd.isdigit():
145+
if cse.isdigit() and cse != "000":
146+
parsed.update({'course': int(cse) if 1 <= int(cse) <= 360 else 0})
147+
if spd.isdigit() and spd != "000":
146148
parsed.update({'speed': int(spd)*1.852})
147149

148-
match = re.findall(r"^/([0-9 .]{3})/([0-9 .]{3})", body)
150+
# DF Report format
151+
# Page 29 of teh spec
152+
match = re.findall(r"^/([0-9 \.]{3})/([0-9 \.]{3})", body)
149153
if match:
154+
# cse=000 means stations is fixed, Page 29 of the spec
155+
if cse == '000':
156+
parsed.update({'course': 0})
150157
brg, nrq = match[0]
151158
body = body[8:]
152159
if brg.isdigit():

aprslib/parsing/position.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from aprslib import base91
44
from aprslib.exceptions import ParseError
55
from aprslib.parsing import logger
6-
from aprslib.parsing.common import parse_timestamp, parse_comment
6+
from aprslib.parsing.common import parse_timestamp, parse_comment, parse_data_extentions
77
from aprslib.parsing.weather import parse_weather_data
88

99
__all__ = [
@@ -60,6 +60,11 @@ def parse_position(packet_type, body):
6060
# check comment for weather information
6161
# Page 62 of the spec
6262
if parsed['symbol'] == '_':
63+
# attempt to parse winddir/speed
64+
# Page 92 of the spec
65+
body, result = parse_data_extentions(body)
66+
parsed.update(result)
67+
6368
logger.debug("Attempting to parse weather report from comment")
6469
body, result = parse_weather_data(body)
6570
parsed.update({

tests/test_parse_common.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,30 +256,47 @@ def test_course_speed(self):
256256
'speed': 100*1.852,
257257
})
258258

259-
def test_empty_course_speed(self):
259+
def test_course_speed_spaces(self):
260260
body = " / /text"
261261
remaining, parsed = parse_data_extentions(body)
262262

263263
self.assertEqual(remaining, '/text')
264-
self.assertEqual(parsed, {
265-
'course': 0,
266-
})
264+
self.assertEqual(parsed, {})
267265

266+
def test_course_speed_dots(self):
268267
body = ".../.../text"
269268
remaining, parsed = parse_data_extentions(body)
270269

271270
self.assertEqual(remaining, '/text')
272-
self.assertEqual(parsed, {
273-
'course': 0,
274-
})
271+
self.assertEqual(parsed, {})
272+
273+
def test_course_speed_zeros(self):
274+
body = "000/000/text"
275+
remaining, parsed = parse_data_extentions(body)
276+
277+
self.assertEqual(remaining, '/text')
278+
self.assertEqual(parsed, {})
275279

280+
def test_course_speed_valid_chars_but_invalid_values(self):
276281
body = "22./33 /text"
277282
remaining, parsed = parse_data_extentions(body)
278283

279284
self.assertEqual(remaining, '/text')
280-
self.assertEqual(parsed, {
281-
'course': 0,
282-
})
285+
self.assertEqual(parsed, {})
286+
287+
def test_course_speed_invalid_chars_spd(self):
288+
body = "222/33a/text"
289+
remaining, parsed = parse_data_extentions(body)
290+
291+
self.assertEqual(remaining, '222/33a/text')
292+
self.assertEqual(parsed, {})
293+
294+
def test_course_speed_invalid_chars_cse(self):
295+
body = "22a/333/text"
296+
remaining, parsed = parse_data_extentions(body)
297+
298+
self.assertEqual(remaining, '22a/333/text')
299+
self.assertEqual(parsed, {})
283300

284301
def test_empty_bearing_nrq(self):
285302
body = "111/100/ /...text"
@@ -300,6 +317,17 @@ def test_empty_bearing_nrq(self):
300317
'speed': 100*1.852,
301318
})
302319

320+
def test_course_speed_bearing_nrq_empty_cse_speed(self):
321+
body = "000/000/234/345text"
322+
remaining, parsed = parse_data_extentions(body)
323+
324+
self.assertEqual(remaining, 'text')
325+
self.assertEqual(parsed, {
326+
'course': 0,
327+
'bearing': 234,
328+
'nrq': 345,
329+
})
330+
303331
def test_course_speed_bearing_nrq(self):
304332
body = "123/100/234/345text"
305333
remaining, parsed = parse_data_extentions(body)

tests/test_parse_position.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import unittest
2+
3+
from aprslib.parsing import parse_position
4+
5+
wind_multiplier = 0.44704
6+
mm_multiplier = 0.254
7+
8+
class ParsePositionDataExtAndWeather(unittest.TestCase):
9+
def setUp(self):
10+
self.maxDiff = None
11+
12+
def test_position_packet_only_weather_valid(self):
13+
packet_type = '@'
14+
packet = "092345z4903.50N/07201.75W_g000t066r000p000...dUII"
15+
expected = {
16+
'messagecapable': True,
17+
'raw_timestamp': '092345z',
18+
'timestamp': 1657410300,
19+
'format': 'uncompressed',
20+
'posambiguity': 0,
21+
'symbol': '_',
22+
'symbol_table': '/',
23+
'latitude': 49.05833333333333,
24+
'longitude': -72.02916666666667,
25+
'comment': '...dUII',
26+
'weather': {
27+
'wind_gust': 0.0,
28+
'temperature': 18.88888888888889,
29+
'rain_1h': 0.0,
30+
'rain_24h': 0.0
31+
}
32+
}
33+
34+
_, result = parse_position(packet_type, packet)
35+
self.assertEqual(expected, result)
36+
37+
def test_position_packet_data_ext_and_weather_valid(self):
38+
packet_type = '@'
39+
packet = "092345z4903.50N/07201.75W_090/001g000t066r000p000...dUII"
40+
expected = {
41+
'messagecapable': True,
42+
'raw_timestamp': '092345z',
43+
'timestamp': 1657410300,
44+
'format': 'uncompressed',
45+
'posambiguity': 0,
46+
'symbol': '_',
47+
'symbol_table': '/',
48+
'latitude': 49.05833333333333,
49+
'longitude': -72.02916666666667,
50+
'course': 90,
51+
'speed': 1*1.852,
52+
'comment': '...dUII',
53+
'weather': {
54+
'wind_gust': 0.0,
55+
'temperature': 18.88888888888889,
56+
'rain_1h': 0.0,
57+
'rain_24h': 0.0
58+
}
59+
}
60+
61+
_, result = parse_position(packet_type, packet)
62+
self.assertEqual(expected, result)
63+
64+
def test_position_packet_optional_speed(self):
65+
packet_type = '@'
66+
packet = "092345z4903.50N/07201.75W_090/...g000t066r000p000...dUII"
67+
expected = {
68+
'messagecapable': True,
69+
'raw_timestamp': '092345z',
70+
'timestamp': 1657410300,
71+
'format': 'uncompressed',
72+
'posambiguity': 0,
73+
'symbol': '_',
74+
'symbol_table': '/',
75+
'latitude': 49.05833333333333,
76+
'longitude': -72.02916666666667,
77+
'course': 90,
78+
'comment': '...dUII',
79+
'weather': {
80+
'wind_gust': 0.0,
81+
'temperature': 18.88888888888889,
82+
'rain_1h': 0.0,
83+
'rain_24h': 0.0
84+
}
85+
}
86+
87+
_, result = parse_position(packet_type, packet)
88+
self.assertEqual(expected, result)
89+
90+
def test_position_packet_optional_course(self):
91+
packet_type = '@'
92+
packet = "092345z4903.50N/07201.75W_ /001g000t066r000p000...dUII"
93+
expected = {
94+
'messagecapable': True,
95+
'raw_timestamp': '092345z',
96+
'timestamp': 1657410300,
97+
'format': 'uncompressed',
98+
'posambiguity': 0,
99+
'symbol': '_',
100+
'symbol_table': '/',
101+
'latitude': 49.05833333333333,
102+
'longitude': -72.02916666666667,
103+
'speed': 1*1.852,
104+
'comment': '...dUII',
105+
'weather': {
106+
'wind_gust': 0.0,
107+
'temperature': 18.88888888888889,
108+
'rain_1h': 0.0,
109+
'rain_24h': 0.0
110+
}
111+
}
112+
113+
_, result = parse_position(packet_type, packet)
114+
self.assertEqual(expected, result)
115+
116+
def test_position_packet_optional_speed_and_course(self):
117+
packet_type = '@'
118+
packet = "092345z4903.50N/07201.75W_.../...g000t066r000p000...dUII"
119+
expected = {
120+
'messagecapable': True,
121+
'raw_timestamp': '092345z',
122+
'timestamp': 1657410300,
123+
'format': 'uncompressed',
124+
'posambiguity': 0,
125+
'symbol': '_',
126+
'symbol_table': '/',
127+
'latitude': 49.05833333333333,
128+
'longitude': -72.02916666666667,
129+
'comment': '...dUII',
130+
'weather': {
131+
'wind_gust': 0.0,
132+
'temperature': 18.88888888888889,
133+
'rain_1h': 0.0,
134+
'rain_24h': 0.0
135+
}
136+
}
137+
138+
_, result = parse_position(packet_type, packet)
139+
self.assertEqual(expected, result)
140+
def test_position_packet_optional_course(self):
141+
packet_type = '@'
142+
packet = "092345z4903.50N/07201.75W_ /001g000t066r000p000...dUII"
143+
expected = {
144+
'messagecapable': True,
145+
'raw_timestamp': '092345z',
146+
'timestamp': 1657410300,
147+
'format': 'uncompressed',
148+
'posambiguity': 0,
149+
'symbol': '_',
150+
'symbol_table': '/',
151+
'latitude': 49.05833333333333,
152+
'longitude': -72.02916666666667,
153+
'speed': 1*1.852,
154+
'comment': '...dUII',
155+
'weather': {
156+
'wind_gust': 0.0,
157+
'temperature': 18.88888888888889,
158+
'rain_1h': 0.0,
159+
'rain_24h': 0.0
160+
}
161+
}
162+
163+
_, result = parse_position(packet_type, packet)
164+
self.assertEqual(expected, result)
165+
166+
if __name__ == '__main__':
167+
unittest.main()

0 commit comments

Comments
 (0)