kopia lustrzana https://github.com/glidernet/python-ogn-client
Merge branch 'master' into feature/+telnet_parser
commit
c3012784ab
|
@ -1,6 +1,7 @@
|
||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
## Unreleased
|
## 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)
|
- parser: Added parser for local receiver output (port 50001)
|
||||||
|
|
||||||
## 0.8.2: - 2018-01-20
|
## 0.8.2: - 2018-01-20
|
||||||
|
|
|
@ -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}
|
|
@ -2,10 +2,11 @@ import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from ogn.parser.utils import createTimestamp, parseAngle, kts2kmh, feet2m
|
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.exceptions import AprsParseError, OgnParseError
|
||||||
|
|
||||||
from ogn.parser.aprs_comment.ogn_parser import OgnParser
|
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.lt24_parser import LT24Parser
|
||||||
from ogn.parser.aprs_comment.naviter_parser import NaviterParser
|
from ogn.parser.aprs_comment.naviter_parser import NaviterParser
|
||||||
from ogn.parser.aprs_comment.flarm_parser import FlarmParser
|
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()
|
reference_time = now.time()
|
||||||
|
|
||||||
message = parse_aprs(aprs_message, reference_date, reference_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
|
return message
|
||||||
|
|
||||||
|
|
||||||
def parse_aprs(message, reference_date, reference_time=None):
|
def parse_aprs(message, reference_date, reference_time=None):
|
||||||
match_position = re.search(PATTERN_APRS_POSITION, message)
|
if message and message[0] == '#':
|
||||||
if match_position:
|
match_server = re.search(PATTERN_APRS_SERVER, message)
|
||||||
return {'name': match_position.group('callsign'),
|
if match_server:
|
||||||
'dstcall': match_position.group('dstcall'),
|
return {'version': match_server.group('version'),
|
||||||
'relay': match_position.group('relay') if match_position.group('relay') else None,
|
'timestamp': datetime.strptime(match_server.group('timestamp'), "%d %b %Y %H:%M:%S %Z"),
|
||||||
'receiver_name': match_position.group('receiver'),
|
'server': match_server.group('server'),
|
||||||
'timestamp': createTimestamp(match_position.group('time'), reference_date, reference_time),
|
'ip_address': match_server.group('ip_address'),
|
||||||
'latitude': parseAngle('0' + match_position.group('latitude') + (match_position.group('latitude_enhancement') or '0')) *
|
'port': match_server.group('port'),
|
||||||
(-1 if match_position.group('latitude_sign') == 'S' else 1),
|
'aprs_type': 'server'}
|
||||||
'symboltable': match_position.group('symbol_table'),
|
else:
|
||||||
'longitude': parseAngle(match_position.group('longitude') + (match_position.group('longitude_enhancement') or '0')) *
|
return {'comment': message,
|
||||||
(-1 if match_position.group('longitude_sign') == 'W' else 1),
|
'aprs_type': 'comment'}
|
||||||
'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'}
|
|
||||||
|
|
||||||
match_status = re.search(PATTERN_APRS_STATUS, message)
|
match = re.search(PATTERN_APRS, message)
|
||||||
if match_status:
|
if match:
|
||||||
return {'name': match_status.group('callsign'),
|
aprs_type = 'position' if match.group('aprs_type') == '/' else 'status'
|
||||||
'dstcall': match_status.group('dstcall'),
|
aprs_body = match.group('aprs_body')
|
||||||
'receiver_name': match_status.group('receiver'),
|
if aprs_type == 'position':
|
||||||
'timestamp': createTimestamp(match_status.group('time'), reference_date, reference_time),
|
match_position = re.search(PATTERN_APRS_POSITION, aprs_body)
|
||||||
'comment': match_status.group('comment') if match_status.group('comment') else "",
|
if match_position:
|
||||||
'aprs_type': 'status'}
|
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)
|
raise AprsParseError(message)
|
||||||
|
|
||||||
|
|
||||||
dstcall_parser_mapping = {'APRS': OgnParser(),
|
dstcall_parser_mapping = {'APRS': OgnParser(),
|
||||||
|
'OGNFNT': FanetParser(),
|
||||||
'OGFLR': FlarmParser(),
|
'OGFLR': FlarmParser(),
|
||||||
'OGNTRK': TrackerParser(),
|
'OGNTRK': TrackerParser(),
|
||||||
'OGNSDR': ReceiverParser(),
|
'OGNSDR': ReceiverParser(),
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
PATTERN_APRS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),((?P<relay>[A-Za-z0-9]+)\*)?.*,(?P<receiver>.+?):(?P<aprs_type>(/|>))(?P<aprs_body>.*)$")
|
||||||
|
PATTERN_APRS_POSITION = re.compile(r"^(?P<time>(([0-1]\d|2[0-3])[0-5]\d[0-5]\dh|([0-2]\d|3[0-1])([0-1]\d|2[0-3])[0-5]\dz))(?P<latitude>9000\.00|[0-8]\d{3}\.\d{2})(?P<latitude_sign>N|S)(?P<symbol_table>.)(?P<longitude>18000\.00|1[0-7]\d{3}\.\d{2}|0\d{4}\.\d{2})(?P<longitude_sign>E|W)(?P<symbol>.)(?P<course_extension>(?P<course>\d{3})/(?P<ground_speed>\d{3}))?/A=(?P<altitude>(-\d{5}|\d{6}))(?P<pos_extension>\s!W((?P<latitude_enhancement>\d)(?P<longitude_enhancement>\d))!)?(?:\s(?P<comment>.*))?$")
|
||||||
|
PATTERN_APRS_STATUS = re.compile(r"^(?P<time>(([0-1]\d|2[0-3])[0-5]\d[0-5]\dh|([0-2]\d|3[0-1])([0-1]\d|2[0-3])[0-5]\dz))\s(?P<comment>.*)$")
|
||||||
|
PATTERN_APRS_SERVER = re.compile(r"^# aprsc (?P<version>[a-z0-9\.\-]+) (?P<timestamp>\d+ [A-Za-z]+ \d+ \d{2}:\d{2}:\d{2} GMT) (?P<server>[A-Z0-9]+) (?P<ip_address>\d+\.\d+\.\d+\.\d+):(?P<port>\d+)$")
|
||||||
|
|
||||||
PATTERN_APRS_POSITION = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),((?P<relay>[A-Za-z0-9]+)\*)?.*,(?P<receiver>.+?):/(?P<time>(([0-1]\d|2[0-3])[0-5]\d[0-5]\dh|([0-2]\d|3[0-1])([0-1]\d|2[0-3])[0-5]\dz))(?P<latitude>9000\.00|[0-8]\d{3}\.\d{2})(?P<latitude_sign>N|S)(?P<symbol_table>.)(?P<longitude>18000\.00|1[0-7]\d{3}\.\d{2}|0\d{4}\.\d{2})(?P<longitude_sign>E|W)(?P<symbol>.)(?P<course_extension>(?P<course>\d{3})/(?P<ground_speed>\d{3}))?/A=(?P<altitude>(-\d{5}|\d{6}))(?P<pos_extension>\s!W((?P<latitude_enhancement>\d)(?P<longitude_enhancement>\d))!)?(?:\s(?P<comment>.*))?$")
|
PATTERN_FANET_POSITION_COMMENT = re.compile("""
|
||||||
PATTERN_APRS_STATUS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),.+,(?P<receiver>.+?):>(?P<time>\d{6}(h|z))\s(?P<comment>.*)$")
|
(?:id(?P<id>[\dA-F]+)\s)?
|
||||||
|
(?:(?P<climb_rate>[+-]\d+)fpm)?
|
||||||
|
""", re.VERBOSE | re.MULTILINE)
|
||||||
|
|
||||||
PATTERN_LT24_POSITION_COMMENT = re.compile("""
|
PATTERN_LT24_POSITION_COMMENT = re.compile("""
|
||||||
id(?P<id>\d+)\s
|
id(?P<id>\d+)\s
|
||||||
|
|
|
@ -13,13 +13,13 @@ class TestStringMethods(unittest.TestCase):
|
||||||
def parse_valid_beacon_data_file(self, filename, beacon_type):
|
def parse_valid_beacon_data_file(self, filename, beacon_type):
|
||||||
with open(os.path.dirname(__file__) + '/valid_beacon_data/' + filename) as f:
|
with open(os.path.dirname(__file__) + '/valid_beacon_data/' + filename) as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if not line[0] == '#':
|
try:
|
||||||
try:
|
message = parse(line, datetime(2015, 4, 10, 17, 0))
|
||||||
message = parse(line, datetime(2015, 4, 10, 17, 0))
|
self.assertFalse(message is None)
|
||||||
self.assertFalse(message is None)
|
if message['aprs_type'] == 'position' or message['aprs_type'] == 'status':
|
||||||
self.assertEqual(message['beacon_type'], beacon_type)
|
self.assertEqual(message['beacon_type'], beacon_type)
|
||||||
except NotImplementedError as e:
|
except NotImplementedError as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
def test_aprs_aircraft_beacons(self):
|
def test_aprs_aircraft_beacons(self):
|
||||||
self.parse_valid_beacon_data_file(filename='aprs_aircraft.txt', beacon_type='aircraft')
|
self.parse_valid_beacon_data_file(filename='aprs_aircraft.txt', beacon_type='aircraft')
|
||||||
|
@ -27,6 +27,9 @@ class TestStringMethods(unittest.TestCase):
|
||||||
def test_aprs_receiver_beacons(self):
|
def test_aprs_receiver_beacons(self):
|
||||||
self.parse_valid_beacon_data_file(filename='aprs_receiver.txt', beacon_type='receiver')
|
self.parse_valid_beacon_data_file(filename='aprs_receiver.txt', beacon_type='receiver')
|
||||||
|
|
||||||
|
def test_aprs_fanet_beacons(self):
|
||||||
|
self.parse_valid_beacon_data_file(filename='fanet.txt', beacon_type='fanet')
|
||||||
|
|
||||||
def test_ogn_flarm_beacons(self):
|
def test_ogn_flarm_beacons(self):
|
||||||
self.parse_valid_beacon_data_file(filename='ogn_flarm.txt', beacon_type='flarm')
|
self.parse_valid_beacon_data_file(filename='ogn_flarm.txt', beacon_type='flarm')
|
||||||
|
|
||||||
|
|
|
@ -90,10 +90,31 @@ class TestStringMethods(unittest.TestCase):
|
||||||
with self.assertRaises(AprsParseError):
|
with self.assertRaises(AprsParseError):
|
||||||
parse_aprs("OGND4362A>APRS,qAS,Eternoz:/194490h4700.25N/00601.47E'003/063/A=000000 !W22! id07D4362A 0fpm +0.0rot FL000.00 2.0dB 3e -2.8kHz gps3x4 +12.2dBm", reference_date=datetime(2015, 1, 1))
|
parse_aprs("OGND4362A>APRS,qAS,Eternoz:/194490h4700.25N/00601.47E'003/063/A=000000 !W22! id07D4362A 0fpm +0.0rot FL000.00 2.0dB 3e -2.8kHz gps3x4 +12.2dBm", reference_date=datetime(2015, 1, 1))
|
||||||
|
|
||||||
|
with self.assertRaises(AprsParseError):
|
||||||
|
parse_aprs("Ulrichamn>APRS,TCPIP*,qAC,GLIDERN1:/194490h5747.30NI01324.77E&/A=001322", reference_date=datetime(2015, 1, 1))
|
||||||
|
|
||||||
def test_invalid_altitude(self):
|
def test_invalid_altitude(self):
|
||||||
with self.assertRaises(AprsParseError):
|
with self.assertRaises(AprsParseError):
|
||||||
parse_aprs("Ulrichamn>APRS,TCPIP*,qAC,GLIDERN1:/085616h5747.30NI01324.77E&/A=12-345", reference_date=datetime(2015, 1, 1))
|
parse_aprs("Ulrichamn>APRS,TCPIP*,qAC,GLIDERN1:/085616h5747.30NI01324.77E&/A=12-345", reference_date=datetime(2015, 1, 1))
|
||||||
|
|
||||||
|
def test_bad_comment(self):
|
||||||
|
raw_message = "# bad configured ogn receiver"
|
||||||
|
message = parse_aprs(raw_message, reference_date=datetime(2015, 1, 1))
|
||||||
|
|
||||||
|
self.assertEqual(message['comment'], raw_message)
|
||||||
|
self.assertEqual(message['aprs_type'], 'comment')
|
||||||
|
|
||||||
|
def test_server_comment(self):
|
||||||
|
raw_message = "# aprsc 2.1.4-g408ed49 17 Mar 2018 09:30:36 GMT GLIDERN1 37.187.40.234:10152"
|
||||||
|
message = parse_aprs(raw_message, reference_date=datetime(2015, 1, 1))
|
||||||
|
|
||||||
|
self.assertEqual(message['version'], '2.1.4-g408ed49')
|
||||||
|
self.assertEqual(message['timestamp'], datetime(2018, 3, 17, 9, 30, 36))
|
||||||
|
self.assertEqual(message['server'], 'GLIDERN1')
|
||||||
|
self.assertEqual(message['ip_address'], '37.187.40.234')
|
||||||
|
self.assertEqual(message['port'], '10152')
|
||||||
|
self.assertEqual(message['aprs_type'], 'server')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from ogn.parser.utils import ms2fpm
|
||||||
|
from ogn.parser.aprs_comment.fanet_parser import FanetParser
|
||||||
|
|
||||||
|
|
||||||
|
class TestStringMethods(unittest.TestCase):
|
||||||
|
def test_regular_beacon(self):
|
||||||
|
message = FanetParser.parse_position("id1E1103CE -02fpm")
|
||||||
|
|
||||||
|
self.assertEqual(message['id'], "1E1103CE")
|
||||||
|
self.assertAlmostEqual(message['climb_rate'] * ms2fpm, -2, 0.1)
|
||||||
|
|
||||||
|
def test_pseudo_status_beacon(self):
|
||||||
|
message = FanetParser.parse_position("")
|
||||||
|
|
||||||
|
self.assertIsNone(message['id'])
|
||||||
|
self.assertIsNone(message['climb_rate'])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -0,0 +1,8 @@
|
||||||
|
# With OGN software 0.2.7 receivers have the dstcall "OGNFNT"
|
||||||
|
#
|
||||||
|
FNT1103CE>OGNFNT,qAS,FNB1103CE:/183727h5057.94N/00801.00Eg355/002/A=001042 !W10! id1E1103CE +03fpm
|
||||||
|
FNT1103CE>OGNFNT,qAS,FNB1103CE:/183729h5057.94N/00801.00Eg354/001/A=001042 !W10! id1E1103CE +07fpm
|
||||||
|
FNT1103CE>OGNFNT,qAS,FNB1103CE:/183731h5057.94N/00801.00Eg354/001/A=001042 !W10! id1E1103CE +05fpm
|
||||||
|
FNT1103CE>OGNFNT,qAS,FNB1103CE:/183734h5057.94N/00801.00Eg354/001/A=001042 !W30! id1E1103CE -10fpm
|
||||||
|
FNT1103CE>OGNFNT,qAS,FNB1103CE:/183736h5057.94N/00801.00Eg354/001/A=001042 !W40! id1E1103CE -02fpm
|
||||||
|
FNB1103CE>OGNFNT,TCPIP*,qAC,GLIDERN3:/183738h5057.95NI00801.00E&/A=001042
|
Ładowanie…
Reference in New Issue