From 8855a4f097dbbdcc933097ee4c0605137bde3b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Gru=CC=88ndger?= Date: Fri, 9 Oct 2020 09:23:10 +0200 Subject: [PATCH] Added support for weather data from FANET ground stations --- CHANGELOG.md | 1 + ogn/parser/aprs_comment/fanet_parser.py | 17 +++++++-- ogn/parser/aprs_comment/receiver_parser.py | 10 +++--- ogn/parser/parse.py | 41 ++++++++++++++++++---- ogn/parser/pattern.py | 3 +- ogn/parser/utils.py | 4 +++ tests/parser/test_parse_aprs.py | 27 +++++++++++++- tests/parser/test_parse_fanet.py | 8 +++++ tests/parser/valid_beacon_data/fanet.txt | 11 ++++++ tests/parser/valid_beacon_data/flarm.txt | 2 +- 10 files changed, 107 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89bc79f..440618e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # CHANGELOG ## not released +- parser: Added support for weather data from FANET ground stations - parser: Added support for latency in receiver messages (OGNSDR) (fixes #87) - parser: Added support for reference_timestamp with tzinfo (fixes #84) - parser: Fixed textual altitude part (fixes #81) diff --git a/ogn/parser/aprs_comment/fanet_parser.py b/ogn/parser/aprs_comment/fanet_parser.py index 9e804d6..bf2126b 100644 --- a/ogn/parser/aprs_comment/fanet_parser.py +++ b/ogn/parser/aprs_comment/fanet_parser.py @@ -1,5 +1,5 @@ from ogn.parser.utils import FPM_TO_MS -from ogn.parser.pattern import PATTERN_FANET_POSITION_COMMENT +from ogn.parser.pattern import PATTERN_FANET_POSITION_COMMENT, PATTERN_FANET_STATUS_COMMENT from .base import BaseParser @@ -7,10 +7,11 @@ from .base import BaseParser class FanetParser(BaseParser): def __init__(self): self.beacon_type = 'fanet' - self.position_parser = PATTERN_FANET_POSITION_COMMENT + self.position_pattern = PATTERN_FANET_POSITION_COMMENT + self.status_pattern = PATTERN_FANET_STATUS_COMMENT def parse_position(self, aprs_comment): - match = self.position_parser.match(aprs_comment) + match = self.position_pattern.match(aprs_comment) result = {} if match.group('details'): result.update({ @@ -21,3 +22,13 @@ class FanetParser(BaseParser): }) if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS return result + + def parse_status(self, aprs_comment): + match = self.status_pattern.match(aprs_comment) + result = {} + if match.group('fanet_name'): result['fanet_name'] = match.group('fanet_name') + if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality')) + if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset')) + if match.group('error_count'): result['error_count'] = int(match.group('error_count')) + + return result diff --git a/ogn/parser/aprs_comment/receiver_parser.py b/ogn/parser/aprs_comment/receiver_parser.py index 9fd10e9..d5a9e08 100644 --- a/ogn/parser/aprs_comment/receiver_parser.py +++ b/ogn/parser/aprs_comment/receiver_parser.py @@ -35,11 +35,11 @@ class ReceiverParser(BaseParser): if match.group('senders'): result['senders_total'] = int(match.group('senders')) if match.group('rf_correction_manual'): result['rec_crystal_correction'] = int(match.group('rf_correction_manual')) if match.group('rf_correction_automatic'): result['rec_crystal_correction_fine'] = float(match.group('rf_correction_automatic')) - if match.group('signal_quality'): result['rec_input_noise'] = float(match.group('signal_quality')) - if match.group('senders_signal_quality'): result['senders_signal'] = float(match.group('senders_signal_quality')) - if match.group('senders_messages'): result['senders_messages'] = float(match.group('senders_messages')) - if match.group('good_senders_signal_quality'): result['good_senders_signal'] = float(match.group('good_senders_signal_quality')) - if match.group('good_senders'): result['good_senders'] = float(match.group('good_senders')) + if match.group('signal_quality'): result['rec_input_noise'] = float(match.group('signal_quality')) + if match.group('senders_signal_quality'): result['senders_signal'] = float(match.group('senders_signal_quality')) + if match.group('senders_messages'): result['senders_messages'] = float(match.group('senders_messages')) + if match.group('good_senders_signal_quality'): result['good_senders_signal'] = float(match.group('good_senders_signal_quality')) + if match.group('good_senders'): result['good_senders'] = float(match.group('good_senders')) if match.group('good_and_bad_senders'): result['good_and_bad_senders'] = float(match.group('good_and_bad_senders')) return result diff --git a/ogn/parser/parse.py b/ogn/parser/parse.py index 256097e..bc23ca6 100644 --- a/ogn/parser/parse.py +++ b/ogn/parser/parse.py @@ -1,8 +1,8 @@ import re from datetime import datetime -from ogn.parser.utils import createTimestamp, parseAngle, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER -from ogn.parser.pattern import PATTERN_APRS, PATTERN_APRS_POSITION, PATTERN_APRS_STATUS, PATTERN_SERVER +from ogn.parser.utils import createTimestamp, parseAngle, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER, fahrenheit_to_celsius +from ogn.parser.pattern import PATTERN_APRS, PATTERN_APRS_POSITION, PATTERN_APRS_POSITION_WEATHER, PATTERN_APRS_STATUS, PATTERN_SERVER from ogn.parser.exceptions import AprsParseError from ogn.parser.aprs_comment.ogn_parser import OgnParser @@ -52,7 +52,6 @@ def parse_aprs(message, reference_timestamp=None): result.update({ 'comment': message, 'aprs_type': 'comment'}) - else: match = re.search(PATTERN_APRS, message) if match: @@ -74,12 +73,42 @@ def parse_aprs(message, reference_timestamp=None): 'longitude': parseAngle(match_position.group('longitude') + (match_position.group('longitude_enhancement') or '0')) * # noqa: W504 (-1 if match_position.group('longitude_sign') == 'W' else 1), 'symbolcode': match_position.group('symbol'), + 'track': int(match_position.group('course')) if match_position.group('course_extension') else None, 'ground_speed': int(match_position.group('ground_speed')) * KNOTS_TO_MS / KPH_TO_MS if match_position.group('ground_speed') else None, 'altitude': int(match_position.group('altitude')) * FEETS_TO_METER if match_position.group('altitude') else None, - 'comment': match_position.group('comment') if match_position.group('comment') else ""}) - else: - raise AprsParseError(message) + + 'comment': match_position.group('comment') if match_position.group('comment') else "", + }) + return result + + match_position_weather = re.search(PATTERN_APRS_POSITION_WEATHER, aprs_body) + if match_position_weather: + result.update({ + 'name': match.group('callsign'), + 'dstcall': match.group('dstcall'), + 'relay': match.group('relay') if match.group('relay') else None, + 'receiver_name': match.group('receiver'), + 'timestamp': createTimestamp(match_position_weather.group('time'), reference_timestamp), + 'latitude': parseAngle('0' + match_position_weather.group('latitude')) * # noqa: W504 + (-1 if match_position_weather.group('latitude_sign') == 'S' else 1), + 'symboltable': match_position_weather.group('symbol_table'), + 'longitude': parseAngle(match_position_weather.group('longitude')) * # noqa: W504 + (-1 if match_position_weather.group('longitude_sign') == 'W' else 1), + 'symbolcode': match_position_weather.group('symbol'), + + 'wind_direction': int(match_position_weather.group('wind_direction')) if match_position_weather.group('wind_direction') != '...' else None, + 'wind_speed': int(match_position_weather.group('wind_speed')) * KNOTS_TO_MS / KPH_TO_MS if match_position_weather.group('wind_speed') != '...' else None, + 'wind_speed_peak': int(match_position_weather.group('wind_speed_peak')) * KNOTS_TO_MS / KPH_TO_MS if match_position_weather.group('wind_speed_peak') != '...' else None, + 'temperature': fahrenheit_to_celsius(float(match_position_weather.group('temperature'))) if match_position_weather.group('temperature') != '...' else None, + 'humidity': int(match_position_weather.group('humidity')) * 0.01 if match_position_weather.group('humidity') else None, + 'barometric_pressure': int(match_position_weather.group('barometric_pressure')) if match_position_weather.group('barometric_pressure') else None, + + 'comment': match_position_weather.group('comment') if match_position_weather.group('comment') else "", + }) + return result + + raise AprsParseError(message) elif aprs_type == 'status': match_status = re.search(PATTERN_APRS_STATUS, aprs_body) if match_status: diff --git a/ogn/parser/pattern.py b/ogn/parser/pattern.py index 55e0ee0..cd38500 100644 --- a/ogn/parser/pattern.py +++ b/ogn/parser/pattern.py @@ -2,6 +2,7 @@ import re PATTERN_APRS = re.compile(r"^(?P.+?)>(?P[A-Z0-9]+),((?P[A-Za-z0-9]+)\*)?.*,(?P.+?):(?P(.))(?P.*)$") PATTERN_APRS_POSITION = re.compile(r"^(?P