From 2e224a16b567998bb9ae297aa201ad1d947cc65a Mon Sep 17 00:00:00 2001 From: "Fabian P. Schmidt" Date: Thu, 10 Dec 2015 17:25:43 +0100 Subject: [PATCH 1/2] Add user-defined exception AmbigousTimeError --- ogn/exceptions.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/ogn/exceptions.py b/ogn/exceptions.py index 3a98ff5..fcbb6c1 100644 --- a/ogn/exceptions.py +++ b/ogn/exceptions.py @@ -1,20 +1,34 @@ """ exception definitions """ +from datetime import datetime class AprsParseError(Exception): """Parse error while parsing an aprs packet.""" def __init__(self, aprs_string): - self.message = "This is not a valid APRS string: %s" % aprs_string - super(AprsParseError, self).__init__(self.message) self.aprs_string = aprs_string + self.message = "This is not a valid APRS string: {}".format(aprs_string) + super(AprsParseError, self).__init__(self.message) + class OgnParseError(Exception): - """Parse error while parsing an aprs packet substring""" + """Parse error while parsing an aprs packet substring.""" def __init__(self, substring, expected_type): - self.message = "For type %s this is not a valid token: %s" % (expected_type, substring) - super(OgnParseError, self).__init__(self.message) self.substring = substring self.expected_type = expected_type + + self.message = "For type {} this is not a valid token: {}".format(expected_type, substring) + super(OgnParseError, self).__init__(self.message) + + +class AmbigousTimeError(Exception): + """Timstamp from the past/future, can't fully reconstruct datetime from timestamp.""" + def __init__(self, reference, packet_time): + self.reference = reference + self.packet_time = packet_time + self.timedelta = reference - datetime.combine(reference, packet_time) + + self.message = "Can't reconstruct timstamp, {:.0f}s from past.".format(self.timedelta.total_seconds()) + super(AmbigousTimeError, self).__init__(self.message) From 757e9143a8a36264022601f482802a9548e863bd Mon Sep 17 00:00:00 2001 From: "Fabian P. Schmidt" Date: Thu, 10 Dec 2015 17:28:29 +0100 Subject: [PATCH 2/2] aprs_utils: Raise AmbigousTimeError for outdated packets The aprs string only contains time information, no date. Manual date correction is applied +-one hour around midnight. Thus, packets older than one hour are dropped. --- ogn/aprs_parser.py | 6 ++++-- ogn/aprs_utils.py | 25 ++++++++++++++----------- ogn/gateway/client.py | 5 ++++- ogn/model/beacon.py | 5 +++-- tests/test_aprs_parser.py | 9 ++++++--- tests/test_aprs_utils.py | 5 +++-- tests/valid_beacons.txt | 22 +++++++++++----------- 7 files changed, 45 insertions(+), 32 deletions(-) diff --git a/ogn/aprs_parser.py b/ogn/aprs_parser.py index cca676b..2e353c6 100644 --- a/ogn/aprs_parser.py +++ b/ogn/aprs_parser.py @@ -1,8 +1,10 @@ +from datetime import datetime + from .model import Beacon, AircraftBeacon, ReceiverBeacon from ogn.exceptions import AprsParseError -def parse_aprs(packet): +def parse_aprs(packet, reference_date=datetime.utcnow()): if not isinstance(packet, str): raise TypeError("Expected packet to be str, got %s" % type(packet)) elif packet == "": @@ -11,7 +13,7 @@ def parse_aprs(packet): return None beacon = Beacon() - beacon.parse(packet) + beacon.parse(packet, reference_date) # symboltable / symbolcodes used by OGN: # I&: used as receiver diff --git a/ogn/aprs_utils.py b/ogn/aprs_utils.py index 90b319e..abb44ff 100644 --- a/ogn/aprs_utils.py +++ b/ogn/aprs_utils.py @@ -1,6 +1,8 @@ from datetime import datetime, timedelta import math +from ogn.exceptions import AmbigousTimeError + kmh2kts = 0.539957 feet2m = 0.3048 @@ -18,18 +20,19 @@ def dmsToDeg(dms): return d + m -def createTimestamp(hhmmss, reference=datetime.utcnow(), validate=False): - hh = int(hhmmss[0:2]) - mm = int(hhmmss[2:4]) - ss = int(hhmmss[4:6]) +def createTimestamp(hhmmss, reference): + packet_time = datetime.strptime(hhmmss, '%H%M%S').time() + timestamp = datetime.combine(reference, packet_time) - if (reference.hour == 23) & (hh == 0): - reference = reference + timedelta(days=1) - elif (reference.hour == 0) & (hh == 23): - reference = reference - timedelta(days=1) - elif validate and abs(reference.hour - hh) > 1: - raise Exception("Time difference is too big. Reference time:%s - timestamp:%s" % (reference, hhmmss)) - return datetime(reference.year, reference.month, reference.day, hh, mm, ss) + if reference.hour == 23 and timestamp.hour == 0: + timestamp = timestamp + timedelta(days=1) + elif reference.hour == 0 and timestamp.hour == 23: + timestamp = timestamp - timedelta(days=1) + + if reference - timestamp > timedelta(hours=1): + raise AmbigousTimeError(reference, packet_time) + + return timestamp def create_aprs_login(user_name, pass_code, app_name, app_version, aprs_filter=None): diff --git a/ogn/gateway/client.py b/ogn/gateway/client.py index 045f80d..3a6c7e0 100644 --- a/ogn/gateway/client.py +++ b/ogn/gateway/client.py @@ -5,7 +5,7 @@ from time import time from ogn.gateway import settings from ogn.aprs_parser import parse_aprs from ogn.aprs_utils import create_aprs_login -from ogn.exceptions import AprsParseError, OgnParseError +from ogn.exceptions import AprsParseError, OgnParseError, AmbigousTimeError class ognGateway: @@ -83,6 +83,9 @@ class ognGateway: except OgnParseError: self.logger.error('OgnParseError while parsing line: {}'.format(line), exc_info=True) return + except AmbigousTimeError as e: + self.logger.error('Drop packet, {:.0f}s from past.'.format(e.timedelta.total_seconds())) + return if beacon is not None: self.process_beacon(beacon) diff --git a/ogn/model/beacon.py b/ogn/model/beacon.py index 09bac52..4614bfe 100644 --- a/ogn/model/beacon.py +++ b/ogn/model/beacon.py @@ -1,4 +1,5 @@ import re +from datetime import datetime from sqlalchemy import Column, String, Integer, Float, DateTime from sqlalchemy.ext.declarative import AbstractConcreteBase @@ -29,7 +30,7 @@ class Beacon(AbstractConcreteBase, Base): altitude = Column(Integer) comment = None - def parse(self, text): + def parse(self, text, reference_date=datetime.utcnow()): result = re_pattern_aprs.match(text) if result is None: raise AprsParseError(text) @@ -37,7 +38,7 @@ class Beacon(AbstractConcreteBase, Base): self.name = result.group(1) self.receiver_name = result.group(2) - self.timestamp = createTimestamp(result.group(3)) + self.timestamp = createTimestamp(result.group(3), reference_date) self.latitude = dmsToDeg(float(result.group(4)) / 100) if result.group(5) == "S": diff --git a/tests/test_aprs_parser.py b/tests/test_aprs_parser.py index 6f2e545..4d29fc1 100644 --- a/tests/test_aprs_parser.py +++ b/tests/test_aprs_parser.py @@ -1,4 +1,5 @@ import unittest +from datetime import datetime from ogn.aprs_parser import parse_aprs from ogn.exceptions import AprsParseError, OgnParseError @@ -8,7 +9,7 @@ class TestStringMethods(unittest.TestCase): def test_valid_beacons(self): with open('tests/valid_beacons.txt') as f: for line in f: - parse_aprs(line) + parse_aprs(line, datetime(2015, 4, 10, 17, 0)) def test_fail_none(self): with self.assertRaises(TypeError): @@ -24,11 +25,13 @@ class TestStringMethods(unittest.TestCase): def test_incomplete_device_string(self): with self.assertRaises(OgnParseError): - parse_aprs("ICA4B0E3A>APRS,qAS,Letzi:/072319h4711.75N\\00802.59E^327/149/A=006498 id154B0E3A -395") + parse_aprs("ICA4B0E3A>APRS,qAS,Letzi:/072319h4711.75N\\00802.59E^327/149/A=006498 id154B0E3A -395", + datetime(2015, 4, 10, 7, 24)) def test_incomplete_receiver_string(self): with self.assertRaises(OgnParseError): - parse_aprs("Lachens>APRS,TCPIP*,qAC,GLIDERN2:/165334h4344.70NI00639.19E&/A=005435 v0.2.1 CPU:0.3 RAM:1764.4/21") + parse_aprs("Lachens>APRS,TCPIP*,qAC,GLIDERN2:/165334h4344.70NI00639.19E&/A=005435 v0.2.1 CPU:0.3 RAM:1764.4/21", + datetime(2015, 4, 10, 16, 54)) if __name__ == '__main__': diff --git a/tests/test_aprs_utils.py b/tests/test_aprs_utils.py index e20c2f0..9779f09 100644 --- a/tests/test_aprs_utils.py +++ b/tests/test_aprs_utils.py @@ -2,6 +2,7 @@ import unittest from datetime import datetime from ogn.aprs_utils import dmsToDeg, createTimestamp, create_aprs_login +from ogn.exceptions import AmbigousTimeError class TestStringMethods(unittest.TestCase): @@ -18,8 +19,8 @@ class TestStringMethods(unittest.TestCase): self.assertEqual(timestamp, datetime(2015, 10, 16, 0, 0, 1)) def test_createTimestamp_big_difference(self): - with self.assertRaises(Exception): - createTimestamp('123456', reference=datetime(2015, 10, 15, 23, 59, 59), validate=True) + with self.assertRaises(AmbigousTimeError): + createTimestamp('123456', reference=datetime(2015, 10, 15, 23, 59, 59)) def test_create_aprs_login(self): basic_login = create_aprs_login('klaus', -1, 'myApp', '0.1') diff --git a/tests/valid_beacons.txt b/tests/valid_beacons.txt index c051c79..bb6334b 100644 --- a/tests/valid_beacons.txt +++ b/tests/valid_beacons.txt @@ -1,17 +1,17 @@ # aprsc 2.0.14-g28c5a6a 10 Apr 2015 18:58:47 GMT GLIDERN1 37.187.40.234:14580 -FLRDDA5BA>APRS,qAS,LFMX:/160829h4415.41N/00600.03E'342/049/A=005524 id0ADDA5BA -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 -ICA4B0E3A>APRS,qAS,Letzi:/072319h4711.75N\00802.59E^327/149/A=006498 id154B0E3A -3959fpm +0.5rot 9.0dB 0e -6.3kHz gps1x3 +FLRDDA5BA>APRS,qAS,LFMX:/165829h4415.41N/00600.03E'342/049/A=005524 id0ADDA5BA -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 +ICA4B0E3A>APRS,qAS,Letzi:/165319h4711.75N\00802.59E^327/149/A=006498 id154B0E3A -3959fpm +0.5rot 9.0dB 0e -6.3kHz gps1x3 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 -LFGU>APRS,TCPIP*,qAC,GLIDERN2:/190556h4907.63NI00706.41E&/A=000833 v0.2.0 CPU:0.9 RAM:281.3/458.9MB NTP:0.5ms/-19.1ppm +53.0C RF:+0.70dB -FLRDDB091>APRS,qAS,Letzi:/195831h4740.04N/00806.01EX152/124/A=004881 id06DD8E80 +198fpm +0.0rot 6.5dB 13e +4.0kHz gps3x4 -LSGS>APRS,TCPIP*,qAC,GLIDERN1:/195345h4613.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 +LFGU>APRS,TCPIP*,qAC,GLIDERN2:/165556h4907.63NI00706.41E&/A=000833 v0.2.0 CPU:0.9 RAM:281.3/458.9MB NTP:0.5ms/-19.1ppm +53.0C RF:+0.70dB +FLRDDB091>APRS,qAS,Letzi:/165831h4740.04N/00806.01EX152/124/A=004881 id06DD8E80 +198fpm +0.0rot 6.5dB 13e +4.0kHz gps3x4 +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 FLRDDDD33>APRS,qAS,LFNF:/165341h4344.27N/00547.41E'/A=000886 id06DDDD33 +020fpm +0.0rot 20.8dB 0e -14.3kHz gps3x4 FLRDDE026>APRS,qAS,LFNF:/165341h4358.58N/00553.89E'204/055/A=005048 id06DDE026 +257fpm +0.1rot 7.2dB 0e -0.8kHz gps4x7 ICA484A9C>APRS,qAS,LFMX:/165341h4403.50N/00559.67E'/A=001460 id05484A9C +000fpm +0.0rot 18.0dB 0e +3.5kHz gps4x7 WolvesSW>APRS,TCPIP*,qAC,GLIDERN2:/165343h5232.23NI00210.91W&/A=000377 CPU:1.5 RAM:159.9/458.7MB NTP:6.6ms/-36.7ppm +45.5C RF:+130-0.4ppm/-0.1dB -Oxford>APRS,TCPIP*,qAC,GLIDERN1:/190533h5142.96NI00109.68W&/A=000380 v0.1.3 CPU:0.9 RAM:268.8/458.6MB NTP:0.5ms/-45.9ppm +60.5C RF:+55+2.9ppm/+1.54dB -OGNE95A16>APRS,qAS,Sylwek:/203641h5001.94N/01956.91E'270/004/A=000000 id07E95A16 +000fpm +0.1rot 37.8dB 0e -0.4kHz -Salland>APRS,TCPIP*,qAC,GLIDERN2:/201426h5227.93NI00620.03E&/A=000049 v0.2.2 CPU:0.7 RAM:659.3/916.9MB NTP:2.5ms/-75.0ppm RF:+0.41dB -LSGS>APRS,TCPIP*,qAC,GLIDERN1:/195345h4613.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:/203011h5147.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:/210202h4429.25S/16959.33E'/A=001407 id05C821EA +020fpm +0.0rot 16.8dB 0e -3.1kHz gps1x3 hear1084 hearB597 hearB598 \ No newline at end of file +Oxford>APRS,TCPIP*,qAC,GLIDERN1:/165533h5142.96NI00109.68W&/A=000380 v0.1.3 CPU:0.9 RAM:268.8/458.6MB NTP:0.5ms/-45.9ppm +60.5C RF:+55+2.9ppm/+1.54dB +OGNE95A16>APRS,qAS,Sylwek:/165641h5001.94N/01956.91E'270/004/A=000000 id07E95A16 +000fpm +0.1rot 37.8dB 0e -0.4kHz +Salland>APRS,TCPIP*,qAC,GLIDERN2:/165426h5227.93NI00620.03E&/A=000049 v0.2.2 CPU:0.7 RAM:659.3/916.9MB NTP:2.5ms/-75.0ppm RF:+0.41dB +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