Separated parser in separate files

pull/31/head
Konstantin Gründger 2017-09-30 08:48:56 +02:00
rodzic d995f0320b
commit 127f3935d2
12 zmienionych plików z 111 dodań i 109 usunięć

Wyświetl plik

@ -1,2 +1,2 @@
from ogn.parser.parse import parse_aprs, parse_ogn_beacon, parse_ogn_receiver_beacon, parse_ogn_aircraft_beacon # flake8: noqa from ogn.parser.parse import parse_aprs, parse_ogn_beacon, parse_receiver_beacon, parse_aircraft_beacon # flake8: noqa
from ogn.parser.exceptions import ParseError, AprsParseError, OgnParseError, AmbigousTimeError # flake8: noqa from ogn.parser.exceptions import ParseError, AprsParseError, OgnParseError, AmbigousTimeError # flake8: noqa

Wyświetl plik

@ -1,11 +1,17 @@
import re import re
from datetime import datetime from datetime import datetime
from ogn.parser.utils import createTimestamp, parseAngle, kts2kmh, feet2m, fpm2ms from ogn.parser.utils import createTimestamp, parseAngle, kts2kmh, feet2m
from ogn.parser.pattern import PATTERN_APRS_POSITION, PATTERN_APRS_STATUS, PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON from ogn.parser.pattern import PATTERN_APRS_POSITION, PATTERN_APRS_STATUS
from ogn.parser.pattern import PATTERN_NAVITER_BEACON
from ogn.parser.exceptions import AprsParseError, OgnParseError from ogn.parser.exceptions import AprsParseError, OgnParseError
from ogn.parser.parse_ogn import parse_aircraft_beacon, parse_receiver_beacon
from ogn.parser.parse_naviter import parse as parse_naviter_beacon
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_spot import parse as parse_spot_beacon
from ogn.parser.parse_skylines import parse as parse_skylines_beacon
def parse_aprs(message, reference_date=None, reference_time=None): def parse_aprs(message, reference_date=None, reference_time=None):
if reference_date is None: if reference_date is None:
@ -44,114 +50,32 @@ def parse_aprs(message, reference_date=None, reference_time=None):
raise AprsParseError(message) raise AprsParseError(message)
def parse_ogn_aircraft_beacon(aprs_comment):
ac_match = re.search(PATTERN_AIRCRAFT_BEACON, aprs_comment)
if ac_match:
return {'address_type': int(ac_match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(ac_match.group('details'), 16) & 0b01111100) >> 2,
'stealth': (int(ac_match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': ac_match.group('id'),
'climb_rate': int(ac_match.group('climb_rate')) * fpm2ms if ac_match.group('climb_rate') else None,
'turn_rate': float(ac_match.group('turn_rate')) if ac_match.group('turn_rate') else None,
'flightlevel': float(ac_match.group('flight_level')) if ac_match.group('flight_level') else None,
'signal_quality': float(ac_match.group('signal_quality')) if ac_match.group('signal_quality') else None,
'error_count': int(ac_match.group('errors')) if ac_match.group('errors') else None,
'frequency_offset': float(ac_match.group('frequency_offset')) if ac_match.group('frequency_offset') else None,
'gps_status': ac_match.group('gps_accuracy') if ac_match.group('gps_accuracy') else None,
'software_version': float(ac_match.group('flarm_software_version')) if ac_match.group('flarm_software_version') else None,
'hardware_version': int(ac_match.group('flarm_hardware_version'), 16) if ac_match.group('flarm_hardware_version') else None,
'real_address': ac_match.group('flarm_id') if ac_match.group('flarm_id') else None,
'signal_power': float(ac_match.group('signal_power')) if ac_match.group('signal_power') else None,
'proximity': [hear[4:] for hear in ac_match.group('proximity').split(" ")] if ac_match.group('proximity') else None}
else:
return None
def parse_ogn_receiver_beacon(aprs_comment):
rec_match = re.search(PATTERN_RECEIVER_BEACON, aprs_comment)
if rec_match:
return {'version': rec_match.group('version'),
'platform': rec_match.group('platform'),
'cpu_load': float(rec_match.group('cpu_load')),
'free_ram': float(rec_match.group('ram_free')),
'total_ram': float(rec_match.group('ram_total')),
'ntp_error': float(rec_match.group('ntp_offset')),
'rt_crystal_correction': float(rec_match.group('ntp_correction')),
'voltage': float(rec_match.group('voltage')) if rec_match.group('voltage') else None,
'amperage': float(rec_match.group('amperage')) if rec_match.group('amperage') else None,
'cpu_temp': float(rec_match.group('cpu_temperature')) if rec_match.group('cpu_temperature') else None,
'senders_visible': int(rec_match.group('visible_senders')) if rec_match.group('visible_senders') else None,
'senders_total': int(rec_match.group('senders')) if rec_match.group('senders') else None,
'rec_crystal_correction': int(rec_match.group('rf_correction_manual')) if rec_match.group('rf_correction_manual') else None,
'rec_crystal_correction_fine': float(rec_match.group('rf_correction_automatic')) if rec_match.group('rf_correction_automatic') else None,
'rec_input_noise': float(rec_match.group('signal_quality')) if rec_match.group('signal_quality') else None,
'senders_signal': float(rec_match.group('senders_signal_quality')) if rec_match.group('senders_signal_quality') else None,
'senders_messages': float(rec_match.group('senders_messages')) if rec_match.group('senders_messages') else None,
'good_senders_signal': float(rec_match.group('good_senders_signal_quality')) if rec_match.group('good_senders_signal_quality') else None,
'good_senders': float(rec_match.group('good_senders')) if rec_match.group('good_senders') else None,
'good_and_bad_senders': float(rec_match.group('good_and_bad_senders')) if rec_match.group('good_and_bad_senders') else None}
else:
return None
def parse_lt24_beacon(aprs_comment):
raise NotImplementedError("LT24 parser not implemented")
def parse_naviter_beacon(aprs_comment):
ac_match = re.search(PATTERN_NAVITER_BEACON, aprs_comment)
return {'stealth': (int(ac_match.group('details'), 16) & 0b1000000000000000) >> 15 == 1,
'do_not_track': (int(ac_match.group('details'), 16) & 0b0100000000000000) >> 14 == 1,
'aircraft_type': (int(ac_match.group('details'), 16) & 0b0011110000000000) >> 10,
'address_type': (int(ac_match.group('details'), 16) & 0b0000001111110000) >> 4,
'reserved': (int(ac_match.group('details'), 16) & 0b0000000000001111),
'address': ac_match.group('id'),
'climb_rate': int(ac_match.group('climb_rate')) * fpm2ms if ac_match.group('climb_rate') else None,
'turn_rate': float(ac_match.group('turn_rate')) if ac_match.group('turn_rate') else None}
def parse_skylines_beacon(aprs_comment):
raise NotImplementedError("Skylines parser not implemented")
def parse_spider_beacon(aprs_comment):
raise NotImplementedError("Spider parser not implemented")
def parse_spot_beacon(aprs_comment):
raise NotImplementedError("SPOT parser not implemented")
def parse_capture_beacon(aprs_comment):
raise NotImplementedError("Capture beacon parser not yet implemented")
def parse_ogn_beacon(aprs_comment, dstcall="APRS"): def parse_ogn_beacon(aprs_comment, dstcall="APRS"):
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'}
ac_data = parse_ogn_aircraft_beacon(aprs_comment) ac_data = parse_aircraft_beacon(aprs_comment)
if ac_data: if ac_data:
ac_data.update({'beacon_type': 'aircraft_beacon'}) ac_data.update({'beacon_type': 'aircraft_beacon'})
return ac_data return ac_data
rc_data = parse_ogn_receiver_beacon(aprs_comment) rc_data = parse_receiver_beacon(aprs_comment)
if rc_data: if rc_data:
rc_data.update({'beacon_type': 'receiver_beacon'}) rc_data.update({'beacon_type': 'receiver_beacon'})
return rc_data return rc_data
raise OgnParseError(aprs_comment) raise OgnParseError(aprs_comment)
elif dstcall == "OGFLR": elif dstcall == "OGFLR":
ac_data = parse_ogn_aircraft_beacon(aprs_comment) ac_data = parse_aircraft_beacon(aprs_comment)
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_ogn_aircraft_beacon(aprs_comment) ac_data = parse_aircraft_beacon(aprs_comment)
ac_data.update({'beacon_type': 'aircraft_beacon'}) ac_data.update({'beacon_type': 'aircraft_beacon'})
return ac_data return ac_data
elif dstcall == "OGNSDR": elif dstcall == "OGNSDR":
ac_data = parse_ogn_receiver_beacon(aprs_comment) ac_data = parse_receiver_beacon(aprs_comment)
ac_data.update({'beacon_type': 'receiver_beacon'}) ac_data.update({'beacon_type': 'receiver_beacon'})
return ac_data return ac_data
elif dstcall == "OGLT24": elif dstcall == "OGLT24":

Wyświetl plik

@ -0,0 +1,2 @@
def parse(aprs_comment):
raise NotImplementedError("LT24 beacon parser not yet implemented")

Wyświetl plik

@ -0,0 +1,16 @@
import re
from ogn.parser.utils import fpm2ms
from ogn.parser.pattern import PATTERN_NAVITER_BEACON
def parse(aprs_comment):
match = re.search(PATTERN_NAVITER_BEACON, aprs_comment)
return {'stealth': (int(match.group('details'), 16) & 0b1000000000000000) >> 15 == 1,
'do_not_track': (int(match.group('details'), 16) & 0b0100000000000000) >> 14 == 1,
'aircraft_type': (int(match.group('details'), 16) & 0b0011110000000000) >> 10,
'address_type': (int(match.group('details'), 16) & 0b0000001111110000) >> 4,
'reserved': (int(match.group('details'), 16) & 0b0000000000001111),
'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}

Wyświetl plik

@ -0,0 +1,54 @@
import re
from ogn.parser.utils import fpm2ms
from ogn.parser.pattern import PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON
def parse_aircraft_beacon(aprs_comment):
ac_match = re.search(PATTERN_AIRCRAFT_BEACON, aprs_comment)
if ac_match:
return {'address_type': int(ac_match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(ac_match.group('details'), 16) & 0b01111100) >> 2,
'stealth': (int(ac_match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': ac_match.group('id'),
'climb_rate': int(ac_match.group('climb_rate')) * fpm2ms if ac_match.group('climb_rate') else None,
'turn_rate': float(ac_match.group('turn_rate')) if ac_match.group('turn_rate') else None,
'flightlevel': float(ac_match.group('flight_level')) if ac_match.group('flight_level') else None,
'signal_quality': float(ac_match.group('signal_quality')) if ac_match.group('signal_quality') else None,
'error_count': int(ac_match.group('errors')) if ac_match.group('errors') else None,
'frequency_offset': float(ac_match.group('frequency_offset')) if ac_match.group('frequency_offset') else None,
'gps_status': ac_match.group('gps_accuracy') if ac_match.group('gps_accuracy') else None,
'software_version': float(ac_match.group('flarm_software_version')) if ac_match.group('flarm_software_version') else None,
'hardware_version': int(ac_match.group('flarm_hardware_version'), 16) if ac_match.group('flarm_hardware_version') else None,
'real_address': ac_match.group('flarm_id') if ac_match.group('flarm_id') else None,
'signal_power': float(ac_match.group('signal_power')) if ac_match.group('signal_power') else None,
'proximity': [hear[4:] for hear in ac_match.group('proximity').split(" ")] if ac_match.group('proximity') else None}
else:
return None
def parse_receiver_beacon(aprs_comment):
rec_match = re.search(PATTERN_RECEIVER_BEACON, aprs_comment)
if rec_match:
return {'version': rec_match.group('version'),
'platform': rec_match.group('platform'),
'cpu_load': float(rec_match.group('cpu_load')),
'free_ram': float(rec_match.group('ram_free')),
'total_ram': float(rec_match.group('ram_total')),
'ntp_error': float(rec_match.group('ntp_offset')),
'rt_crystal_correction': float(rec_match.group('ntp_correction')),
'voltage': float(rec_match.group('voltage')) if rec_match.group('voltage') else None,
'amperage': float(rec_match.group('amperage')) if rec_match.group('amperage') else None,
'cpu_temp': float(rec_match.group('cpu_temperature')) if rec_match.group('cpu_temperature') else None,
'senders_visible': int(rec_match.group('visible_senders')) if rec_match.group('visible_senders') else None,
'senders_total': int(rec_match.group('senders')) if rec_match.group('senders') else None,
'rec_crystal_correction': int(rec_match.group('rf_correction_manual')) if rec_match.group('rf_correction_manual') else None,
'rec_crystal_correction_fine': float(rec_match.group('rf_correction_automatic')) if rec_match.group('rf_correction_automatic') else None,
'rec_input_noise': float(rec_match.group('signal_quality')) if rec_match.group('signal_quality') else None,
'senders_signal': float(rec_match.group('senders_signal_quality')) if rec_match.group('senders_signal_quality') else None,
'senders_messages': float(rec_match.group('senders_messages')) if rec_match.group('senders_messages') else None,
'good_senders_signal': float(rec_match.group('good_senders_signal_quality')) if rec_match.group('good_senders_signal_quality') else None,
'good_senders': float(rec_match.group('good_senders')) if rec_match.group('good_senders') else None,
'good_and_bad_senders': float(rec_match.group('good_and_bad_senders')) if rec_match.group('good_and_bad_senders') else None}
else:
return None

Wyświetl plik

@ -0,0 +1,2 @@
def parse(aprs_comment):
raise NotImplementedError("Skylines beacon parser not yet implemented")

Wyświetl plik

@ -0,0 +1,2 @@
def parse(aprs_comment):
raise NotImplementedError("Spider beacon parser not yet implemented")

Wyświetl plik

@ -0,0 +1,2 @@
def parse(aprs_comment):
raise NotImplementedError("SPOT beacon parser not yet implemented")

Wyświetl plik

@ -1,12 +1,12 @@
import unittest import unittest
from ogn.parser.utils import ms2fpm from ogn.parser.utils import ms2fpm
from ogn.parser.parse import parse_naviter_beacon from ogn.parser.parse_naviter import parse
class TestStringMethods(unittest.TestCase): class TestStringMethods(unittest.TestCase):
def test_OGNAVI_1(self): def test_OGNAVI_1(self):
message = parse_naviter_beacon("id0440042121 +123fpm +0.5rot") message = parse("id0440042121 +123fpm +0.5rot")
# id0440042121 == 0b0000 0100 0100 0000 0000 0100 0010 0001 0010 0001 # id0440042121 == 0b0000 0100 0100 0000 0000 0100 0010 0001 0010 0001
# bit 0: stealth mode # bit 0: stealth mode

Wyświetl plik

@ -1,15 +1,15 @@
import unittest import unittest
from ogn.parser.utils import ms2fpm from ogn.parser.utils import ms2fpm
from ogn.parser.parse import parse_ogn_aircraft_beacon from ogn.parser.parse import parse_aircraft_beacon
class TestStringMethods(unittest.TestCase): class TestStringMethods(unittest.TestCase):
def test_invalid_token(self): def test_invalid_token(self):
self.assertEqual(parse_ogn_aircraft_beacon("notAValidToken"), None) self.assertEqual(parse_aircraft_beacon("notAValidToken"), None)
def test_basic(self): def test_basic(self):
aircraft_beacon = parse_ogn_aircraft_beacon("id0ADDA5BA -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598") aircraft_beacon = parse_aircraft_beacon("id0ADDA5BA -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertEqual(aircraft_beacon['address_type'], 2) self.assertEqual(aircraft_beacon['address_type'], 2)
self.assertEqual(aircraft_beacon['aircraft_type'], 2) self.assertEqual(aircraft_beacon['aircraft_type'], 2)
@ -27,33 +27,33 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(aircraft_beacon['proximity'][2], 'B598') self.assertEqual(aircraft_beacon['proximity'][2], 'B598')
def test_stealth(self): def test_stealth(self):
aircraft_beacon = parse_ogn_aircraft_beacon("id0ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598") aircraft_beacon = parse_aircraft_beacon("id0ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertFalse(aircraft_beacon['stealth']) self.assertFalse(aircraft_beacon['stealth'])
aircraft_beacon = parse_ogn_aircraft_beacon("id8ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598") aircraft_beacon = parse_aircraft_beacon("id8ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertTrue(aircraft_beacon['stealth']) self.assertTrue(aircraft_beacon['stealth'])
def test_v024(self): def test_v024(self):
aircraft_beacon = parse_ogn_aircraft_beacon("id21400EA9 -2454fpm +0.9rot 19.5dB 0e -6.6kHz gps1x1 s6.02 h0A rDF0C56") aircraft_beacon = parse_aircraft_beacon("id21400EA9 -2454fpm +0.9rot 19.5dB 0e -6.6kHz gps1x1 s6.02 h0A rDF0C56")
self.assertEqual(aircraft_beacon['software_version'], 6.02) self.assertEqual(aircraft_beacon['software_version'], 6.02)
self.assertEqual(aircraft_beacon['hardware_version'], 10) self.assertEqual(aircraft_beacon['hardware_version'], 10)
self.assertEqual(aircraft_beacon['real_address'], "DF0C56") self.assertEqual(aircraft_beacon['real_address'], "DF0C56")
def test_v024_ogn_tracker(self): def test_v024_ogn_tracker(self):
aircraft_beacon = parse_ogn_aircraft_beacon("id07353800 +020fpm -14.0rot FL004.43 38.5dB 0e -2.9kHz") aircraft_beacon = parse_aircraft_beacon("id07353800 +020fpm -14.0rot FL004.43 38.5dB 0e -2.9kHz")
self.assertEqual(aircraft_beacon['flightlevel'], 4.43) self.assertEqual(aircraft_beacon['flightlevel'], 4.43)
def test_v025(self): def test_v025(self):
aircraft_beacon = parse_ogn_aircraft_beacon("id06DDE28D +535fpm +3.8rot 11.5dB 0e -1.0kHz gps2x3 s6.01 h0C +7.4dBm") aircraft_beacon = parse_aircraft_beacon("id06DDE28D +535fpm +3.8rot 11.5dB 0e -1.0kHz gps2x3 s6.01 h0C +7.4dBm")
self.assertEqual(aircraft_beacon['signal_power'], 7.4) self.assertEqual(aircraft_beacon['signal_power'], 7.4)
def test_v026(self): def test_v026(self):
# from 0.2.6 it is sufficent we have only the ID, climb and turn rate or just the ID # from 0.2.6 it is sufficent we have only the ID, climb and turn rate or just the ID
aircraft_beacon_triple = parse_ogn_aircraft_beacon("id093D0930 +000fpm +0.0rot") aircraft_beacon_triple = parse_aircraft_beacon("id093D0930 +000fpm +0.0rot")
aircraft_beacon_single = parse_ogn_aircraft_beacon("id093D0930") aircraft_beacon_single = parse_aircraft_beacon("id093D0930")
self.assertIsNotNone(aircraft_beacon_triple) self.assertIsNotNone(aircraft_beacon_triple)
self.assertIsNotNone(aircraft_beacon_single) self.assertIsNotNone(aircraft_beacon_single)

Wyświetl plik

@ -1,14 +1,14 @@
import unittest import unittest
from ogn.parser.parse import parse_ogn_receiver_beacon from ogn.parser.parse import parse_receiver_beacon
class TestStringMethods(unittest.TestCase): class TestStringMethods(unittest.TestCase):
def test_fail_validation(self): def test_fail_validation(self):
self.assertEqual(parse_ogn_receiver_beacon("notAValidToken"), None) self.assertEqual(parse_receiver_beacon("notAValidToken"), None)
def test_v021(self): def test_v021(self):
receiver_beacon = parse_ogn_receiver_beacon("v0.2.1 CPU:0.8 RAM:25.6/458.9MB NTP:0.1ms/+2.3ppm +51.9C RF:+26-1.4ppm/-0.25dB") receiver_beacon = parse_receiver_beacon("v0.2.1 CPU:0.8 RAM:25.6/458.9MB NTP:0.1ms/+2.3ppm +51.9C RF:+26-1.4ppm/-0.25dB")
self.assertEqual(receiver_beacon['version'], "0.2.1") self.assertEqual(receiver_beacon['version'], "0.2.1")
self.assertEqual(receiver_beacon['cpu_load'], 0.8) self.assertEqual(receiver_beacon['cpu_load'], 0.8)
@ -23,17 +23,17 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(receiver_beacon['rec_input_noise'], -0.25) self.assertEqual(receiver_beacon['rec_input_noise'], -0.25)
def test_v022(self): 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") receiver_beacon = parse_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['platform'], 'x86') self.assertEqual(receiver_beacon['platform'], 'x86')
def test_v025(self): 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 5.016V 0.534A +51.9C RF:+55+0.4ppm/-0.67dB/+10.8dB@10km[57282]") receiver_beacon = parse_receiver_beacon("v0.2.5.RPI-GPU CPU:0.8 RAM:287.3/458.7MB NTP:1.0ms/-6.4ppm 5.016V 0.534A +51.9C RF:+55+0.4ppm/-0.67dB/+10.8dB@10km[57282]")
self.assertEqual(receiver_beacon['voltage'], 5.016) self.assertEqual(receiver_beacon['voltage'], 5.016)
self.assertEqual(receiver_beacon['amperage'], 0.534) self.assertEqual(receiver_beacon['amperage'], 0.534)
self.assertEqual(receiver_beacon['senders_signal'], 10.8) self.assertEqual(receiver_beacon['senders_signal'], 10.8)
self.assertEqual(receiver_beacon['senders_messages'], 57282) self.assertEqual(receiver_beacon['senders_messages'], 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]") receiver_beacon = parse_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['senders_visible'], 14) self.assertEqual(receiver_beacon['senders_visible'], 14)
self.assertEqual(receiver_beacon['senders_total'], 16) self.assertEqual(receiver_beacon['senders_total'], 16)
self.assertEqual(receiver_beacon['senders_signal'], 24.0) self.assertEqual(receiver_beacon['senders_signal'], 24.0)