diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d8d0a..738f718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +## 2.0.0: unreleased +- parser: use rust parser as default + ## 1.3.3: - 2025-05-21 - parser: use rust parser with option "use_rust_parser=True" (default for v2.0.0) diff --git a/ogn/parser/__init__.py b/ogn/parser/__init__.py index 7e0f479..c501c44 100644 --- a/ogn/parser/__init__.py +++ b/ogn/parser/__init__.py @@ -1,3 +1,3 @@ from ogn.parser import parse as parse_module # noqa: F40 --- only for test functions. Without this a mock of parse would mock the function instead of the module -from ogn.parser.parse import parse, parse_aprs, parse_comment # noqa: F401 +from ogn.parser.parse import parse # noqa: F401 from ogn.parser.exceptions import ParseError, AprsParseError, OgnParseError # noqa: F401 diff --git a/ogn/parser/aprs_comment/__init__.py b/ogn/parser/aprs_comment/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ogn/parser/aprs_comment/base.py b/ogn/parser/aprs_comment/base.py deleted file mode 100644 index 5fbbd12..0000000 --- a/ogn/parser/aprs_comment/base.py +++ /dev/null @@ -1,19 +0,0 @@ -class BaseParser(): - def __init__(self): - self.beacon_type = 'unknown' - - def parse(self, aprs_comment, aprs_type): - if aprs_type.startswith('position'): - data = self.parse_position(aprs_comment) - elif aprs_type.startswith('status'): - data = self.parse_status(aprs_comment) - else: - raise ValueError(f"aprs_type '{aprs_type}' unknown") - data.update({'beacon_type': self.beacon_type}) - return data - - def parse_position(self, aprs_comment): - raise NotImplementedError(f"Position parser for parser '{self.beacon_type}' not yet implemented") - - def parse_status(self, aprs_comment): - raise NotImplementedError(f"Status parser for parser '{self.beacon_type}' not yet implemented") diff --git a/ogn/parser/aprs_comment/fanet_parser.py b/ogn/parser/aprs_comment/fanet_parser.py deleted file mode 100644 index bf2126b..0000000 --- a/ogn/parser/aprs_comment/fanet_parser.py +++ /dev/null @@ -1,34 +0,0 @@ -from ogn.parser.utils import FPM_TO_MS -from ogn.parser.pattern import PATTERN_FANET_POSITION_COMMENT, PATTERN_FANET_STATUS_COMMENT - -from .base import BaseParser - - -class FanetParser(BaseParser): - def __init__(self): - self.beacon_type = 'fanet' - self.position_pattern = PATTERN_FANET_POSITION_COMMENT - self.status_pattern = PATTERN_FANET_STATUS_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - result = {} - if match.group('details'): - result.update({ - 'address_type': int(match.group('details'), 16) & 0b00000011, - 'aircraft_type': (int(match.group('details'), 16) & 0b01111100) >> 2, - 'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1, - 'address': match.group('address') - }) - 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/flarm_parser.py b/ogn/parser/aprs_comment/flarm_parser.py deleted file mode 100644 index 6e9f147..0000000 --- a/ogn/parser/aprs_comment/flarm_parser.py +++ /dev/null @@ -1,40 +0,0 @@ -from ogn.parser.pattern import PATTERN_FLARM_POSITION_COMMENT -from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS - -from .base import BaseParser - - -class FlarmParser(BaseParser): - def __init__(self): - self.beacon_type = 'flarm' - self.position_pattern = PATTERN_FLARM_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - - result = {} - if match.group('details'): - result.update({ - 'address_type': int(match.group('details'), 16) & 0b00000011, - 'aircraft_type': (int(match.group('details'), 16) & 0b00111100) >> 2, - 'no-tracking': (int(match.group('details'), 16) & 0b01000000) >> 6 == 1, - 'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1, - 'address': match.group('address'), - }) - if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS - if match.group('turn_rate'): result['turn_rate'] = float(match.group('turn_rate')) * HPM_TO_DEGS - if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality')) - if match.group('error_count'): result['error_count'] = int(match.group('error_count')) - if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset')) - if match.group('gps_quality'): - result.update({ - 'gps_quality': { - 'horizontal': int(match.group('gps_quality_horizontal')), - 'vertical': int(match.group('gps_quality_vertical')) - } - }) - if match.group('software_version'): result['software_version'] = float(match.group('software_version')) - if match.group('hardware_version'): result['hardware_version'] = int(match.group('hardware_version'), 16) - if match.group('real_address'): result['real_address'] = match.group('real_address') - if match.group('signal_power'): result['signal_power'] = float(match.group('signal_power')) - return result diff --git a/ogn/parser/aprs_comment/generic_parser.py b/ogn/parser/aprs_comment/generic_parser.py deleted file mode 100644 index 775ec08..0000000 --- a/ogn/parser/aprs_comment/generic_parser.py +++ /dev/null @@ -1,12 +0,0 @@ -from .base import BaseParser - - -class GenericParser(BaseParser): - def __init__(self, beacon_type='unknown'): - self.beacon_type = beacon_type - - def parse_position(self, aprs_comment): - return {'comment': aprs_comment} - - def parse_status(self, aprs_comment): - return {'comment': aprs_comment} diff --git a/ogn/parser/aprs_comment/inreach_parser.py b/ogn/parser/aprs_comment/inreach_parser.py deleted file mode 100644 index 84861b3..0000000 --- a/ogn/parser/aprs_comment/inreach_parser.py +++ /dev/null @@ -1,16 +0,0 @@ -from ogn.parser.pattern import PATTERN_INREACH_POSITION_COMMENT - -from .base import BaseParser - - -class InreachParser(BaseParser): - def __init__(self): - self.beacon_type = 'inreach' - self.position_pattern = PATTERN_INREACH_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - return {'address': match.group('id'), - 'model': match.group('model') if match.group('model') else None, - 'status': match.group('status') == 'True' if match.group('status') else None, - 'pilot_name': match.group('pilot_name') if match.group('pilot_name') else None} diff --git a/ogn/parser/aprs_comment/lt24_parser.py b/ogn/parser/aprs_comment/lt24_parser.py deleted file mode 100644 index b7aec66..0000000 --- a/ogn/parser/aprs_comment/lt24_parser.py +++ /dev/null @@ -1,16 +0,0 @@ -from ogn.parser.utils import FPM_TO_MS -from ogn.parser.pattern import PATTERN_LT24_POSITION_COMMENT - -from .base import BaseParser - - -class LT24Parser(BaseParser): - def __init__(self): - self.beacon_type = 'lt24' - self.position_pattern = PATTERN_LT24_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - return {'lt24_id': match.group('lt24_id'), - 'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None, - 'source': match.group('source') if match.group('source') else None} diff --git a/ogn/parser/aprs_comment/microtrak_parser.py b/ogn/parser/aprs_comment/microtrak_parser.py deleted file mode 100644 index 702a218..0000000 --- a/ogn/parser/aprs_comment/microtrak_parser.py +++ /dev/null @@ -1,23 +0,0 @@ -from ogn.parser.pattern import PATTERN_MICROTRAK_POSITION_COMMENT - -from .base import BaseParser - - -class MicrotrakParser(BaseParser): - def __init__(self): - self.beacon_type = 'microtrak' - self.position_pattern = PATTERN_MICROTRAK_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - - result = {} - if match.group('details'): - result.update({ - 'address_type': int(match.group('details'), 16) & 0b00000011, - 'aircraft_type': (int(match.group('details'), 16) & 0b00111100) >> 2, - 'no-tracking': (int(match.group('details'), 16) & 0b01000000) >> 6 == 1, - 'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1, - 'address': match.group('address'), - }) - return result diff --git a/ogn/parser/aprs_comment/naviter_parser.py b/ogn/parser/aprs_comment/naviter_parser.py deleted file mode 100644 index b18e60c..0000000 --- a/ogn/parser/aprs_comment/naviter_parser.py +++ /dev/null @@ -1,21 +0,0 @@ -from ogn.parser.pattern import PATTERN_NAVITER_POSITION_COMMENT -from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS - -from .base import BaseParser - - -class NaviterParser(BaseParser): - def __init__(self): - self.beacon_type = 'naviter' - self.position_pattern = PATTERN_NAVITER_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - return {'stealth': (int(match.group('details'), 16) & 0b1000000000000000) >> 15 == 1, - 'do_not_track': (int(match.group('details'), 16) & 0b0100000000000000) >> 14 == 1, - 'aircraft_type': (int(match.group('details'), 16) & 0b0011110000000000) >> 10, - 'address_type': (int(match.group('details'), 16) & 0b0000001111110000) >> 4, - 'reserved': (int(match.group('details'), 16) & 0b0000000000001111), - 'address': match.group('address'), - 'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None, - 'turn_rate': float(match.group('turn_rate')) * HPM_TO_DEGS if match.group('turn_rate') else None} diff --git a/ogn/parser/aprs_comment/ogn_parser.py b/ogn/parser/aprs_comment/ogn_parser.py deleted file mode 100644 index 4d90a51..0000000 --- a/ogn/parser/aprs_comment/ogn_parser.py +++ /dev/null @@ -1,92 +0,0 @@ -from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS -from ogn.parser.pattern import PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON - -from .base import BaseParser - - -class OgnParser(BaseParser): - def __init__(self): - self.beacon_type = None - self.aircraft_pattern = PATTERN_AIRCRAFT_BEACON - self.receiver_pattern = PATTERN_RECEIVER_BEACON - - def parse(self, aprs_comment, aprs_type): - if not aprs_comment: - return {'beacon_type': 'aprs_receiver'} - - ab_data = self.parse_aircraft_beacon(aprs_comment) - if ab_data: - ab_data.update({'beacon_type': 'aprs_aircraft'}) - return ab_data - - rb_data = self.parse_receiver_beacon(aprs_comment) - if rb_data: - rb_data.update({'beacon_type': 'aprs_receiver'}) - return rb_data - else: - return {'user_comment': aprs_comment, - 'beacon_type': 'aprs_receiver'} - - def parse_aircraft_beacon(self, aprs_comment): - match = self.aircraft_pattern.match(aprs_comment) - if match: - result = {} - if match.group('details'): - result.update({ - 'address_type': int(match.group('details'), 16) & 0b00000011, - 'aircraft_type': (int(match.group('details'), 16) & 0b00111100) >> 2, - 'no-tracking': (int(match.group('details'), 16) & 0b01000000) >> 6 == 1, - 'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1, - 'address': match.group('address'), - }) - if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS - if match.group('turn_rate'): result['turn_rate'] = float(match.group('turn_rate')) * HPM_TO_DEGS - if match.group('flight_level'): result['flightlevel'] = float(match.group('flight_level')) - if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality')) - if match.group('errors'): result['error_count'] = int(match.group('errors')) - if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset')) - if match.group('gps_quality'): - result.update({ - 'gps_quality': { - 'horizontal': int(match.group('gps_quality_horizontal')), - 'vertical': int(match.group('gps_quality_vertical')) - } - }) - if match.group('flarm_software_version'): result['software_version'] = float(match.group('flarm_software_version')) - if match.group('flarm_hardware_version'): result['hardware_version'] = int(match.group('flarm_hardware_version'), 16) - if match.group('flarm_id'): result['real_address'] = match.group('flarm_id') - if match.group('signal_power'): result['signal_power'] = float(match.group('signal_power')) - if match.group('proximity'): result['proximity'] = [hear[4:] for hear in match.group('proximity').split(' ')] - return result - else: - return None - - def parse_receiver_beacon(self, aprs_comment): - match = self.receiver_pattern.match(aprs_comment) - if match: - result = { - 'version': match.group('version'), - 'platform': match.group('platform'), - 'cpu_load': float(match.group('cpu_load')), - 'free_ram': float(match.group('ram_free')), - 'total_ram': float(match.group('ram_total')), - 'ntp_error': float(match.group('ntp_offset')), - 'rt_crystal_correction': float(match.group('ntp_correction')) - } - if match.group('voltage'): result['voltage'] = float(match.group('voltage')) - if match.group('amperage'): result['amperage'] = float(match.group('amperage')) - if match.group('cpu_temperature'): result['cpu_temp'] = float(match.group('cpu_temperature')) - if match.group('visible_senders'): result['senders_visible'] = int(match.group('visible_senders')) - if match.group('senders'): result['senders_total'] = int(match.group('senders')) - if match.group('latency'): result['latency'] = float(match.group('latency')) - 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('good_and_bad_senders'): result['good_and_bad_senders'] = float(match.group('good_and_bad_senders')) - return result - else: - return None diff --git a/ogn/parser/aprs_comment/receiver_parser.py b/ogn/parser/aprs_comment/receiver_parser.py deleted file mode 100644 index d5a9e08..0000000 --- a/ogn/parser/aprs_comment/receiver_parser.py +++ /dev/null @@ -1,45 +0,0 @@ -from ogn.parser.pattern import PATTERN_RECEIVER_POSITION_COMMENT, PATTERN_RECEIVER_STATUS_COMMENT - -from .base import BaseParser - - -class ReceiverParser(BaseParser): - def __init__(self): - self.beacon_type = 'receiver' - self.position_pattern = PATTERN_RECEIVER_POSITION_COMMENT - self.status_pattern = PATTERN_RECEIVER_STATUS_COMMENT - - def parse_position(self, aprs_comment): - if aprs_comment is None: - return {} - else: - match = self.position_pattern.match(aprs_comment) - return {'user_comment': match.group('user_comment') if match.group('user_comment') else None} - - def parse_status(self, aprs_comment): - match = self.status_pattern.match(aprs_comment) - result = { - 'version': match.group('version'), - 'platform': match.group('platform'), - 'cpu_load': float(match.group('cpu_load')), - 'free_ram': float(match.group('ram_free')), - 'total_ram': float(match.group('ram_total')), - 'ntp_error': float(match.group('ntp_offset')), - } - - if match.group('ntp_correction'): result['rt_crystal_correction'] = float(match.group('ntp_correction')) - if match.group('voltage'): result['voltage'] = float(match.group('voltage')) - if match.group('amperage'): result['amperage'] = float(match.group('amperage')) - if match.group('cpu_temperature'): result['cpu_temp'] = float(match.group('cpu_temperature')) - if match.group('visible_senders'): result['senders_visible'] = int(match.group('visible_senders')) - 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('good_and_bad_senders'): result['good_and_bad_senders'] = float(match.group('good_and_bad_senders')) - - return result diff --git a/ogn/parser/aprs_comment/safesky_parser.py b/ogn/parser/aprs_comment/safesky_parser.py deleted file mode 100644 index 5c6c05b..0000000 --- a/ogn/parser/aprs_comment/safesky_parser.py +++ /dev/null @@ -1,32 +0,0 @@ -from ogn.parser.utils import FPM_TO_MS -from ogn.parser.pattern import PATTERN_SAFESKY_POSITION_COMMENT - -from .base import BaseParser - - -class SafeskyParser(BaseParser): - def __init__(self): - self.beacon_type = 'safesky' - self.position_pattern = PATTERN_SAFESKY_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - result = dict() - if match.group('details'): - result.update({ - 'address_type': int(match.group('details'), 16) & 0b00000011, - 'aircraft_type': (int(match.group('details'), 16) & 0b00111100) >> 2, - 'no-tracking': (int(match.group('details'), 16) & 0b01000000) >> 6 == 1, - 'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1, - 'address': match.group('address'), - }) - result.update( - {'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None}) - if match.group('gps_quality'): - result.update({ - 'gps_quality': { - 'horizontal': int(match.group('gps_quality_horizontal')), - 'vertical': int(match.group('gps_quality_vertical')) - } - }) - return result diff --git a/ogn/parser/aprs_comment/skylines_parser.py b/ogn/parser/aprs_comment/skylines_parser.py deleted file mode 100644 index 043c29c..0000000 --- a/ogn/parser/aprs_comment/skylines_parser.py +++ /dev/null @@ -1,15 +0,0 @@ -from ogn.parser.utils import FPM_TO_MS -from ogn.parser.pattern import PATTERN_SKYLINES_POSITION_COMMENT - -from .base import BaseParser - - -class SkylinesParser(BaseParser): - def __init__(self): - self.beacon_type = 'skylines' - self.position_pattern = PATTERN_SKYLINES_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - return {'skylines_id': match.group('skylines_id'), - 'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None} diff --git a/ogn/parser/aprs_comment/spider_parser.py b/ogn/parser/aprs_comment/spider_parser.py deleted file mode 100644 index f8c3d64..0000000 --- a/ogn/parser/aprs_comment/spider_parser.py +++ /dev/null @@ -1,16 +0,0 @@ -from ogn.parser.pattern import PATTERN_SPIDER_POSITION_COMMENT - -from .base import BaseParser - - -class SpiderParser(BaseParser): - def __init__(self): - self.beacon_type = 'spider' - self.position_pattern = PATTERN_SPIDER_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - return {'spider_id': match.group('spider_id'), - 'signal_power': int(match.group('signal_power')) if match.group('signal_power') else None, - 'spider_registration': match.group('spider_registration') if match.group('spider_registration') else None, - 'gps_quality': match.group('gps_quality') if match.group('gps_quality') else None} diff --git a/ogn/parser/aprs_comment/spot_parser.py b/ogn/parser/aprs_comment/spot_parser.py deleted file mode 100644 index 65fb65a..0000000 --- a/ogn/parser/aprs_comment/spot_parser.py +++ /dev/null @@ -1,15 +0,0 @@ -from ogn.parser.pattern import PATTERN_SPOT_POSITION_COMMENT - -from .base import BaseParser - - -class SpotParser(BaseParser): - def __init__(self): - self.beacon_type = 'spot' - self.position_pattern = PATTERN_SPOT_POSITION_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - return {'spot_id': match.group('spot_id'), - 'model': match.group('model') if match.group('model') else None, - 'status': match.group('status') if match.group('status') else None} diff --git a/ogn/parser/aprs_comment/tracker_parser.py b/ogn/parser/aprs_comment/tracker_parser.py deleted file mode 100644 index e1149c0..0000000 --- a/ogn/parser/aprs_comment/tracker_parser.py +++ /dev/null @@ -1,59 +0,0 @@ -from ogn.parser.pattern import PATTERN_TRACKER_POSITION_COMMENT, PATTERN_TRACKER_STATUS_COMMENT -from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS - -from .base import BaseParser - - -class TrackerParser(BaseParser): - def __init__(self): - self.beacon_type = 'tracker' - self.position_pattern = PATTERN_TRACKER_POSITION_COMMENT - self.status_pattern = PATTERN_TRACKER_STATUS_COMMENT - - def parse_position(self, aprs_comment): - match = self.position_pattern.match(aprs_comment) - - result = {} - if match.group('details'): - result.update({ - 'address_type': int(match.group('details'), 16) & 0b00000011, - 'aircraft_type': (int(match.group('details'), 16) & 0b01111100) >> 2, - 'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1, - 'address': match.group('address'), - }) - if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS - if match.group('turn_rate'): result['turn_rate'] = float(match.group('turn_rate')) * HPM_TO_DEGS - if match.group('flight_level'): result['flightlevel'] = float(match.group('flight_level')) - if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality')) - if match.group('error_count'): result['error_count'] = int(match.group('error_count')) - if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset')) - if match.group('gps_quality'): - result.update({ - 'gps_quality': { - 'horizontal': int(match.group('gps_quality_horizontal')), - 'vertical': int(match.group('gps_quality_vertical')) - } - }) - if match.group('signal_power'): result['signal_power'] = float(match.group('signal_power')) - return result - - def parse_status(self, aprs_comment): - match = self.status_pattern.match(aprs_comment) - if match: - result = {} - - if match.group('hardware_version'): result['hardware_version'] = int(match.group('hardware_version')) - if match.group('software_version'): result['software_version'] = int(match.group('software_version')) - if match.group('gps_satellites'): result['gps_satellites'] = int(match.group('gps_satellites')) - if match.group('gps_quality'): result['gps_quality'] = int(match.group('gps_quality')) - if match.group('gps_altitude'): result['gps_altitude'] = int(match.group('gps_altitude')) - if match.group('pressure'): result['pressure'] = float(match.group('pressure')) - if match.group('temperature'): result['temperature'] = float(match.group('temperature')) - if match.group('humidity'): result['humidity'] = int(match.group('humidity')) - if match.group('voltage'): result['voltage'] = float(match.group('voltage')) - if match.group('transmitter_power'): result['transmitter_power'] = int(match.group('transmitter_power')) - if match.group('noise_level'): result['noise_level'] = float(match.group('noise_level')) - if match.group('relays'): result['relays'] = int(match.group('relays')) - return result - else: - return {'comment': aprs_comment} diff --git a/ogn/parser/parse.py b/ogn/parser/parse.py index 0f500b6..418f45d 100644 --- a/ogn/parser/parse.py +++ b/ogn/parser/parse.py @@ -1,23 +1,7 @@ from datetime import datetime, timezone -from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS, createTimestamp, parseAngle, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER, INCH_TO_MM, fahrenheit_to_celsius, CheapRuler, normalized_quality -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, OgnParseError - -from ogn.parser.aprs_comment.ogn_parser import OgnParser -from ogn.parser.aprs_comment.fanet_parser import FanetParser -from ogn.parser.aprs_comment.lt24_parser import LT24Parser -from ogn.parser.aprs_comment.naviter_parser import NaviterParser -from ogn.parser.aprs_comment.flarm_parser import FlarmParser -from ogn.parser.aprs_comment.tracker_parser import TrackerParser -from ogn.parser.aprs_comment.receiver_parser import ReceiverParser -from ogn.parser.aprs_comment.skylines_parser import SkylinesParser -from ogn.parser.aprs_comment.spider_parser import SpiderParser -from ogn.parser.aprs_comment.spot_parser import SpotParser -from ogn.parser.aprs_comment.inreach_parser import InreachParser -from ogn.parser.aprs_comment.safesky_parser import SafeskyParser -from ogn.parser.aprs_comment.microtrak_parser import MicrotrakParser -from ogn.parser.aprs_comment.generic_parser import GenericParser +from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS, createTimestamp, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER, INCH_TO_MM, fahrenheit_to_celsius, CheapRuler, normalized_quality +from ogn.parser.exceptions import AprsParseError from ogn_parser import parse as rust_parse @@ -45,7 +29,7 @@ mapping = { } -def parse(aprs_message, reference_timestamp=None, calculate_relations=False, use_server_timestamp=True, use_rust_parser=False): +def parse(aprs_message, reference_timestamp=None, calculate_relations=False, use_server_timestamp=True): global server_timestamp if use_server_timestamp is True: @@ -53,118 +37,113 @@ def parse(aprs_message, reference_timestamp=None, calculate_relations=False, use elif reference_timestamp is None: reference_timestamp = datetime.now(timezone.utc) - if use_rust_parser: - rust_response = rust_parse(aprs_message)[0] - message = {'raw_message': aprs_message, 'reference_timestamp': reference_timestamp} - if parser_error := rust_response.get('parser_error'): - message['aprs_type'] = 'comment' - message['comment'] = str(parser_error) - elif aprs_packet := rust_response.get('aprs_packet'): + rust_messages = rust_parse(aprs_message) + if rust_messages: + rust_message = rust_messages[0] + else: + raise AprsParseError("Empty message") + + message = {'raw_message': aprs_message, 'reference_timestamp': reference_timestamp} + if parser_error := rust_message.get('parser_error'): + raise AprsParseError(f"Parser error: {parser_error}") + elif aprs_packet := rust_message.get('aprs_packet'): + message.update({ + 'aprs_type': 'position', + 'beacon_type': mapping.get(aprs_packet['to'], 'unknown'), + 'name': aprs_packet['from'], + 'dstcall': aprs_packet['to'], + }) + if via := aprs_packet.get('via'): + message['receiver_name'] = via[-1] + if aprs_packet['via'][0] != 'TCPIP*' and aprs_packet['via'][0].endswith('*'): message['relay'] = aprs_packet['via'][0][:-1] + if position := aprs_packet.get('position'): message.update({ - 'aprs_type': 'position', - 'beacon_type': mapping.get(aprs_packet['to'], 'unknown'), - 'name': aprs_packet['from'], - 'dstcall': aprs_packet['to'], + 'latitude': position['latitude'], + 'longitude': position['longitude'], + 'symboltable': position['symbol_table'], + 'symbolcode': position['symbol_code'], }) - if via := aprs_packet.get('via'): - message['receiver_name'] = via[-1] - if aprs_packet['via'][0] != 'TCPIP*' and aprs_packet['via'][0].endswith('*'): message['relay'] = aprs_packet['via'][0][:-1] - if position := aprs_packet.get('position'): - message.update({ - 'latitude': position['latitude'], - 'longitude': position['longitude'], - 'symboltable': position['symbol_table'], - 'symbolcode': position['symbol_code'], - }) - if 'timestamp' in position: message['timestamp'] = createTimestamp(position['timestamp'], reference_timestamp) + if 'timestamp' in position: message['timestamp'] = createTimestamp(position['timestamp'], reference_timestamp) - if 'wind_direction' in position: - message['aprs_type'] = 'position_weather' - if 'wind_direction' in position: message["wind_direction"] = position['wind_direction'] - if 'wind_speed' in position: message["wind_speed"] = position['wind_speed'] * KNOTS_TO_MS / KPH_TO_MS - if 'gust' in position: message['wind_speed_peak'] = position['gust'] * KNOTS_TO_MS / KPH_TO_MS - if 'temperature' in position: message['temperature'] = fahrenheit_to_celsius(position['temperature']) - if 'rainfall_1h' in position: message['rainfall_1h'] = position['rainfall_1h'] / 100.0 * INCH_TO_MM - if 'rainfall_24h' in position: message['rainfall_24h'] = position['rainfall_24h'] / 100.0 * INCH_TO_MM - if 'humidity' in position: message['humidity'] = 1. if position['humidity'] == 0 else position['humidity'] * 0.01 - if 'barometric_pressure' in position: message['barometric_pressure'] = position['barometric_pressure'] + if 'wind_direction' in position: + message['aprs_type'] = 'position_weather' + if 'wind_direction' in position: message["wind_direction"] = position['wind_direction'] + if 'wind_speed' in position: message["wind_speed"] = position['wind_speed'] * KNOTS_TO_MS / KPH_TO_MS + if 'gust' in position: message['wind_speed_peak'] = position['gust'] * KNOTS_TO_MS / KPH_TO_MS + if 'temperature' in position: message['temperature'] = fahrenheit_to_celsius(position['temperature']) + if 'rainfall_1h' in position: message['rainfall_1h'] = position['rainfall_1h'] / 100.0 * INCH_TO_MM + if 'rainfall_24h' in position: message['rainfall_24h'] = position['rainfall_24h'] / 100.0 * INCH_TO_MM + if 'humidity' in position: message['humidity'] = 1. if position['humidity'] == 0 else position['humidity'] * 0.01 + if 'barometric_pressure' in position: message['barometric_pressure'] = position['barometric_pressure'] - if 'course' in position: message["track"] = position['course'] - if 'speed' in position: message["ground_speed"] = position['speed'] * KNOTS_TO_MS / KPH_TO_MS - if 'altitude' in position: message["altitude"] = position['altitude'] * FEETS_TO_METER + if 'course' in position: message["track"] = position['course'] + if 'speed' in position: message["ground_speed"] = position['speed'] * KNOTS_TO_MS / KPH_TO_MS + if 'altitude' in position: message["altitude"] = position['altitude'] * FEETS_TO_METER - if 'reserved' in position: message['reserved'] = position['reserved'] - if 'address_type' in position: message['address_type'] = position['address_type'] - if 'aircraft_type' in position: message['aircraft_type'] = position['aircraft_type'] - if 'is_notrack' in position: message['no-tracking'] = position['is_notrack'] - if 'is_stealth' in position: message['stealth'] = position['is_stealth'] - if 'address' in position: message['address'] = f"{position['address']:06X}" + if 'reserved' in position: message['reserved'] = position['reserved'] + if 'address_type' in position: message['address_type'] = position['address_type'] + if 'aircraft_type' in position: message['aircraft_type'] = position['aircraft_type'] + if 'is_notrack' in position: message['no-tracking'] = position['is_notrack'] + if 'is_stealth' in position: message['stealth'] = position['is_stealth'] + if 'address' in position: message['address'] = f"{position['address']:06X}" - if 'climb_rate' in position: message["climb_rate"] = position['climb_rate'] * FPM_TO_MS - if 'turn_rate' in position: message["turn_rate"] = position['turn_rate'] * HPM_TO_DEGS - if 'signal_quality' in position: message["signal_quality"] = position['signal_quality'] - if 'error' in position: message["error_count"] = position['error'] - if 'frequency_offset' in position: message["frequency_offset"] = position['frequency_offset'] - if 'gps_quality' in position: message["gps_quality"] = position['gps_quality'] - if 'flight_level' in position: message["flightlevel"] = position['flight_level'] - if 'signal_power' in position: message["signal_power"] = position['signal_power'] - if 'software_version' in position: message["software_version"] = position['software_version'] - if 'hardware_version' in position: message["hardware_version"] = position['hardware_version'] - if 'original_address' in position: message["real_address"] = f"{position['original_address']:06X}" + if 'climb_rate' in position: message["climb_rate"] = position['climb_rate'] * FPM_TO_MS + if 'turn_rate' in position: message["turn_rate"] = position['turn_rate'] * HPM_TO_DEGS + if 'signal_quality' in position: message["signal_quality"] = position['signal_quality'] + if 'error' in position: message["error_count"] = position['error'] + if 'frequency_offset' in position: message["frequency_offset"] = position['frequency_offset'] + if 'gps_quality' in position: message["gps_quality"] = position['gps_quality'] + if 'flight_level' in position: message["flightlevel"] = position['flight_level'] + if 'signal_power' in position: message["signal_power"] = position['signal_power'] + if 'software_version' in position: message["software_version"] = position['software_version'] + if 'hardware_version' in position: message["hardware_version"] = position['hardware_version'] + if 'original_address' in position: message["real_address"] = f"{position['original_address']:06X}" - if 'unparsed' in position: message["user_comment"] = position['unparsed'] + if 'unparsed' in position: message["user_comment"] = position['unparsed'] - elif status := aprs_packet.get('status'): - message['aprs_type'] = 'status' - if 'timestamp' in status: message['timestamp'] = createTimestamp(status['timestamp'], reference_timestamp) + elif status := aprs_packet.get('status'): + message['aprs_type'] = 'status' + if 'timestamp' in status: message['timestamp'] = createTimestamp(status['timestamp'], reference_timestamp) - if 'version' in status: message["version"] = status['version'] - if 'platform' in status: message["platform"] = status['platform'] - if 'cpu_load' in status: message["cpu_load"] = status['cpu_load'] - if 'ram_free' in status: message["free_ram"] = status['ram_free'] - if 'ram_total' in status: message["total_ram"] = status['ram_total'] - if 'ntp_offset' in status: message["ntp_error"] = status['ntp_offset'] - if 'ntp_correction' in status: message["rt_crystal_correction"] = status['ntp_correction'] - if 'voltage' in status: message["voltage"] = status['voltage'] - if 'amperage' in status: message["amperage"] = status['amperage'] - if 'cpu_temperature' in status: message["cpu_temp"] = status['cpu_temperature'] - if 'visible_senders' in status: message["senders_visible"] = status['visible_senders'] - if 'latency' in status: message["latency"] = status['latency'] - if 'senders' in status: message["senders_total"] = status['senders'] - if 'rf_correction_manual' in status: message["rec_crystal_correction"] = status['rf_correction_manual'] - if 'rf_correction_automatic' in status: message["rec_crystal_correction_fine"] = status['rf_correction_automatic'] - if 'noise' in status: message["rec_input_noise"] = status['noise'] - if 'senders_signal_quality' in status: message["senders_signal"] = status['senders_signal_quality'] - if 'senders_messages' in status: message["senders_messages"] = status['senders_messages'] - if 'good_senders_signal_quality' in status: message["good_senders_signal"] = status['good_senders_signal_quality'] - if 'good_senders' in status: message["good_senders"] = status['good_senders'] - if 'good_and_bad_senders' in status: message["good_and_bad_senders"] = status['good_and_bad_senders'] + if 'version' in status: message["version"] = status['version'] + if 'platform' in status: message["platform"] = status['platform'] + if 'cpu_load' in status: message["cpu_load"] = status['cpu_load'] + if 'ram_free' in status: message["free_ram"] = status['ram_free'] + if 'ram_total' in status: message["total_ram"] = status['ram_total'] + if 'ntp_offset' in status: message["ntp_error"] = status['ntp_offset'] + if 'ntp_correction' in status: message["rt_crystal_correction"] = status['ntp_correction'] + if 'voltage' in status: message["voltage"] = status['voltage'] + if 'amperage' in status: message["amperage"] = status['amperage'] + if 'cpu_temperature' in status: message["cpu_temp"] = status['cpu_temperature'] + if 'visible_senders' in status: message["senders_visible"] = status['visible_senders'] + if 'latency' in status: message["latency"] = status['latency'] + if 'senders' in status: message["senders_total"] = status['senders'] + if 'rf_correction_manual' in status: message["rec_crystal_correction"] = status['rf_correction_manual'] + if 'rf_correction_automatic' in status: message["rec_crystal_correction_fine"] = status['rf_correction_automatic'] + if 'noise' in status: message["rec_input_noise"] = status['noise'] + if 'senders_signal_quality' in status: message["senders_signal"] = status['senders_signal_quality'] + if 'senders_messages' in status: message["senders_messages"] = status['senders_messages'] + if 'good_senders_signal_quality' in status: message["good_senders_signal"] = status['good_senders_signal_quality'] + if 'good_senders' in status: message["good_senders"] = status['good_senders'] + if 'good_and_bad_senders' in status: message["good_and_bad_senders"] = status['good_and_bad_senders'] - if 'unparsed' in status: message["user_comment"] = status['unparsed'] - else: - raise ValueError("WTF") - elif server_comment := rust_response.get('servercomment'): - message.update({ - 'version': server_comment['version'], - 'timestamp': datetime.strptime(server_comment['timestamp'], "%d %b %Y %H:%M:%S %Z"), - 'server': server_comment['server'], - 'ip_address': server_comment['ip_address'], - 'port': server_comment['port'], - 'aprs_type': 'server'}) - elif comment := rust_response.get('comment'): - message.update({ - 'comment': comment['comment'], - 'aprs_type': 'comment'}) + if 'unparsed' in status: message["user_comment"] = status['unparsed'] else: raise ValueError("WTF") - + elif server_comment := rust_message.get('server_comment'): + message.update({ + 'version': server_comment['version'], + 'timestamp': datetime.fromisoformat(server_comment['timestamp']), + 'server': server_comment['server'], + 'ip_address': server_comment['ip_address'], + 'port': server_comment['port'], + 'aprs_type': 'server'}) + elif comment := rust_message.get('comment'): + message.update({ + 'comment': comment['comment'], + 'aprs_type': 'comment'}) else: - message = parse_aprs(aprs_message, reference_timestamp=reference_timestamp) - if message['aprs_type'] == 'position' or message['aprs_type'] == 'status': - try: - message.update(parse_comment(message['comment'], dstcall=message['dstcall'], aprs_type=message['aprs_type'])) - except Exception: - raise OgnParseError(f"dstcall: {message['dstcall']}, aprs_type: {message['aprs_type']}, comment: {message['comment']}") + raise ValueError("WTF") if message['aprs_type'].startswith('position') and calculate_relations is True: positions[message['name']] = (message['longitude'], message['latitude']) @@ -178,132 +157,3 @@ def parse(aprs_message, reference_timestamp=None, calculate_relations=False, use server_timestamp = message['timestamp'] return message - - -def parse_aprs(message, reference_timestamp=None): - if reference_timestamp is None: - reference_timestamp = datetime.now(timezone.utc) - - result = {'raw_message': message, - 'reference_timestamp': reference_timestamp} - - if message and message[0] == '#': - match_server = PATTERN_SERVER.search(message) - if match_server: - result.update({ - 'version': match_server.group('version'), - 'timestamp': datetime.strptime(match_server.group('timestamp'), "%d %b %Y %H:%M:%S %Z"), - 'server': match_server.group('server'), - 'ip_address': match_server.group('ip_address'), - 'port': match_server.group('port'), - 'aprs_type': 'server'}) - else: - result.update({ - 'comment': message, - 'aprs_type': 'comment'}) - else: - match = PATTERN_APRS.search(message) - if match: - aprs_type = 'position' if match.group('aprs_type') == '/' else 'status' if match.group('aprs_type') == '>' else 'unknown' - result.update({'aprs_type': aprs_type}) - aprs_body = match.group('aprs_body') - if aprs_type == 'position': - match_position = PATTERN_APRS_POSITION.search(aprs_body) - if match_position: - result.update({ - 'name': match.group('callsign'), - 'dstcall': match.group('dstcall'), - 'relay': match.group('relay'), - 'receiver_name': match.group('receiver'), - 'timestamp': createTimestamp(match_position.group('time'), reference_timestamp), - 'latitude': parseAngle('0' + match_position.group('latitude') + (match_position.group('latitude_enhancement') or '0')) * # noqa: W504 - (-1 if match_position.group('latitude_sign') == 'S' else 1), - 'symboltable': match_position.group('symbol_table'), - '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 "", - }) - return result - - match_position_weather = PATTERN_APRS_POSITION_WEATHER.search(aprs_body) - if match_position_weather: - result.update({ - 'aprs_type': 'position_weather', - - 'name': match.group('callsign'), - 'dstcall': match.group('dstcall'), - 'relay': match.group('relay'), - '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, - 'rainfall_1h': int(match_position_weather.group('rainfall_1h')) / 100.0 * INCH_TO_MM if match_position_weather.group('rainfall_1h') else None, - 'rainfall_24h': int(match_position_weather.group('rainfall_24h')) / 100.0 * INCH_TO_MM if match_position_weather.group('rainfall_24h') 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 = PATTERN_APRS_STATUS.search(aprs_body) - if match_status: - result.update({ - 'name': match.group('callsign'), - 'dstcall': match.group('dstcall'), - 'receiver_name': match.group('receiver'), - 'timestamp': createTimestamp(match_status.group('time'), reference_timestamp), - 'comment': match_status.group('comment') if match_status.group('comment') else ""}) - else: - raise NotImplementedError(message) - else: - raise AprsParseError(message) - - return result - - -dstcall_parser_mapping = {'APRS': OgnParser(), - 'OGNFNT': FanetParser(), - 'OGFLR': FlarmParser(), - 'OGFLR6': FlarmParser(), - 'OGFLR7': FlarmParser(), - 'OGNTRK': TrackerParser(), - 'OGNSDR': ReceiverParser(), - 'OGCAPT': GenericParser(beacon_type='capturs'), - 'OGFLYM': GenericParser(beacon_type='flymaster'), - 'OGNINRE': InreachParser(), - 'OGLT24': LT24Parser(), - 'OGNAVI': NaviterParser(), - 'OGPAW': GenericParser(beacon_type='pilot_aware'), - 'OGSKYL': SkylinesParser(), - 'OGSPID': SpiderParser(), - 'OGSPOT': SpotParser(), - 'OGNSKY': SafeskyParser(), - 'OGNMTK': MicrotrakParser(), - 'GENERIC': GenericParser(beacon_type='unknown'), - } - - -def parse_comment(aprs_comment, dstcall='APRS', aprs_type="position"): - parser = dstcall_parser_mapping.get(dstcall) - if parser: - return parser.parse(aprs_comment, aprs_type) - else: - return dstcall_parser_mapping.get('GENERIC').parse(aprs_comment, aprs_type) diff --git a/ogn/parser/pattern.py b/ogn/parser/pattern.py index 711e6c8..ed595db 100644 --- a/ogn/parser/pattern.py +++ b/ogn/parser/pattern.py @@ -1,136 +1,5 @@ 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