From a2e7d508ca52dd658a7977581ac44e2823d90420 Mon Sep 17 00:00:00 2001 From: Stefano Lande Date: Wed, 24 Aug 2022 18:08:50 +0200 Subject: [PATCH 1/4] implement telemetry report parsing --- .gitignore | 1 + aprslib/parsing/__init__.py | 44 +++++++++++++++------------- aprslib/parsing/telemetry.py | 44 ++++++++++++++++++++-------- docs/parse_formats.rst | 28 ++++++++++++++++++ tests/test_parse_telemetry_report.py | 22 ++++++++++++++ 5 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 tests/test_parse_telemetry_report.py diff --git a/.gitignore b/.gitignore index 8e10410..856d786 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist *.egg-info env3 env2 +.idea diff --git a/aprslib/parsing/__init__.py b/aprslib/parsing/__init__.py index e8e37c4..fce7aae 100644 --- a/aprslib/parsing/__init__.py +++ b/aprslib/parsing/__init__.py @@ -45,25 +45,25 @@ def detect(x): 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', + '#': 'raw weather report', + '$': 'raw gps', + '%': 'agrelo', + '&': 'reserved', + '(': 'unused', + ')': 'item report', + '*': 'complete weather report', + '+': 'reserved', + '-': 'unused', + '.': 'reserved', + '<': 'station capabilities', + '?': 'general query format', + '[': 'maidenhead locator beacon', + '\\': 'unused', + ']': 'unused', + '^': 'unused', } + def _unicode_packet(packet): # attempt utf-8 try: @@ -114,7 +114,7 @@ def parse(packet): parsed = { 'raw': packet, - } + } # parse head try: @@ -149,7 +149,7 @@ def parse(packet): parsed.update({ 'format': 'beacon', 'text': packet_type + body, - }) + }) logger.debug("Parsed ok.") return parsed @@ -208,6 +208,10 @@ def _try_toparse_body(packet_type, body, parsed): body, result = parse_position(packet_type, body) + elif packet_type == "T": + logger.debug("Attempting to parse as telemetry report") + + body, result = parse_telemetry_report(body) + # we are done parsed.update(result) - diff --git a/aprslib/parsing/telemetry.py b/aprslib/parsing/telemetry.py index b3f35b5..ebe5b14 100644 --- a/aprslib/parsing/telemetry.py +++ b/aprslib/parsing/telemetry.py @@ -4,9 +4,10 @@ from aprslib.parsing import logger __all__ = [ - 'parse_comment_telemetry', - 'parse_telemetry_config', - ] + 'parse_comment_telemetry', + 'parse_telemetry_config', + 'parse_telemetry_report' +] def parse_comment_telemetry(text): @@ -23,19 +24,19 @@ def parse_comment_telemetry(text): temp = [0] * 7 for i in range(7): - temp[i] = base91.to_decimal(telemetry[i*2:i*2+2]) + temp[i] = base91.to_decimal(telemetry[i * 2:i * 2 + 2]) parsed.update({ 'telemetry': { 'seq': temp[0], 'vals': temp[1:6] - } - }) + } + }) if temp[6] != '': parsed['telemetry'].update({ 'bits': "{0:08b}".format(temp[6] & 0xFF)[::-1] - }) + }) return (text, parsed) @@ -62,14 +63,14 @@ def parse_telemetry_config(body): parsed.update({ 't%s' % form: defvals - }) + }) elif form == "EQNS": eqns = body.rstrip().split(',')[:15] teqns = [0, 1, 0] * 5 for idx, val in enumerate(eqns): if not re.match(r"^([-]?\d*\.?\d+|)$", val): - raise ParseError("value at %d is not a number in %s" % (idx+1, form)) + raise ParseError("value at %d is not a number in %s" % (idx + 1, form)) else: try: val = int(val) @@ -79,11 +80,11 @@ def parse_telemetry_config(body): teqns[idx] = val # group values in 5 list of 3 - teqns = [teqns[i*3:(i+1)*3] for i in range(5)] + teqns = [teqns[i * 3:(i + 1) * 3] for i in range(5)] parsed.update({ 't%s' % form: teqns - }) + }) elif form == "BITS": match = re.findall(r"^([01]{8}),(.{0,23})$", body.rstrip()) if not match: @@ -94,7 +95,26 @@ def parse_telemetry_config(body): parsed.update({ 't%s' % form: bits, 'title': title.strip(' ') - }) + }) return (body, parsed) + +def parse_telemetry_report(text): + temp = text.split(",") + parsed = {} + + if len(temp) == 7: + + seq = int(temp[0].replace('#', '')) + values = list(map(float, temp[1:6])) + + parsed.update({ + 'telemetry': { + 'seq': seq, + 'vals': values, + 'bits': temp[6] + } + }) + + return '', parsed diff --git a/docs/parse_formats.rst b/docs/parse_formats.rst index abeb7d1..3c401e4 100644 --- a/docs/parse_formats.rst +++ b/docs/parse_formats.rst @@ -257,6 +257,32 @@ Regular 'to': u'TOCALL', 'via': ''} + +Telemetry report +----------------------- + + +.. code:: python + + >>> aprslib.parse("FROMCALL>APDW16,WIDE1-1,qAR,TOCALL:T#165,13.21,0.39,5.10,14.94,36.12,11111100") + + { + 'raw': 'FROMCALL>APDW16,WIDE1-1,qAR,TOCALL:T#165,13.21,0.39,5.10,14.94,36.12,11111100', + 'from': 'FROMCALL', + 'to': 'APDW16', + 'path': ['WIDE1-1', 'qAR', 'TOCALL'], + 'via': 'TOCALL', + 'telemetry': + { + 'seq': 165, + 'vals': [13.21, 0.39, 5.1, 14.94, 36.12], + 'bits': '11111100' + }, + 'format': 'beacon', + 'text': 'T#165,13.21,0.39,5.10,14.94,36.12,11111100' + } + + Telemetry configuration ----------------------- @@ -296,3 +322,5 @@ Telemetry configuration 'to': 'TOCALL', 'via': ''} + + diff --git a/tests/test_parse_telemetry_report.py b/tests/test_parse_telemetry_report.py new file mode 100644 index 0000000..a2e9e4b --- /dev/null +++ b/tests/test_parse_telemetry_report.py @@ -0,0 +1,22 @@ +import unittest + +from aprslib.parsing import parse_telemetry_report + + +class ParseTelemetryReport(unittest.TestCase): + def setUp(self): + self.maxDiff = None + + def test_valid_telemetry_report(self): + packet = "#111,13.64,0.37,5.10,16.96,33.38,11110000" + expected = {'telemetry': + {'bits': '11110000', + 'seq': 111, + 'vals': [13.64, 0.37, 5.1, 16.96, 33.38]}} + + _, result = parse_telemetry_report(packet) + self.assertEqual(expected, result) + + +if __name__ == '__main__': + unittest.main() From f86c36f8fdb343c598bd8020cfe00c2a9131a9e1 Mon Sep 17 00:00:00 2001 From: Stefano Lande Date: Thu, 25 Aug 2022 16:07:48 +0200 Subject: [PATCH 2/4] improve handling of bad telemetry packets --- aprslib/parsing/telemetry.py | 14 +++++++++++--- docs/parse_formats.rst | 2 +- tests/test_parse_telemetry_report.py | 19 ++++++++++++++----- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/aprslib/parsing/telemetry.py b/aprslib/parsing/telemetry.py index ebe5b14..50ecca7 100644 --- a/aprslib/parsing/telemetry.py +++ b/aprslib/parsing/telemetry.py @@ -101,10 +101,16 @@ def parse_telemetry_config(body): def parse_telemetry_report(text): - temp = text.split(",") parsed = {} + rest = "" - if len(temp) == 7: + match = re.findall("(^#\d{3},(\d+(\.\d+)?,){5}[01]{8}$)", text) + + if match: + logger.debug("Attempting to parse telemetry-message packet") + + temp = text.split(",") + parsed.update({'format': 'telemetry-report'}) seq = int(temp[0].replace('#', '')) values = list(map(float, temp[1:6])) @@ -116,5 +122,7 @@ def parse_telemetry_report(text): 'bits': temp[6] } }) + else: + rest = text - return '', parsed + return rest, parsed diff --git a/docs/parse_formats.rst b/docs/parse_formats.rst index 3c401e4..5b29541 100644 --- a/docs/parse_formats.rst +++ b/docs/parse_formats.rst @@ -278,7 +278,7 @@ Telemetry report 'vals': [13.21, 0.39, 5.1, 14.94, 36.12], 'bits': '11111100' }, - 'format': 'beacon', + 'format': 'telemetry-report', 'text': 'T#165,13.21,0.39,5.10,14.94,36.12,11111100' } diff --git a/tests/test_parse_telemetry_report.py b/tests/test_parse_telemetry_report.py index a2e9e4b..3f48ea5 100644 --- a/tests/test_parse_telemetry_report.py +++ b/tests/test_parse_telemetry_report.py @@ -8,15 +8,24 @@ def setUp(self): self.maxDiff = None def test_valid_telemetry_report(self): - packet = "#111,13.64,0.37,5.10,16.96,33.38,11110000" - expected = {'telemetry': - {'bits': '11110000', - 'seq': 111, - 'vals': [13.64, 0.37, 5.1, 16.96, 33.38]}} + packet = "#111,13.64,0.37,5,16.96,33.38,11110000" + expected = {'format': 'telemetry-report', + 'telemetry': + {'bits': '11110000', + 'seq': 111, + 'vals': [13.64, 0.37, 5, 16.96, 33.38]}} _, result = parse_telemetry_report(packet) self.assertEqual(expected, result) + def test_invalid_telemetry_report(self): + packet = "#111a,Nan,0.37,5.10,16.96,33.38,11110000" + expected = {} + + rest, result = parse_telemetry_report(packet) + self.assertEqual(expected, result) + self.assertEqual(rest,packet) + if __name__ == '__main__': unittest.main() From 0a57535d5f7758df50e7bba3eae776e222c926fe Mon Sep 17 00:00:00 2001 From: Stefano Lande Date: Wed, 21 Sep 2022 09:44:09 +0200 Subject: [PATCH 3/4] remove unnecessary changes --- aprslib/parsing/__init__.py | 37 ++++++++++++++++++------------------ aprslib/parsing/telemetry.py | 18 +++++++++--------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/aprslib/parsing/__init__.py b/aprslib/parsing/__init__.py index fce7aae..b4f0b73 100644 --- a/aprslib/parsing/__init__.py +++ b/aprslib/parsing/__init__.py @@ -45,25 +45,24 @@ def detect(x): 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', - '[': 'maidenhead locator beacon', - '\\': 'unused', - ']': 'unused', - '^': 'unused', + '#':'raw weather report', + '$':'raw gps', + '%':'agrelo', + '&':'reserved', + '(':'unused', + ')':'item report', + '*':'complete weather report', + '+':'reserved', + '-':'unused', + '.':'reserved', + '<':'station capabilities', + '?':'general query format', + '[':'maidenhead locator beacon', + '\\':'unused', + ']':'unused', + '^':'unused', } - def _unicode_packet(packet): # attempt utf-8 try: @@ -114,7 +113,7 @@ def parse(packet): parsed = { 'raw': packet, - } + } # parse head try: @@ -149,7 +148,7 @@ def parse(packet): parsed.update({ 'format': 'beacon', 'text': packet_type + body, - }) + }) logger.debug("Parsed ok.") return parsed diff --git a/aprslib/parsing/telemetry.py b/aprslib/parsing/telemetry.py index 50ecca7..ccd7c60 100644 --- a/aprslib/parsing/telemetry.py +++ b/aprslib/parsing/telemetry.py @@ -24,19 +24,19 @@ def parse_comment_telemetry(text): temp = [0] * 7 for i in range(7): - temp[i] = base91.to_decimal(telemetry[i * 2:i * 2 + 2]) + temp[i] = base91.to_decimal(telemetry[i*2:i*2+2]) parsed.update({ 'telemetry': { 'seq': temp[0], 'vals': temp[1:6] - } - }) + } + }) if temp[6] != '': parsed['telemetry'].update({ 'bits': "{0:08b}".format(temp[6] & 0xFF)[::-1] - }) + }) return (text, parsed) @@ -63,14 +63,14 @@ def parse_telemetry_config(body): parsed.update({ 't%s' % form: defvals - }) + }) elif form == "EQNS": eqns = body.rstrip().split(',')[:15] teqns = [0, 1, 0] * 5 for idx, val in enumerate(eqns): if not re.match(r"^([-]?\d*\.?\d+|)$", val): - raise ParseError("value at %d is not a number in %s" % (idx + 1, form)) + raise ParseError("value at %d is not a number in %s" % (idx+1, form)) else: try: val = int(val) @@ -80,11 +80,11 @@ def parse_telemetry_config(body): teqns[idx] = val # group values in 5 list of 3 - teqns = [teqns[i * 3:(i + 1) * 3] for i in range(5)] + teqns = [teqns[i*3:(i+1)*3] for i in range(5)] parsed.update({ 't%s' % form: teqns - }) + }) elif form == "BITS": match = re.findall(r"^([01]{8}),(.{0,23})$", body.rstrip()) if not match: @@ -95,7 +95,7 @@ def parse_telemetry_config(body): parsed.update({ 't%s' % form: bits, 'title': title.strip(' ') - }) + }) return (body, parsed) From 09ac00219b7d6a380b683b61bace1c7df904e125 Mon Sep 17 00:00:00 2001 From: Stefano Lande Date: Wed, 21 Sep 2022 10:01:15 +0200 Subject: [PATCH 4/4] fix deprecation warning --- aprslib/parsing/telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aprslib/parsing/telemetry.py b/aprslib/parsing/telemetry.py index ccd7c60..1bb8fcc 100644 --- a/aprslib/parsing/telemetry.py +++ b/aprslib/parsing/telemetry.py @@ -104,7 +104,7 @@ def parse_telemetry_report(text): parsed = {} rest = "" - match = re.findall("(^#\d{3},(\d+(\.\d+)?,){5}[01]{8}$)", text) + match = re.findall(r"(^#\d{3},(\d+(\.\d+)?,){5}[01]{8}$)", text) if match: logger.debug("Attempting to parse telemetry-message packet")