Merge pull request #36 from Meisterschueler/prepare/release

Prepare/release
pull/41/merge v0.8.0
Meisterschueler 2017-10-02 20:12:04 +02:00 zatwierdzone przez GitHub
commit 53ecdb251e
12 zmienionych plików z 98 dodań i 73 usunięć

Wyświetl plik

@ -3,6 +3,7 @@ language: python
python:
- 3.4
- 3.5
- 3.6
before_script:
- flake8 tests ogn

Wyświetl plik

@ -1,8 +1,11 @@
# CHANGELOG
## Unreleased
- parser: Added support for heared aircrafts
## 0.8.0 - 2017-10-02
- parser: Merged function 'parse_aprs' and 'parse_ogn_beacon' to 'parse'
- parser: Added support for OGNSDR (receiver), OGNTRK (ogn tracker), OGNFLR (flarm) and OGNAV (Naviter) beacons
- parser: Added support for RELAYed messages
- parser: Added support for ddmmss time format (eg. '312359z')
- parser: Added support for heared aircrafts
- client: Allow client to do sequential connect-disconnect
## 0.7.1 - 2017-06-05

Wyświetl plik

@ -4,7 +4,7 @@ APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = 14580
APRS_APP_NAME = 'python-ogn-client'
PACKAGE_VERSION = '0.7.1'
PACKAGE_VERSION = '0.8.0'
APRS_APP_VER = PACKAGE_VERSION[:3]
APRS_KEEPALIVE_TIME = 240

Wyświetl plik

