diff --git a/CHANGELOG.md b/CHANGELOG.md index fd3d301..f1a7ecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## Unreleased -- parser: Added support for OGNLT24 (LT24), OGSKYL (Skylines), OGSPID (Spider) and OGSPOT (Spot) +- parser: Added support for OGNLT24 (LT24), OGSKYL (Skylines), OGSPID (Spider), OGSPOT (Spot) and OGNFNT (Fanet) +- parser: Added support for (server) comments - parser: Added parser for local receiver output (port 50001) ## 0.8.2: - 2018-01-20 diff --git a/ogn/parser/aprs_comment/fanet_parser.py b/ogn/parser/aprs_comment/fanet_parser.py new file mode 100644 index 0000000..6eb7a02 --- /dev/null +++ b/ogn/parser/aprs_comment/fanet_parser.py @@ -0,0 +1,17 @@ +import re + +from ogn.parser.utils import fpm2ms +from ogn.parser.pattern import PATTERN_FANET_POSITION_COMMENT + +from .base import BaseParser + + +class FanetParser(BaseParser): + def __init__(self): + self.beacon_type = 'fanet' + + @staticmethod + def parse_position(aprs_comment): + ac_match = re.search(PATTERN_FANET_POSITION_COMMENT, aprs_comment) + return {'id': ac_match.group('id') if ac_match.group('id') else None, + 'climb_rate': int(ac_match.group('climb_rate')) * fpm2ms if ac_match.group('climb_rate') else None} diff --git a/ogn/parser/parse.py b/ogn/parser/parse.py index cdfd48f..b7f7791 100644 --- a/ogn/parser/parse.py +++ b/ogn/parser/parse.py @@ -2,10 +2,11 @@ import re from datetime import datetime from ogn.parser.utils import createTimestamp, parseAngle, kts2kmh, feet2m -from ogn.parser.pattern import PATTERN_APRS_POSITION, PATTERN_APRS_STATUS +from ogn.parser.pattern import PATTERN_APRS, PATTERN_APRS_POSITION, PATTERN_APRS_STATUS, PATTERN_APRS_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 @@ -23,43 +24,65 @@ def parse(aprs_message, reference_date=None, reference_time=None): reference_time = now.time() message = parse_aprs(aprs_message, reference_date, reference_time) - message.update(parse_comment(message['comment'], dstcall=message['dstcall'], aprs_type=message['aprs_type'])) + if message['aprs_type'] == 'position' or message['aprs_type'] == 'status': + message.update(parse_comment(message['comment'], + dstcall=message['dstcall'], + aprs_type=message['aprs_type'])) return message def parse_aprs(message, reference_date, reference_time=None): - match_position = re.search(PATTERN_APRS_POSITION, message) - if match_position: - return {'name': match_position.group('callsign'), - '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), - 'symboltable': match_position.group('symbol_table'), - 'longitude': parseAngle(match_position.group('longitude') + (match_position.group('longitude_enhancement') or '0')) * - (-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')) * kts2kmh if match_position.group('ground_speed') else None, - 'altitude': int(match_position.group('altitude')) * feet2m, - 'comment': match_position.group('comment') if match_position.group('comment') else "", - 'aprs_type': 'position'} + if message and message[0] == '#': + match_server = re.search(PATTERN_APRS_SERVER, message) + if match_server: + return {'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: + return {'comment': message, + 'aprs_type': 'comment'} - match_status = re.search(PATTERN_APRS_STATUS, message) - if match_status: - return {'name': match_status.group('callsign'), - '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') if match_status.group('comment') else "", - 'aprs_type': 'status'} + match = re.search(PATTERN_APRS, message) + if match: + aprs_type = 'position' if match.group('aprs_type') == '/' else 'status' + aprs_body = match.group('aprs_body') + if aprs_type == 'position': + match_position = re.search(PATTERN_APRS_POSITION, aprs_body) + if match_position: + return {'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.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), + 'symboltable': match_position.group('symbol_table'), + 'longitude': parseAngle(match_position.group('longitude') + (match_position.group('longitude_enhancement') or '0')) * + (-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')) * kts2kmh if match_position.group('ground_speed') else None, + 'altitude': int(match_position.group('altitude')) * feet2m, + 'comment': match_position.group('comment') if match_position.group('comment') else "", + 'aprs_type': aprs_type} + elif aprs_type == 'status': + match_status = re.search(PATTERN_APRS_STATUS, aprs_body) + if match_status: + return {'name': match.group('callsign'), + 'dstcall': match.group('dstcall'), + 'receiver_name': match.group('receiver'), + 'timestamp': createTimestamp(match_status.group('time'), reference_date, reference_time), + 'comment': match_status.group('comment') if match_status.group('comment') else "", + 'aprs_type': aprs_type} raise AprsParseError(message) dstcall_parser_mapping = {'APRS': OgnParser(), + 'OGNFNT': FanetParser(), 'OGFLR': FlarmParser(), 'OGNTRK': TrackerParser(), 'OGNSDR': ReceiverParser(), diff --git a/ogn/parser/pattern.py b/ogn/parser/pattern.py index e183c7f..35715df 100644 --- a/ogn/parser/pattern.py +++ b/ogn/parser/pattern.py @@ -1,8 +1,14 @@ 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