kopia lustrzana https://github.com/glidernet/python-ogn-client
Implemented new receiver protocol for 0.2.5
Update for 0.2.5 protocolpull/15/head
rodzic
13adb3efed
commit
5bcd04fe20
|
@ -1,6 +1,7 @@
|
|||
# CHANGELOG
|
||||
|
||||
## Unreleased
|
||||
- updated the parser for the 0.2.5 protocol
|
||||
|
||||
## 0.5.0 - 2016-09-29
|
||||
- Added aprs destination callsign as `dstcall` to aprs beacon keys (#9)
|
||||
|
|
|
@ -2,7 +2,7 @@ import re
|
|||
from datetime import datetime
|
||||
|
||||
from ogn.parser.utils import createTimestamp, parseAngle, kts2kmh, feet2m, fpm2ms
|
||||
from ogn.parser.pattern import PATTERN_APRS, PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON
|
||||
from ogn.parser.pattern import PATTERN_APRS_POSITION, PATTERN_APRS_STATUS, PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON
|
||||
from ogn.parser.exceptions import AprsParseError, OgnParseError
|
||||
|
||||
|
||||
|
@ -12,22 +12,30 @@ def parse_aprs(message, reference_date=None, reference_time=None):
|
|||
reference_date = now.date()
|
||||
reference_time = now.time()
|
||||
|
||||
match = re.search(PATTERN_APRS, message)
|
||||
if match:
|
||||
return {'name': match.group('callsign'),
|
||||
'receiver_name': match.group('receiver'),
|
||||
'dstcall': match.group('dstcall'),
|
||||
'timestamp': createTimestamp(match.group('time'), reference_date, reference_time),
|
||||
'latitude': parseAngle('0' + match.group('latitude') + (match.group('latitude_enhancement') or '0')) *
|
||||
(-1 if match.group('latitude_sign') == 'S' else 1),
|
||||
'symboltable': match.group('symbol_table'),
|
||||
'longitude': parseAngle(match.group('longitude') + (match.group('longitude_enhancement') or '0')) *
|
||||
(-1 if match.group('longitude_sign') == 'W' else 1),
|
||||
'symbolcode': match.group('symbol'),
|
||||
'track': int(match.group('course')) if match.group('course_extension') else 0,
|
||||
'ground_speed': int(match.group('ground_speed')) * kts2kmh if match.group('ground_speed') else 0,
|
||||
'altitude': int(match.group('altitude')) * feet2m,
|
||||
'comment': match.group('comment')}
|
||||
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'),
|
||||
'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 0,
|
||||
'ground_speed': int(match_position.group('ground_speed')) * kts2kmh if match_position.group('ground_speed') else 0,
|
||||
'altitude': int(match_position.group('altitude')) * feet2m,
|
||||
'comment': match_position.group('comment')}
|
||||
|
||||
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'),
|
||||
'timestamp': createTimestamp(match_status.group('time'), reference_date, reference_time),
|
||||
'comment': match_status.group('comment')}
|
||||
|
||||
raise AprsParseError(message)
|
||||
|
||||
|
@ -64,9 +72,16 @@ def parse_ogn_receiver_beacon(aprs_comment):
|
|||
'ntp_error': float(rec_match.group('ntp_offset')),
|
||||
'rt_crystal_correction': float(rec_match.group('ntp_correction')),
|
||||
'cpu_temp': float(rec_match.group('cpu_temperature')) if rec_match.group('cpu_temperature') else None,
|
||||
'aircraft_counter_visible': int(rec_match.group('aircraft_counter_visible')) if rec_match.group('aircraft_counter_visible') else None,
|
||||
'aircraft_counter_total': int(rec_match.group('aircraft_counter_total')) if rec_match.group('aircraft_counter_total') else None,
|
||||
'rec_crystal_correction': int(rec_match.group('manual_correction')) if rec_match.group('manual_correction') else 0,
|
||||
'rec_crystal_correction_fine': float(rec_match.group('automatic_correction')) if rec_match.group('automatic_correction') else 0.0,
|
||||
'rec_input_noise': float(rec_match.group('input_noise')) if rec_match.group('input_noise') else None}
|
||||
'rec_input_noise': float(rec_match.group('input_noise')) if rec_match.group('input_noise') else None,
|
||||
'total_snr': float(rec_match.group('total_snr')) if rec_match.group('total_snr') else None,
|
||||
'total_fixes': float(rec_match.group('total_fixes')) if rec_match.group('total_fixes') else None,
|
||||
'daily_snr_selection': float(rec_match.group('daily_snr_selection')) if rec_match.group('daily_snr_selection') else None,
|
||||
'daily_devices_selection': float(rec_match.group('daily_devices_selection')) if rec_match.group('daily_devices_selection') else None,
|
||||
'daily_devices': float(rec_match.group('daily_devices')) if rec_match.group('daily_devices') else None}
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import re
|
||||
|
||||
|
||||
PATTERN_APRS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),.+,(?P<receiver>.+?):/(?P<time>\d{6})+h(?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<receiver>.+?):/(?P<time>\d{6})+h(?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>.*)$")
|
||||
|
||||
# The following regexp patterns are part of the ruby ogn-client.
|
||||
# source: https://github.com/svoop/ogn_client-ruby
|
||||
|
@ -37,12 +38,15 @@ PATTERN_RECEIVER_BEACON = re.compile(r"""
|
|||
RAM:(?P<ram_free>[\d.]+)\/(?P<ram_total>[\d.]+)MB\s
|
||||
NTP:(?P<ntp_offset>[\d.]+)ms\/(?P<ntp_correction>[+-][\d.]+)ppm\s?
|
||||
(?:(?P<cpu_temperature>[+-][\d.]+)C\s*)?
|
||||
(?:(?P<aircraft_counter_visible>\d+)\/(?P<aircraft_counter_total>\d+)Acfts\[1h\]\s*)?
|
||||
(?:RF:
|
||||
(?:
|
||||
(?P<manual_correction>[+-][\d]+)
|
||||
(?P<automatic_correction>[+-][\d.]+)ppm\/
|
||||
)?
|
||||
(?P<input_noise>[+-][\d.]+)dB
|
||||
(?:\/(?P<total_snr>[+-][\d.]+)dB\@10km\[(?P<total_fixes>\d+)\])?
|
||||
(?:\/(?P<daily_snr_selection>[+-][\d.]+)dB\@10km\[(?P<daily_devices_selection>\d+)\/(?P<daily_devices>\d+)\])?
|
||||
)?
|
||||
""", re.VERBOSE | re.MULTILINE)
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ class OgnClientTest(unittest.TestCase):
|
|||
client.sock.shutdown.assert_called_once_with(0)
|
||||
client.sock.close.assert_called_once_with()
|
||||
|
||||
@unittest.skip("messages from rtlsdr_ogn v0.2.5 are not supported yet")
|
||||
def test_50_live_messages(self):
|
||||
print("Enter")
|
||||
self.remaining_messages = 50
|
||||
|
|
|
@ -28,12 +28,22 @@ class TestStringMethods(unittest.TestCase):
|
|||
self.assertEqual(message['comment'], "this is a comment")
|
||||
|
||||
def test_v024(self):
|
||||
# higher precision datum format introduced
|
||||
raw_message = "FLRDDA5BA>APRS,qAS,LFMX:/160829h4415.41N/00600.03E'342/049/A=005524 !W26! id21400EA9 -2454fpm +0.9rot 19.5dB 0e -6.6kHz gps1x1 s6.02 h44 rDF0C56"
|
||||
message = parse_aprs(raw_message, reference_date=datetime(2015, 1, 1, 16, 8, 29))
|
||||
|
||||
self.assertAlmostEqual(message['latitude'] - 44.2568 - 1 / 30000, 2 / 1000 / 60, 10)
|
||||
self.assertAlmostEqual(message['longitude'] - 6.0005, 6 / 1000 / 60, 10)
|
||||
|
||||
def test_v025(self):
|
||||
# introduced the "aprs status" format where many informations (lat, lon, alt, speed, ...) are just optional
|
||||
raw_message = "EPZR>APRS,TCPIP*,qAC,GLIDERN1:>093456h this is a comment"
|
||||
message = parse_aprs(raw_message)
|
||||
|
||||
self.assertEqual(message['name'], "EPZR")
|
||||
self.assertEqual(message['receiver_name'], "GLIDERN1")
|
||||
self.assertEqual(message['timestamp'].strftime('%H:%M:%S'), "09:34:56")
|
||||
self.assertEqual(message['comment'], "this is a comment")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -7,6 +7,12 @@ class TestStringMethods(unittest.TestCase):
|
|||
def test_fail_validation(self):
|
||||
self.assertEqual(parse_ogn_receiver_beacon("notAValidToken"), None)
|
||||
|
||||
def test_v021(self):
|
||||
receiver_beacon = parse_ogn_receiver_beacon("v0.2.1 CPU:0.8 RAM:25.6/458.9MB NTP:0.0ms/+0.0ppm +51.9C RF:+26-1.4ppm/-0.25dB")
|
||||
self.assertEqual(receiver_beacon['rec_crystal_correction'], 26)
|
||||
self.assertEqual(receiver_beacon['rec_crystal_correction_fine'], -1.4)
|
||||
self.assertEqual(receiver_beacon['rec_input_noise'], -0.25)
|
||||
|
||||
def test_v022(self):
|
||||
receiver_beacon = parse_ogn_receiver_beacon("v0.2.2.x86 CPU:0.5 RAM:669.9/887.7MB NTP:1.0ms/+6.2ppm +52.0C RF:+0.06dB")
|
||||
self.assertEqual(receiver_beacon['version'], '0.2.2')
|
||||
|
@ -20,11 +26,19 @@ class TestStringMethods(unittest.TestCase):
|
|||
self.assertEqual(receiver_beacon['rec_crystal_correction_fine'], 0.0)
|
||||
self.assertEqual(receiver_beacon['rec_input_noise'], 0.06)
|
||||
|
||||
def test_v021(self):
|
||||
receiver_beacon = parse_ogn_receiver_beacon("v0.2.1 CPU:0.8 RAM:25.6/458.9MB NTP:0.0ms/+0.0ppm +51.9C RF:+26-1.4ppm/-0.25dB")
|
||||
self.assertEqual(receiver_beacon['rec_crystal_correction'], 26)
|
||||
self.assertEqual(receiver_beacon['rec_crystal_correction_fine'], -1.4)
|
||||
self.assertEqual(receiver_beacon['rec_input_noise'], -0.25)
|
||||
def test_v025(self):
|
||||
receiver_beacon = parse_ogn_receiver_beacon("v0.2.5.RPI-GPU CPU:0.8 RAM:287.3/458.7MB NTP:1.0ms/-6.4ppm +51.9C RF:+55+0.4ppm/-0.67dB/+10.8dB@10km[57282]")
|
||||
self.assertEqual(receiver_beacon['total_snr'], 10.8)
|
||||
self.assertEqual(receiver_beacon['total_fixes'], 57282)
|
||||
|
||||
receiver_beacon = parse_ogn_receiver_beacon("v0.2.5.ARM CPU:0.4 RAM:638.0/970.5MB NTP:0.2ms/-1.1ppm +65.5C 14/16Acfts[1h] RF:+45+0.0ppm/+3.88dB/+24.0dB@10km[143717]/+26.7dB@10km[68/135]")
|
||||
self.assertEqual(receiver_beacon['aircraft_counter_visible'], 14)
|
||||
self.assertEqual(receiver_beacon['aircraft_counter_total'], 16)
|
||||
self.assertEqual(receiver_beacon['total_snr'], 24.0)
|
||||
self.assertEqual(receiver_beacon['total_fixes'], 143717)
|
||||
self.assertEqual(receiver_beacon['daily_snr_selection'], 26.7)
|
||||
self.assertEqual(receiver_beacon['daily_devices_selection'], 68)
|
||||
self.assertEqual(receiver_beacon['daily_devices'], 135)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -15,3 +15,14 @@ Salland>APRS,TCPIP*,qAC,GLIDERN2:/165426h5227.93NI00620.03E&/A=000049 v0.2.2 CPU
|
|||
LSGS>APRS,TCPIP*,qAC,GLIDERN1:/165345h4613.25NI00719.68E&/A=001581 CPU:0.7 RAM:247.9/456.4MB NTP:0.7ms/-11.4ppm +44.4C RF:+53+71.9ppm/+0.4dB
|
||||
Drenstein>APRS,TCPIP*,qAC,GLIDERN1:/165011h5147.51NI00744.45E&/A=000213 v0.2.2 CPU:0.8 RAM:695.7/4025.5MB NTP:16000.0ms/+0.0ppm +63.0C
|
||||
ZK-GSC>APRS,qAS,Omarama:/165202h4429.25S/16959.33E'/A=001407 id05C821EA +020fpm +0.0rot 16.8dB 0e -3.1kHz gps1x3 hear1084 hearB597 hearB598
|
||||
# since 0.2.5 for receiver information not only the "aprs position" format is used but also the "aprs status" format (without lat/lon/alt informations)
|
||||
Cordoba>APRS,TCPIP*,qAC,GLIDERN3:/194847h3112.85SI06409.56W&/A=001712 v0.2.5.ARM CPU:0.4 RAM:755.4/970.8MB NTP:6.7ms/-0.1ppm +45.5C RF:+48+18.3ppm/+3.45dB
|
||||
Cordoba>APRS,TCPIP*,qAC,GLIDERN3:>194847h v0.2.5.ARM CPU:0.4 RAM:755.4/970.8MB NTP:6.7ms/-0.1ppm +45.5C 0/0Acfts[1h] RF:+48+18.3ppm/+3.45dB/+0.4dB@10km[71]/+0.4dB@10km[1/1]
|
||||
VITACURA1>APRS,TCPIP*,qAC,GLIDERN3:/042149h3322.81SI07034.95W&/A=002345 v0.2.5.ARM CPU:0.6 RAM:694.4/970.5MB NTP:0.8ms/-7.5ppm +54.8C RF:+0-0.2ppm/+3.81dB
|
||||
VITACURA1>APRS,TCPIP*,qAC,GLIDERN3:>042149h v0.2.5.ARM CPU:0.6 RAM:694.4/970.5MB NTP:0.8ms/-7.5ppm +54.8C 0/0Acfts[1h] RF:+0-0.2ppm/+3.81dB/+1.3dB@10km[132205]/+6.6dB@10km[10/20]
|
||||
Arnsberg>APRS,TCPIP*,qAC,GLIDERN1:/042146h5123.04NI00803.77E&/A=000623 v0.2.5.ARM CPU:0.4 RAM:765.1/970.8MB NTP:0.4ms/-1.7ppm +62.3C RF:+27+1.1ppm/+3.17dB
|
||||
Arnsberg>APRS,TCPIP*,qAC,GLIDERN1:>042146h v0.2.5.ARM CPU:0.4 RAM:764.9/970.8MB NTP:0.4ms/-1.7ppm +62.3C 0/0Acfts[1h] RF:+27+1.1ppm/+3.17dB/+9.2dB@10km[44487]/+12.1dB@10km[20/40]
|
||||
CNF3a>APRS,TCPIP*,qAC,GLIDERN3:/042143h4529.25NI07505.65W&/A=000259 v0.2.5.ARM CPU:0.6 RAM:514.6/970.8MB NTP:4.5ms/-1.5ppm +27.2C RF:+0-0.4ppm/+18.69dB
|
||||
CNF3a>APRS,TCPIP*,qAC,GLIDERN3:>042143h v0.2.5.ARM CPU:0.6 RAM:514.6/970.8MB NTP:4.5ms/-1.5ppm +27.2C 0/0Acfts[1h] RF:+0-0.4ppm/+18.69dB/+13.0dB@10km[104282]/+9.7dB@10km[2/3]
|
||||
VITACURA2>APRS,TCPIP*,qAC,GLIDERN3:/042136h3322.81SI07034.95W&/A=002345 v0.2.5.ARM CPU:0.3 RAM:695.0/970.5MB NTP:0.6ms/-5.7ppm +51.5C RF:+0-0.0ppm/+1.32dB
|
||||
VITACURA2>APRS,TCPIP*,qAC,GLIDERN3:>042136h v0.2.5.ARM CPU:0.3 RAM:695.0/970.5MB NTP:0.6ms/-5.7ppm +52.1C 0/0Acfts[1h] RF:+0-0.0ppm/+1.32dB/+2.1dB@10km[193897]/+9.0dB@10km[10/20]
|
Ładowanie…
Reference in New Issue