Implemented new receiver protocol for 0.2.5

Update for 0.2.5 protocol
pull/15/head
Konstantin Gründger 2016-10-03 14:42:50 +02:00
rodzic 13adb3efed
commit 5bcd04fe20
7 zmienionych plików z 79 dodań i 25 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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__':

Wyświetl plik

@ -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]