diff --git a/CHANGELOG.md b/CHANGELOG.md index 0546d56..7e485b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased - parser: Added support for heared aircrafts +- parser: Added support for naviter beacons - client: Allow client to do sequential connect-disconnect ## 0.7.1 - 2017-06-05 diff --git a/ogn/__init__.py b/ogn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ogn/parser/__init__.py b/ogn/parser/__init__.py index 3e79519..5bcc93f 100644 --- a/ogn/parser/__init__.py +++ b/ogn/parser/__init__.py @@ -1,2 +1,2 @@ -from ogn.parser.parse import parse_aprs, parse_ogn_beacon, parse_ogn_receiver_beacon, parse_ogn_aircraft_beacon # flake8: noqa +from ogn.parser.parse import parse_aprs, parse_ogn_beacon, parse_receiver_beacon, parse_aircraft_beacon # flake8: noqa from ogn.parser.exceptions import ParseError, AprsParseError, OgnParseError, AmbigousTimeError # flake8: noqa diff --git a/ogn/parser/parse.py b/ogn/parser/parse.py index f04c5d1..6978668 100644 --- a/ogn/parser/parse.py +++ b/ogn/parser/parse.py @@ -1,10 +1,17 @@ import re from datetime import datetime -from ogn.parser.utils import createTimestamp, parseAngle, kts2kmh, feet2m, fpm2ms -from ogn.parser.pattern import PATTERN_APRS_POSITION, PATTERN_APRS_STATUS, PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON +from ogn.parser.utils import createTimestamp, parseAngle, kts2kmh, feet2m +from ogn.parser.pattern import PATTERN_APRS_POSITION, PATTERN_APRS_STATUS from ogn.parser.exceptions import AprsParseError, OgnParseError +from ogn.parser.parse_ogn import parse_aircraft_beacon, parse_receiver_beacon +from ogn.parser.parse_naviter import parse as parse_naviter_beacon +from ogn.parser.parse_lt24 import parse as parse_lt24_beacon +from ogn.parser.parse_spider import parse as parse_spider_beacon +from ogn.parser.parse_spot import parse as parse_spot_beacon +from ogn.parser.parse_skylines import parse as parse_skylines_beacon + def parse_aprs(message, reference_date=None, reference_time=None): if reference_date is None: @@ -15,8 +22,9 @@ def parse_aprs(message, reference_date=None, reference_time=None): match_position = re.search(PATTERN_APRS_POSITION, message) if match_position: return {'name': match_position.group('callsign'), - 'receiver_name': match_position.group('receiver'), 'dstcall': match_position.group('dstcall'), + 'relay': match_position.group('relay') if match_position.group('relay') else None, + 'receiver_name': match_position.group('receiver'), 'timestamp': createTimestamp(match_position.group('time'), reference_date, reference_time), 'latitude': parseAngle('0' + match_position.group('latitude') + (match_position.group('latitude_enhancement') or '0')) * (-1 if match_position.group('latitude_sign') == 'S' else 1), @@ -27,81 +35,68 @@ def parse_aprs(message, reference_date=None, reference_time=None): 'track': int(match_position.group('course')) if match_position.group('course_extension') else None, 'ground_speed': int(match_position.group('ground_speed')) * kts2kmh if match_position.group('ground_speed') else None, 'altitude': int(match_position.group('altitude')) * feet2m, - 'comment': match_position.group('comment')} + 'comment': match_position.group('comment'), + 'aprs_type': 'position'} match_status = re.search(PATTERN_APRS_STATUS, message) if match_status: return {'name': match_status.group('callsign'), - 'receiver_name': match_status.group('receiver'), 'dstcall': match_status.group('dstcall'), + 'receiver_name': match_status.group('receiver'), 'timestamp': createTimestamp(match_status.group('time'), reference_date, reference_time), - 'comment': match_status.group('comment')} + 'comment': match_status.group('comment'), + 'aprs_type': 'status'} raise AprsParseError(message) -def parse_ogn_aircraft_beacon(aprs_comment): - ac_match = re.search(PATTERN_AIRCRAFT_BEACON, aprs_comment) - if ac_match: - return {'address_type': int(ac_match.group('details'), 16) & 0b00000011, - 'aircraft_type': (int(ac_match.group('details'), 16) & 0b01111100) >> 2, - 'stealth': (int(ac_match.group('details'), 16) & 0b10000000) >> 7 == 1, - 'address': ac_match.group('id'), - 'climb_rate': int(ac_match.group('climb_rate')) * fpm2ms if ac_match.group('climb_rate') else None, - 'turn_rate': float(ac_match.group('turn_rate')) if ac_match.group('turn_rate') else None, - 'flightlevel': float(ac_match.group('flight_level')) if ac_match.group('flight_level') else None, - 'signal_quality': float(ac_match.group('signal_quality')) if ac_match.group('signal_quality') else None, - 'error_count': int(ac_match.group('errors')) if ac_match.group('errors') else None, - 'frequency_offset': float(ac_match.group('frequency_offset')) if ac_match.group('frequency_offset') else None, - 'gps_status': ac_match.group('gps_accuracy') if ac_match.group('gps_accuracy') else None, - 'software_version': float(ac_match.group('flarm_software_version')) if ac_match.group('flarm_software_version') else None, - 'hardware_version': int(ac_match.group('flarm_hardware_version'), 16) if ac_match.group('flarm_hardware_version') else None, - 'real_address': ac_match.group('flarm_id') if ac_match.group('flarm_id') else None, - 'signal_power': float(ac_match.group('signal_power')) if ac_match.group('signal_power') else None, - 'proximity': [hear[4:] for hear in ac_match.group('proximity').split(" ")] if ac_match.group('proximity') else None} - else: - return None +def parse_ogn_beacon(aprs_comment, dstcall="APRS"): + if dstcall == "APRS": # this can be a receiver or an aircraft + if not aprs_comment: + return {'beacon_type': 'receiver_beacon'} + ac_data = parse_aircraft_beacon(aprs_comment) + if ac_data: + ac_data.update({'beacon_type': 'aircraft_beacon'}) + return ac_data -def parse_ogn_receiver_beacon(aprs_comment): - rec_match = re.search(PATTERN_RECEIVER_BEACON, aprs_comment) - if rec_match: - return {'version': rec_match.group('version'), - 'platform': rec_match.group('platform'), - 'cpu_load': float(rec_match.group('cpu_load')), - 'free_ram': float(rec_match.group('ram_free')), - 'total_ram': float(rec_match.group('ram_total')), - 'ntp_error': float(rec_match.group('ntp_offset')), - 'rt_crystal_correction': float(rec_match.group('ntp_correction')), - 'voltage': float(rec_match.group('voltage')) if rec_match.group('voltage') else None, - 'amperage': float(rec_match.group('amperage')) if rec_match.group('amperage') else None, - 'cpu_temp': float(rec_match.group('cpu_temperature')) if rec_match.group('cpu_temperature') else None, - 'senders_visible': int(rec_match.group('visible_senders')) if rec_match.group('visible_senders') else None, - 'senders_total': int(rec_match.group('senders')) if rec_match.group('senders') else None, - 'rec_crystal_correction': int(rec_match.group('rf_correction_manual')) if rec_match.group('rf_correction_manual') else None, - 'rec_crystal_correction_fine': float(rec_match.group('rf_correction_automatic')) if rec_match.group('rf_correction_automatic') else None, - 'rec_input_noise': float(rec_match.group('signal_quality')) if rec_match.group('signal_quality') else None, - 'senders_signal': float(rec_match.group('senders_signal_quality')) if rec_match.group('senders_signal_quality') else None, - 'senders_messages': float(rec_match.group('senders_messages')) if rec_match.group('senders_messages') else None, - 'good_senders_signal': float(rec_match.group('good_senders_signal_quality')) if rec_match.group('good_senders_signal_quality') else None, - 'good_senders': float(rec_match.group('good_senders')) if rec_match.group('good_senders') else None, - 'good_and_bad_senders': float(rec_match.group('good_and_bad_senders')) if rec_match.group('good_and_bad_senders') else None} - else: - return None + rc_data = parse_receiver_beacon(aprs_comment) + if rc_data: + rc_data.update({'beacon_type': 'receiver_beacon'}) + return rc_data - -def parse_ogn_beacon(aprs_comment): - if not aprs_comment: - return {'beacon_type': 'receiver_beacon'} - - ac_data = parse_ogn_aircraft_beacon(aprs_comment) - if ac_data: + raise OgnParseError(aprs_comment) + elif dstcall == "OGFLR": + ac_data = parse_aircraft_beacon(aprs_comment) ac_data.update({'beacon_type': 'aircraft_beacon'}) return ac_data - - rc_data = parse_ogn_receiver_beacon(aprs_comment) - if rc_data: - rc_data.update({'beacon_type': 'receiver_beacon'}) - return rc_data - - raise OgnParseError(aprs_comment) + elif dstcall == "OGNTRK": + ac_data = parse_aircraft_beacon(aprs_comment) + ac_data.update({'beacon_type': 'aircraft_beacon'}) + return ac_data + elif dstcall == "OGNSDR": + ac_data = parse_receiver_beacon(aprs_comment) + ac_data.update({'beacon_type': 'receiver_beacon'}) + return ac_data + elif dstcall == "OGLT24": + ac_data = parse_lt24_beacon(aprs_comment) + ac_data.update({'beacon_type': 'lt24_beacon'}) + return ac_data + elif dstcall == "OGNAVI": + ac_data = parse_naviter_beacon(aprs_comment) + ac_data.update({'beacon_type': 'naviter_beacon'}) + return ac_data + elif dstcall == "OGSKYL": + ac_data = parse_skylines_beacon(aprs_comment) + ac_data.update({'beacon_type': 'skylines_beacon'}) + return ac_data + elif dstcall == "OGSPID": + ac_data = parse_spider_beacon(aprs_comment) + ac_data.update({'beacon_type': 'spider_beacon'}) + return ac_data + elif dstcall == "OGSPOT": + ac_data = parse_spot_beacon(aprs_comment) + ac_data.update({'beacon_type': 'spot_beacon'}) + return ac_data + else: + raise ValueError("dstcall {} unknown".format(dstcall)) diff --git a/ogn/parser/parse_lt24.py b/ogn/parser/parse_lt24.py new file mode 100644 index 0000000..44fa237 --- /dev/null +++ b/ogn/parser/parse_lt24.py @@ -0,0 +1,2 @@ +def parse(aprs_comment): + raise NotImplementedError("LT24 beacon parser not yet implemented") diff --git a/ogn/parser/parse_naviter.py b/ogn/parser/parse_naviter.py new file mode 100644 index 0000000..5787d8e --- /dev/null +++ b/ogn/parser/parse_naviter.py @@ -0,0 +1,16 @@ +import re + +from ogn.parser.utils import fpm2ms +from ogn.parser.pattern import PATTERN_NAVITER_BEACON + + +def parse(aprs_comment): + match = re.search(PATTERN_NAVITER_BEACON, 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('id'), + 'climb_rate': int(match.group('climb_rate')) * fpm2ms if match.group('climb_rate') else None, + 'turn_rate': float(match.group('turn_rate')) if match.group('turn_rate') else None} diff --git a/ogn/parser/parse_ogn.py b/ogn/parser/parse_ogn.py new file mode 100644 index 0000000..cd5f004 --- /dev/null +++ b/ogn/parser/parse_ogn.py @@ -0,0 +1,54 @@ +import re + +from ogn.parser.utils import fpm2ms +from ogn.parser.pattern import PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON + + +def parse_aircraft_beacon(aprs_comment): + ac_match = re.search(PATTERN_AIRCRAFT_BEACON, aprs_comment) + if ac_match: + return {'address_type': int(ac_match.group('details'), 16) & 0b00000011, + 'aircraft_type': (int(ac_match.group('details'), 16) & 0b01111100) >> 2, + 'stealth': (int(ac_match.group('details'), 16) & 0b10000000) >> 7 == 1, + 'address': ac_match.group('id'), + 'climb_rate': int(ac_match.group('climb_rate')) * fpm2ms if ac_match.group('climb_rate') else None, + 'turn_rate': float(ac_match.group('turn_rate')) if ac_match.group('turn_rate') else None, + 'flightlevel': float(ac_match.group('flight_level')) if ac_match.group('flight_level') else None, + 'signal_quality': float(ac_match.group('signal_quality')) if ac_match.group('signal_quality') else None, + 'error_count': int(ac_match.group('errors')) if ac_match.group('errors') else None, + 'frequency_offset': float(ac_match.group('frequency_offset')) if ac_match.group('frequency_offset') else None, + 'gps_status': ac_match.group('gps_accuracy') if ac_match.group('gps_accuracy') else None, + 'software_version': float(ac_match.group('flarm_software_version')) if ac_match.group('flarm_software_version') else None, + 'hardware_version': int(ac_match.group('flarm_hardware_version'), 16) if ac_match.group('flarm_hardware_version') else None, + 'real_address': ac_match.group('flarm_id') if ac_match.group('flarm_id') else None, + 'signal_power': float(ac_match.group('signal_power')) if ac_match.group('signal_power') else None, + 'proximity': [hear[4:] for hear in ac_match.group('proximity').split(" ")] if ac_match.group('proximity') else None} + else: + return None + + +def parse_receiver_beacon(aprs_comment): + rec_match = re.search(PATTERN_RECEIVER_BEACON, aprs_comment) + if rec_match: + return {'version': rec_match.group('version'), + 'platform': rec_match.group('platform'), + 'cpu_load': float(rec_match.group('cpu_load')), + 'free_ram': float(rec_match.group('ram_free')), + 'total_ram': float(rec_match.group('ram_total')), + 'ntp_error': float(rec_match.group('ntp_offset')), + 'rt_crystal_correction': float(rec_match.group('ntp_correction')), + 'voltage': float(rec_match.group('voltage')) if rec_match.group('voltage') else None, + 'amperage': float(rec_match.group('amperage')) if rec_match.group('amperage') else None, + 'cpu_temp': float(rec_match.group('cpu_temperature')) if rec_match.group('cpu_temperature') else None, + 'senders_visible': int(rec_match.group('visible_senders')) if rec_match.group('visible_senders') else None, + 'senders_total': int(rec_match.group('senders')) if rec_match.group('senders') else None, + 'rec_crystal_correction': int(rec_match.group('rf_correction_manual')) if rec_match.group('rf_correction_manual') else None, + 'rec_crystal_correction_fine': float(rec_match.group('rf_correction_automatic')) if rec_match.group('rf_correction_automatic') else None, + 'rec_input_noise': float(rec_match.group('signal_quality')) if rec_match.group('signal_quality') else None, + 'senders_signal': float(rec_match.group('senders_signal_quality')) if rec_match.group('senders_signal_quality') else None, + 'senders_messages': float(rec_match.group('senders_messages')) if rec_match.group('senders_messages') else None, + 'good_senders_signal': float(rec_match.group('good_senders_signal_quality')) if rec_match.group('good_senders_signal_quality') else None, + 'good_senders': float(rec_match.group('good_senders')) if rec_match.group('good_senders') else None, + 'good_and_bad_senders': float(rec_match.group('good_and_bad_senders')) if rec_match.group('good_and_bad_senders') else None} + else: + return None diff --git a/ogn/parser/parse_skylines.py b/ogn/parser/parse_skylines.py new file mode 100644 index 0000000..94f0b70 --- /dev/null +++ b/ogn/parser/parse_skylines.py @@ -0,0 +1,2 @@ +def parse(aprs_comment): + raise NotImplementedError("Skylines beacon parser not yet implemented") diff --git a/ogn/parser/parse_spider.py b/ogn/parser/parse_spider.py new file mode 100644 index 0000000..41ae86c --- /dev/null +++ b/ogn/parser/parse_spider.py @@ -0,0 +1,2 @@ +def parse(aprs_comment): + raise NotImplementedError("Spider beacon parser not yet implemented") diff --git a/ogn/parser/parse_spot.py b/ogn/parser/parse_spot.py new file mode 100644 index 0000000..d8503d1 --- /dev/null +++ b/ogn/parser/parse_spot.py @@ -0,0 +1,2 @@ +def parse(aprs_comment): + raise NotImplementedError("SPOT beacon parser not yet implemented") diff --git a/ogn/parser/pattern.py b/ogn/parser/pattern.py index c166e89..8a0dce2 100644 --- a/ogn/parser/pattern.py +++ b/ogn/parser/pattern.py @@ -1,8 +1,14 @@ import re -PATTERN_APRS_POSITION = re.compile(r"^(?P.+?)>(?P[A-Z0-9]+),.+,(?P.+?):/(?P