kopia lustrzana https://github.com/glidernet/python-ogn-client
Merge pull request #32 from Meisterschueler/feature/+tracker_receiver_parser
Feature/+tracker receiver parserpull/33/head
commit
94a9150a22
|
@ -11,6 +11,10 @@ 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_spider import parse as parse_spider_beacon
|
||||||
from ogn.parser.parse_spot import parse as parse_spot_beacon
|
from ogn.parser.parse_spot import parse as parse_spot_beacon
|
||||||
from ogn.parser.parse_skylines import parse as parse_skylines_beacon
|
from ogn.parser.parse_skylines import parse as parse_skylines_beacon
|
||||||
|
from ogn.parser.parse_tracker import parse_position as parse_tracker_position
|
||||||
|
from ogn.parser.parse_tracker import parse_status as parse_tracker_status
|
||||||
|
from ogn.parser.parse_receiver import parse_position as parse_receiver_position
|
||||||
|
from ogn.parser.parse_receiver import parse_status as parse_receiver_status
|
||||||
|
|
||||||
|
|
||||||
def parse_aprs(message, reference_date=None, reference_time=None):
|
def parse_aprs(message, reference_date=None, reference_time=None):
|
||||||
|
@ -21,11 +25,18 @@ def parse_aprs(message, reference_date=None, reference_time=None):
|
||||||
|
|
||||||
match_position = re.search(PATTERN_APRS_POSITION, message)
|
match_position = re.search(PATTERN_APRS_POSITION, message)
|
||||||
if match_position:
|
if match_position:
|
||||||
|
if match_position.group('time_hhmmss'):
|
||||||
|
timestamp = createTimestamp(match_position.group('time_hhmmss'), reference_date, reference_time)
|
||||||
|
else:
|
||||||
|
timestamp_ddmmhh = match_position.group('time_ddmmhh')
|
||||||
|
reference_date = reference_date.replace(day=int(timestamp_ddmmhh[:2]))
|
||||||
|
timestamp = createTimestamp(timestamp_ddmmhh[2:] + '00', reference_date)
|
||||||
|
|
||||||
return {'name': match_position.group('callsign'),
|
return {'name': match_position.group('callsign'),
|
||||||
'dstcall': match_position.group('dstcall'),
|
'dstcall': match_position.group('dstcall'),
|
||||||
'relay': match_position.group('relay') if match_position.group('relay') else None,
|
'relay': match_position.group('relay') if match_position.group('relay') else None,
|
||||||
'receiver_name': match_position.group('receiver'),
|
'receiver_name': match_position.group('receiver'),
|
||||||
'timestamp': createTimestamp(match_position.group('time'), reference_date, reference_time),
|
'timestamp': timestamp,
|
||||||
'latitude': parseAngle('0' + match_position.group('latitude') + (match_position.group('latitude_enhancement') or '0')) *
|
'latitude': parseAngle('0' + match_position.group('latitude') + (match_position.group('latitude_enhancement') or '0')) *
|
||||||
(-1 if match_position.group('latitude_sign') == 'S' else 1),
|
(-1 if match_position.group('latitude_sign') == 'S' else 1),
|
||||||
'symboltable': match_position.group('symbol_table'),
|
'symboltable': match_position.group('symbol_table'),
|
||||||
|
@ -50,7 +61,7 @@ def parse_aprs(message, reference_date=None, reference_time=None):
|
||||||
raise AprsParseError(message)
|
raise AprsParseError(message)
|
||||||
|
|
||||||
|
|
||||||
def parse_ogn_beacon(aprs_comment, dstcall="APRS"):
|
def parse_ogn_beacon(aprs_comment, dstcall="APRS", aprs_type="position"):
|
||||||
if dstcall == "APRS": # this can be a receiver or an aircraft
|
if dstcall == "APRS": # this can be a receiver or an aircraft
|
||||||
if not aprs_comment:
|
if not aprs_comment:
|
||||||
return {'beacon_type': 'receiver_beacon'}
|
return {'beacon_type': 'receiver_beacon'}
|
||||||
|
@ -71,13 +82,21 @@ def parse_ogn_beacon(aprs_comment, dstcall="APRS"):
|
||||||
ac_data.update({'beacon_type': 'aircraft_beacon'})
|
ac_data.update({'beacon_type': 'aircraft_beacon'})
|
||||||
return ac_data
|
return ac_data
|
||||||
elif dstcall == "OGNTRK":
|
elif dstcall == "OGNTRK":
|
||||||
ac_data = parse_aircraft_beacon(aprs_comment)
|
if aprs_type == "position":
|
||||||
ac_data.update({'beacon_type': 'aircraft_beacon'})
|
data = parse_tracker_position(aprs_comment)
|
||||||
return ac_data
|
data.update({'beacon_type': 'aircraft_beacon'})
|
||||||
|
elif aprs_type == "status":
|
||||||
|
data = parse_tracker_status(aprs_comment)
|
||||||
|
data.update({'beacon_type': 'aircraft_beacon'})
|
||||||
|
return data
|
||||||
elif dstcall == "OGNSDR":
|
elif dstcall == "OGNSDR":
|
||||||
ac_data = parse_receiver_beacon(aprs_comment)
|
if aprs_type == "position":
|
||||||
ac_data.update({'beacon_type': 'receiver_beacon'})
|
data = parse_receiver_position(aprs_comment)
|
||||||
return ac_data
|
data.update({'beacon_type': 'receiver_beacon'})
|
||||||
|
elif aprs_type == "status":
|
||||||
|
data = parse_receiver_status(aprs_comment)
|
||||||
|
data.update({'beacon_type': 'receiver_beacon'})
|
||||||
|
return data
|
||||||
elif dstcall == "OGLT24":
|
elif dstcall == "OGLT24":
|
||||||
ac_data = parse_lt24_beacon(aprs_comment)
|
ac_data = parse_lt24_beacon(aprs_comment)
|
||||||
ac_data.update({'beacon_type': 'lt24_beacon'})
|
ac_data.update({'beacon_type': 'lt24_beacon'})
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ogn.parser.pattern import PATTERN_RECEIVER_POSITION, PATTERN_RECEIVER_STATUS
|
||||||
|
|
||||||
|
|
||||||
|
def parse_position(aprs_comment):
|
||||||
|
match = re.search(PATTERN_RECEIVER_POSITION, aprs_comment)
|
||||||
|
return {'user_comment': match.group('user_comment') if match.group('user_comment') else None}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_status(aprs_comment):
|
||||||
|
match = re.search(PATTERN_RECEIVER_STATUS, aprs_comment)
|
||||||
|
return {'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')),
|
||||||
|
'voltage': float(match.group('voltage')) if match.group('voltage') else None,
|
||||||
|
'amperage': float(match.group('amperage')) if match.group('amperage') else None,
|
||||||
|
'cpu_temp': float(match.group('cpu_temperature')) if match.group('cpu_temperature') else None,
|
||||||
|
'senders_visible': int(match.group('visible_senders')) if match.group('visible_senders') else None,
|
||||||
|
'senders_total': int(match.group('senders')) if match.group('senders') else None,
|
||||||
|
'rec_crystal_correction': int(match.group('rf_correction_manual')) if match.group('rf_correction_manual') else None,
|
||||||
|
'rec_crystal_correction_fine': float(match.group('rf_correction_automatic')) if match.group('rf_correction_automatic') else None,
|
||||||
|
'rec_input_noise': float(match.group('signal_quality')) if match.group('signal_quality') else None,
|
||||||
|
'senders_signal': float(match.group('senders_signal_quality')) if match.group('senders_signal_quality') else None,
|
||||||
|
'senders_messages': float(match.group('senders_messages')) if match.group('senders_messages') else None,
|
||||||
|
'good_senders_signal': float(match.group('good_senders_signal_quality')) if match.group('good_senders_signal_quality') else None,
|
||||||
|
'good_senders': float(match.group('good_senders')) if match.group('good_senders') else None,
|
||||||
|
'good_and_bad_senders': float(match.group('good_and_bad_senders')) if match.group('good_and_bad_senders') else None}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ogn.parser.utils import fpm2ms
|
||||||
|
from ogn.parser.pattern import PATTERN_TRACKER_BEACON_POSITION, PATTERN_TRACKER_BEACON_STATUS
|
||||||
|
|
||||||
|
|
||||||
|
def parse_position(aprs_comment):
|
||||||
|
match = re.search(PATTERN_TRACKER_BEACON_POSITION, aprs_comment)
|
||||||
|
return {'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('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,
|
||||||
|
'flightlevel': float(match.group('flight_level')) if match.group('flight_level') else None,
|
||||||
|
'signal_quality': float(match.group('signal_quality')) if match.group('signal_quality') else None,
|
||||||
|
'error_count': int(match.group('errors')) if match.group('errors') else None,
|
||||||
|
'frequency_offset': float(match.group('frequency_offset')) if match.group('frequency_offset') else None,
|
||||||
|
'gps_status': match.group('gps_accuracy') if match.group('gps_accuracy') else None,
|
||||||
|
'software_version': float(match.group('flarm_software_version')) if match.group('flarm_software_version') else None,
|
||||||
|
'hardware_version': int(match.group('flarm_hardware_version'), 16) if match.group('flarm_hardware_version') else None}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_status(aprs_comment):
|
||||||
|
match = re.search(PATTERN_TRACKER_BEACON_STATUS, aprs_comment)
|
||||||
|
return {'voltage': float(match.group('voltage')) if match.group('voltage') else None,
|
||||||
|
'temperature': float(match.group('temperature')) if match.group('temperature') else None}
|
|
@ -1,7 +1,7 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
PATTERN_APRS_POSITION = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),((?P<relay>[A-Za-z0-9]+)\*)?.*,(?P<receiver>.+?):/(?P<time>\d{6})+(h|z)(?P<latitude>\d{4}\.\d{2})(?P<latitude_sign>N|S)(?P<symbol_table>.)(?P<longitude>\d{5}\.\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{6})(?P<pos_extension>\s!W((?P<latitude_enhancement>\d)(?P<longitude_enhancement>\d))!)?(?:\s(?P<comment>.*))?$")
|
PATTERN_APRS_POSITION = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),((?P<relay>[A-Za-z0-9]+)\*)?.*,(?P<receiver>.+?):/((?P<time_hhmmss>\d{6})h|(?P<time_ddmmhh>\d{6})z)(?P<latitude>\d{4}\.\d{2})(?P<latitude_sign>N|S)(?P<symbol_table>.)(?P<longitude>\d{5}\.\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{6})(?P<pos_extension>\s!W((?P<latitude_enhancement>\d)(?P<longitude_enhancement>\d))!)?(?:\s(?P<comment>.*))?$")
|
||||||
PATTERN_APRS_STATUS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),.+,(?P<receiver>.+?):>(?P<time>\d{6})+h\s(?P<comment>.*)$")
|
PATTERN_APRS_STATUS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),.+,(?P<receiver>.+?):>(?P<time>\d{6})+h\s(?P<comment>.*)$")
|
||||||
|
|
||||||
PATTERN_NAVITER_BEACON = re.compile("""
|
PATTERN_NAVITER_BEACON = re.compile("""
|
||||||
|
@ -10,6 +10,60 @@ PATTERN_NAVITER_BEACON = re.compile("""
|
||||||
(?P<turn_rate>[+-][\d.]+)rot
|
(?P<turn_rate>[+-][\d.]+)rot
|
||||||
""", re.VERBOSE | re.MULTILINE)
|
""", re.VERBOSE | re.MULTILINE)
|
||||||
|
|
||||||
|
PATTERN_TRACKER_BEACON_POSITION = re.compile("""
|
||||||
|
id(?P<details>\w{2})(?P<id>\w{6}?)\s?
|
||||||
|
(?:(?P<climb_rate>[+-]\d+?)fpm\s)?
|
||||||
|
(?:(?P<turn_rate>[+-][\d.]+?)rot\s)?
|
||||||
|
(?:FL(?P<flight_level>[\d.]+)\s)?
|
||||||
|
(?:(?P<signal_quality>[\d.]+?)dB\s)?
|
||||||
|
(?:(?P<errors>\d+)e\s)?
|
||||||
|
(?:(?P<frequency_offset>[+-][\d.]+?)kHz\s?)?
|
||||||
|
(?:gps(?P<gps_accuracy>\d+x\d+)\s?)?
|
||||||
|
(?:s(?P<flarm_software_version>[\d.]+)\s?)?
|
||||||
|
(?:h(?P<flarm_hardware_version>[\dA-F]{2})\s?)?
|
||||||
|
""", re.VERBOSE | re.MULTILINE)
|
||||||
|
|
||||||
|
PATTERN_TRACKER_BEACON_STATUS = re.compile("""
|
||||||
|
h(?P<wtf1>[\d]{2})\s
|
||||||
|
v(?P<wtf2>[\d]{2})\s
|
||||||
|
(?P<wtf3>[\d]+)sat/(?P<wtf4>\d)\s
|
||||||
|
(?P<wtf5>\d+)m\s
|
||||||
|
(?P<air_pressure>[\d.]+)hPa\s
|
||||||
|
(?P<temperature>[+-][\d.]+)degC\s
|
||||||
|
(?P<wtf8>\d+)%\s
|
||||||
|
(?P<voltage>[\d.]+)V\s
|
||||||
|
(?P<wtf9>\d+)/(?P<wtf10>[+-][\d.]+)dBm\s
|
||||||
|
(?P<wtf11>\d+)/min
|
||||||
|
""", re.VERBOSE | re.MULTILINE)
|
||||||
|
|
||||||
|
PATTERN_RECEIVER_POSITION = re.compile(r"""
|
||||||
|
(?:(?P<user_comment>.+))?
|
||||||
|
""", re.VERBOSE | re.MULTILINE)
|
||||||
|
|
||||||
|
PATTERN_RECEIVER_STATUS = re.compile("""
|
||||||
|
(?:
|
||||||
|
v(?P<version>\d+\.\d+\.\d+)
|
||||||
|
(?:\.(?P<platform>.+?))?
|
||||||
|
\s)?
|
||||||
|
CPU:(?P<cpu_load>[\d.]+)\s
|
||||||
|
RAM:(?P<ram_free>[\d.]+)/(?P<ram_total>[\d.]+)MB\s
|
||||||
|
NTP:(?P<ntp_offset>[\d.]+)ms/(?P<ntp_correction>[+-][\d.]+)ppm\s
|
||||||
|
(?:(?P<voltage>[\d.]+)V\s)?
|
||||||
|
(?:(?P<amperage>[\d.]+)A\s)?
|
||||||
|
(?:(?P<cpu_temperature>[+-][\d.]+)C\s*)?
|
||||||
|
(?:(?P<visible_senders>\d+)/(?P<senders>\d+)Acfts\[1h\]\s*)?
|
||||||
|
(?:RF:
|
||||||
|
(?:
|
||||||
|
(?P<rf_correction_manual>[+-][\d]+)
|
||||||
|
(?P<rf_correction_automatic>[+-][\d.]+)ppm/
|
||||||
|
)?
|
||||||
|
(?P<signal_quality>[+-][\d.]+)dB
|
||||||
|
(?:/(?P<senders_signal_quality>[+-][\d.]+)dB@10km\[(?P<senders_messages>\d+)\])?
|
||||||
|
(?:/(?P<good_senders_signal_quality>[+-][\d.]+)dB@10km\[(?P<good_senders>\d+)/(?P<good_and_bad_senders>\d+)\])?
|
||||||
|
)?
|
||||||
|
""", re.VERBOSE | re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
# The following regexp patterns are part of the ruby ogn-client.
|
# The following regexp patterns are part of the ruby ogn-client.
|
||||||
# source: https://github.com/svoop/ogn_client-ruby
|
# source: https://github.com/svoop/ogn_client-ruby
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ class TestStringMethods(unittest.TestCase):
|
||||||
aprs = parse_aprs(line, datetime(2015, 4, 10, 17, 0))
|
aprs = parse_aprs(line, datetime(2015, 4, 10, 17, 0))
|
||||||
self.assertFalse(aprs is None)
|
self.assertFalse(aprs is None)
|
||||||
if aprs['comment']:
|
if aprs['comment']:
|
||||||
message = parse_ogn_beacon(aprs['comment'], dstcall=aprs['dstcall'])
|
message = parse_ogn_beacon(aprs['comment'], dstcall=aprs['dstcall'], aprs_type=aprs['aprs_type'])
|
||||||
self.assertEqual(message['beacon_type'], beacon_type)
|
self.assertEqual(message['beacon_type'], beacon_type)
|
||||||
|
|
||||||
def test_aprs_aircraft_beacons(self):
|
def test_aprs_aircraft_beacons(self):
|
||||||
|
|
|
@ -64,6 +64,13 @@ class TestStringMethods(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(message['relay'], "NAV07220E")
|
self.assertEqual(message['relay'], "NAV07220E")
|
||||||
|
|
||||||
|
def test_v027_ddhhmm(self):
|
||||||
|
# beacons can have hhmmss or ddhhmm timestamp
|
||||||
|
raw_message = "ICA4B0678>APRS,qAS,LSZF:/301046z4729.50N/00812.89E'227/091/A=002854 !W01! id054B0678 +040fpm +0.0rot 19.0dB 0e +1.5kHz gps1x1"
|
||||||
|
message = parse_aprs(raw_message, reference_date=datetime(2015, 1, 1, 9, 35, 29))
|
||||||
|
|
||||||
|
self.assertEqual(message['timestamp'].strftime('%d %H:%M'), "30 10:46")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Ładowanie…
Reference in New Issue