@ -28,7 +28,7 @@ def parse(aprs_message, reference_date=None, reference_time=None):
return message
def parse_aprs(message, reference_date=None, reference_time=None):
def parse_aprs(message, reference_date, reference_time=None):
match_position = re.search(PATTERN_APRS_POSITION, message)
if match_position:
return {'name': match_position.group('callsign'),
@ -74,8 +74,9 @@ def parse_comment(aprs_comment, dstcall="APRS", aprs_type="position"):
if rc_data:
rc_data.update({'beacon_type': 'receiver_beacon'})
return rc_data
raise OgnParseError(aprs_comment)
else:
return {'user_comment': aprs_comment,
'beacon_type': 'receiver_beacon'}
elif dstcall == "OGFLR":
ac_data = parse_aircraft_beacon(aprs_comment)
ac_data.update({'beacon_type': 'aircraft_beacon'})
@ -117,4 +118,4 @@ def parse_comment(aprs_comment, dstcall="APRS", aprs_type="position"):
ac_data.update({'beacon_type': 'spot_beacon'})
return ac_data
else:
raise ValueError("dstcall {} unknown".format(dstcall))
raise OgnParseError("No parser for dstcall {} found. APRS comment: {}".format(dstcall, aprs_comment))

Wyświetl plik

@ -1,7 +1,7 @@
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>\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_STATUS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),.+,(?P<receiver>.+?):>(?P<time>\d{6}(h|z))\s(?P<comment>.*)$")
PATTERN_NAVITER_BEACON = re.compile("""

Wyświetl plik

@ -30,6 +30,7 @@ setup(
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
keywords='gliding ogn',
packages=['ogn.{}'.format(package) for package in find_packages(where='ogn')],
@ -37,8 +38,8 @@ setup(
extras_require={
'dev': [
'nose==1.3.7',
'coveralls==1.1',
'flake8==3.3.0'
'coveralls==1.2',
'flake8==3.4.1'
]
},
zip_safe=False

Wyświetl plik

@ -6,7 +6,7 @@ from ogn.client.client import create_aprs_login, AprsClient
from ogn.client.settings import APRS_APP_NAME, APRS_APP_VER
class OgnClientTest(unittest.TestCase):
class AprsClientTest(unittest.TestCase):
def test_create_aprs_login(self):
basic_login = create_aprs_login('klaus', -1, 'myApp', '0.1')
self.assertEqual('user klaus pass -1 vers myApp 0.1\n', basic_login)
@ -25,7 +25,7 @@ class OgnClientTest(unittest.TestCase):
client.connect()
client.sock.send.assert_called_once_with('user testuser pass -1 vers {} {}\n'.format(
APRS_APP_NAME, APRS_APP_VER).encode('ascii'))
client.sock.makefile.asser_called_once_with('rw')
client.sock.makefile.assert_called_once_with('rw')
@mock.patch('ogn.client.client.socket')
def test_connect_client_defined_filter(self, mock_socket):
@ -33,7 +33,7 @@ class OgnClientTest(unittest.TestCase):
client.connect()
client.sock.send.assert_called_once_with('user testuser pass -1 vers {} {} filter r/50.4976/9.9495/100\n'.format(
APRS_APP_NAME, APRS_APP_VER).encode('ascii'))
client.sock.makefile.asser_called_once_with('rw')
client.sock.makefile.assert_called_once_with('rw')
@mock.patch('ogn.client.client.socket')
def test_disconnect(self, mock_socket):
@ -72,7 +72,12 @@ class OgnClientTest(unittest.TestCase):
def process_message(raw_message):
if raw_message[0] == '#':
return
parse(raw_message)
try:
message = parse(raw_message)
print("{}: {}".format(message['beacon_type'], raw_message))
except NotImplementedError as e:
print("{}: {}".format(e, raw_message))
return
if self.remaining_messages > 0:
self.remaining_messages -= 1
else:

Wyświetl plik

@ -63,6 +63,12 @@ class TestStringMethods(unittest.TestCase):
with self.assertRaises(AprsParseError):
parse("Lachens>APRS,TCPIwontbeavalidstring")
def test_v026_chile(self):
# receiver beacons from chile have a APRS position message with a pure user comment
message = parse("VITACURA1>APRS,TCPIP*,qAC,GLIDERN4:/201146h3322.79SI07034.80W&/A=002329 Vitacura Municipal Aerodrome, Club de Planeadores Vitacura", reference_date=datetime(2015, 1, 1))
self.assertEqual(message['user_comment'], "Vitacura Municipal Aerodrome, Club de Planeadores Vitacura")
@mock.patch('ogn.parser.parse_module.createTimestamp')
def test_default_reference_date(self, createTimestamp_mock):
valid_aprs_string = "Lachens>APRS,TCPIP*,qAC,GLIDERN2:/165334h4344.70NI00639.19E&/A=005435 v0.2.1 CPU:0.3 RAM:1764.4/2121.4MB NTP:2.8ms/+4.9ppm +47.0C RF:+0.70dB"

Wyświetl plik

@ -10,7 +10,7 @@ from ogn.parser.exceptions import AprsParseError
class TestStringMethods(unittest.TestCase):
def test_fail_validation(self):
with self.assertRaises(AprsParseError):
parse_aprs("notAValidString")
parse_aprs("notAValidString", reference_date=datetime(2015, 1, 1))
def test_basic(self):
message = parse_aprs("FLRDDA5BA>APRS,qAS,LFMX:/160829h4415.41N/00600.03E'342/049/A=005524 this is a comment",
@ -71,6 +71,13 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(message['timestamp'].strftime('%d %H:%M'), "30 10:46")
def test_negative_altitude(self):
# some devices can report negative altitudes
raw_message = "OGNF71F40>APRS,qAS,NAVITER:/080852h4414.37N/01532.06E'253/052/A=-00013 !W73! id1EF71F40 -060fpm +0.0rot"
message = parse_aprs(raw_message, reference_date=datetime(2015, 1, 1))
self.assertAlmostEqual(message['altitude'] * m2feet, -13, 5)
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -9,54 +9,54 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(parse_aircraft_beacon("notAValidToken"), None)
def test_basic(self):
aircraft_beacon = parse_aircraft_beacon("id0ADDA5BA -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
message = 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['aircraft_type'], 2)
self.assertFalse(aircraft_beacon['stealth'])
self.assertEqual(aircraft_beacon['address'], "DDA5BA")
self.assertAlmostEqual(aircraft_beacon['climb_rate'] * ms2fpm, -454, 2)
self.assertEqual(aircraft_beacon['turn_rate'], -1.1)
self.assertEqual(aircraft_beacon['signal_quality'], 8.8)
self.assertEqual(aircraft_beacon['error_count'], 0)
self.assertEqual(aircraft_beacon['frequency_offset'], 51.2)
self.assertEqual(aircraft_beacon['gps_status'], '4x5')
self.assertEqual(len(aircraft_beacon['proximity']), 3)
self.assertEqual(aircraft_beacon['proximity'][0], '1084')
self.assertEqual(aircraft_beacon['proximity'][1], 'B597')
self.assertEqual(aircraft_beacon['proximity'][2], 'B598')
self.assertEqual(message['address_type'], 2)
self.assertEqual(message['aircraft_type'], 2)
self.assertFalse(message['stealth'])
self.assertEqual(message['address'], "DDA5BA")
self.assertAlmostEqual(message['climb_rate'] * ms2fpm, -454, 2)
self.assertEqual(message['turn_rate'], -1.1)
self.assertEqual(message['signal_quality'], 8.8)
self.assertEqual(message['error_count'], 0)
self.assertEqual(message['frequency_offset'], 51.2)
self.assertEqual(message['gps_status'], '4x5')
self.assertEqual(len(message['proximity']), 3)
self.assertEqual(message['proximity'][0], '1084')
self.assertEqual(message['proximity'][1], 'B597')
self.assertEqual(message['proximity'][2], 'B598')
def test_stealth(self):
aircraft_beacon = parse_aircraft_beacon("id0ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertFalse(aircraft_beacon['stealth'])
message = parse_aircraft_beacon("id0ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertFalse(message['stealth'])
aircraft_beacon = parse_aircraft_beacon("id8ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertTrue(aircraft_beacon['stealth'])
message = parse_aircraft_beacon("id8ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertTrue(message['stealth'])
def test_v024(self):
aircraft_beacon = parse_aircraft_beacon("id21400EA9 -2454fpm +0.9rot 19.5dB 0e -6.6kHz gps1x1 s6.02 h0A rDF0C56")
message = 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['hardware_version'], 10)
self.assertEqual(aircraft_beacon['real_address'], "DF0C56")
self.assertEqual(message['software_version'], 6.02)
self.assertEqual(message['hardware_version'], 10)
self.assertEqual(message['real_address'], "DF0C56")
def test_v024_ogn_tracker(self):
aircraft_beacon = parse_aircraft_beacon("id07353800 +020fpm -14.0rot FL004.43 38.5dB 0e -2.9kHz")
message = parse_aircraft_beacon("id07353800 +020fpm -14.0rot FL004.43 38.5dB 0e -2.9kHz")
self.assertEqual(aircraft_beacon['flightlevel'], 4.43)
self.assertEqual(message['flightlevel'], 4.43)
def test_v025(self):
aircraft_beacon = parse_aircraft_beacon("id06DDE28D +535fpm +3.8rot 11.5dB 0e -1.0kHz gps2x3 s6.01 h0C +7.4dBm")
message = 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(message['signal_power'], 7.4)
def test_v026(self):
# from 0.2.6 it is sufficent we have only the ID, climb and turn rate or just the ID
aircraft_beacon_triple = parse_aircraft_beacon("id093D0930 +000fpm +0.0rot")
aircraft_beacon_single = parse_aircraft_beacon("id093D0930")
message_triple = parse_aircraft_beacon("id093D0930 +000fpm +0.0rot")
message_single = parse_aircraft_beacon("id093D0930")
self.assertIsNotNone(aircraft_beacon_triple)
self.assertIsNotNone(aircraft_beacon_single)
self.assertIsNotNone(message_triple)
self.assertIsNotNone(message_single)
if __name__ == '__main__':

Wyświetl plik

@ -8,39 +8,39 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(parse_receiver_beacon("notAValidToken"), None)
def test_v021(self):
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")
message = 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['cpu_load'], 0.8)
self.assertEqual(receiver_beacon['free_ram'], 25.6)
self.assertEqual(receiver_beacon['total_ram'], 458.9)
self.assertEqual(receiver_beacon['ntp_error'], 0.1)
self.assertEqual(receiver_beacon['rt_crystal_correction'], 2.3)
self.assertEqual(receiver_beacon['cpu_temp'], 51.9)
self.assertEqual(message['version'], "0.2.1")
self.assertEqual(message['cpu_load'], 0.8)
self.assertEqual(message['free_ram'], 25.6)
self.assertEqual(message['total_ram'], 458.9)
self.assertEqual(message['ntp_error'], 0.1)
self.assertEqual(message['rt_crystal_correction'], 2.3)
self.assertEqual(message['cpu_temp'], 51.9)
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)
self.assertEqual(message['rec_crystal_correction'], 26)
self.assertEqual(message['rec_crystal_correction_fine'], -1.4)
self.assertEqual(message['rec_input_noise'], -0.25)
def test_v022(self):
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')
message = 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(message['platform'], 'x86')
def test_v025(self):
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['amperage'], 0.534)
self.assertEqual(receiver_beacon['senders_signal'], 10.8)
self.assertEqual(receiver_beacon['senders_messages'], 57282)
message = 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(message['voltage'], 5.016)
self.assertEqual(message['amperage'], 0.534)
self.assertEqual(message['senders_signal'], 10.8)
self.assertEqual(message['senders_messages'], 57282)
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_total'], 16)
self.assertEqual(receiver_beacon['senders_signal'], 24.0)
self.assertEqual(receiver_beacon['senders_messages'], 143717)
self.assertEqual(receiver_beacon['good_senders_signal'], 26.7)
self.assertEqual(receiver_beacon['good_senders'], 68)
self.assertEqual(receiver_beacon['good_and_bad_senders'], 135)
message = 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(message['senders_visible'], 14)
self.assertEqual(message['senders_total'], 16)
self.assertEqual(message['senders_signal'], 24.0)
self.assertEqual(message['senders_messages'], 143717)
self.assertEqual(message['good_senders_signal'], 26.7)
self.assertEqual(message['good_senders'], 68)
self.assertEqual(message['good_and_bad_senders'], 135)
if __name__ == '__main__':

Wyświetl plik

@ -22,5 +22,6 @@ CNF3a>APRS,TCPIP*,qAC,GLIDERN3:>042143h v0.2.5.ARM CPU:0.6 RAM:514.6/970.8MB NTP
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]
#
# since 0.2.6 the ogn comment of a receiver beacon is just optional
# since 0.2.6 the ogn comment of a receiver beacon is just optional, it also can be a user comment
Ulrichamn>APRS,TCPIP*,qAC,GLIDERN1:/085616h5747.30NI01324.77E&/A=001322
ROBLE3>APRS,TCPIP*,qAC,GLIDERN4:/200022h3258.58SI07100.78W&/A=007229 Contact: achanes@manquehue.net, brito.felipe@gmail.com