def packet_track(): return APRSTrack( packets=[ APRSPacket.from_frame( "W3EAX-8>APRS,WIDE1-1,WIDE2-1,qAR,K3DO-11:!/:Gh=:j)#O /A=026909|!Q| /W3EAX,262,0,18'C,http://www.umd.edu", packet_time=pytz.timezone("America/New_York").localize( datetime(2018, 11, 11, 10, 20, 13) ), ), APRSPacket.from_frame( "W3EAX-8>APRS,N3TJJ-12,WIDE1*,WIDE2-1,qAR,N3FYI-2:!/:GiD:jcwO /A=028365|!R| /W3EAX,267,0,18'C," "http://www.umd.edu", packet_time=pytz.timezone("America/New_York").localize( datetime(2018, 11, 11, 10, 21, 24) ), ), APRSPacket.from_frame( "W3EAX-8>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=pytz.timezone("America/New_York").localize( datetime(2019, 2, 3, 14, 39, 28) ), ), ], callsign="W3EAX-8", )
def test_index(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 39, 28), ) track = APRSTrack(packets=[packet_1, packet_2, packet_3], callsign="W3EAX-13") assert track[0] == packet_1 assert track[0] is packet_1 assert track[datetime(2019, 2, 3, 14, 36, 16)] == packet_1 assert track["2019-02-03 14:36:16"] == packet_1 assert track["2019-02-03 14:38:00"] == packet_2 with pytest.raises(IndexError): track["2019-02-03 14:30:00"]
def test_append(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-8>APRS,WIDE1-1,WIDE2-1,qAR,K3DO-11:!/:Gh=:j)#O /A=026909|!Q| /W3EAX,262,0,18'C,http://www.umd.edu" ) track = APRSTrack(callsign="W3EAX-13") track.append(packet_1) track.append(packet_2) with pytest.raises(ValueError): track.append(packet_3) assert track[0] is packet_1 assert track[1] is packet_2 assert track[-1] is packet_2 assert track[:] == track assert track[1:] == APRSTrack(packets=track.packets[1:], callsign=track.callsign, crs=track.crs)
def test_packet_database(connection, credentials): table_name = "test_table" packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O " "/A=053614|!g| /W3EAX,313,0,21'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O " "/A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO " "/A=043080|!j| /W3EAX,326,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 39, 28), ) input_packets = [packet_1, packet_2, packet_3] with connection: with connection.cursor() as cursor: if database_has_table(cursor, table_name): cursor.execute(f"DROP TABLE {table_name};") ssh_kwargs = {} if credentials["tunnel"] is not None: ssh_kwargs["hostname"] = (credentials["tunnel"]["hostname"],) ssh_kwargs["username"] = (credentials["tunnel"]["username"],) ssh_kwargs["password"] = (credentials["tunnel"]["password"],) packet_table = APRSDatabaseTable( hostname=credentials["hostname"], database=credentials["database"], table=table_name, username=credentials["username"], password=credentials["password"], **ssh_kwargs, ) packet_table.insert(input_packets) assert packet_1 == packet_table[packet_1.time, packet_1.from_callsign] packets = packet_table.packets with connection: with connection.cursor() as cursor: assert database_has_table(cursor, table_name) cursor.execute(f"DROP TABLE {table_name};") assert len(packets) > 0 and all( packets[packet_index] == input_packets[packet_index] for packet_index in range(len(packets)) )
def test_time_to_ground(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=063614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 39, 28), ) packet_4 = APRSPacket.from_frame( "W3EAX-13>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 41, 50), ) packet_5 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=063614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 42, 34), ) track = APRSTrack(callsign="W3EAX-13") track.append(packet_1) assert not track.falling assert track.time_to_ground == timedelta(seconds=-1) track.append(packet_2) assert track.falling assert track.time_to_ground == timedelta(seconds=1603.148748) track.append(packet_3) assert not track.falling assert track.time_to_ground == timedelta(seconds=-1) track.append(packet_4) track.append(packet_5) assert not track.falling assert track.time_to_ground == timedelta(seconds=297.913704)
def test_subtraction(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_delta = packet_2 - packet_1 assert packet_delta.seconds == 127 assert numpy.allclose(packet_delta.ascent, -2243.0231999999996) assert numpy.allclose(packet_delta.overground, 4019.3334763155167)
def add_frames(frame: str): try: packet = APRSPacket.from_frame(frame) if packet.from_callsign in self.callsigns and packet not in packets: packets.append(packet) except InvalidPacketError: pass
def packets(self) -> List[APRSPacket]: if self.__last_access_time is not None and self.interval is not None: interval = datetime.now() - self.__last_access_time if interval < self.interval: raise TimeIntervalError( f"interval {interval} less than minimum interval {self.interval}" ) packets = [] for record in self.records: record = { field if field in ["time", "x", "y", "z"] else field.replace( "packet_", ""): value for field, value in record.items() if field not in ["point"] } record["from_callsign"] = record["from"] del record["from"] if "to" in record: record["to_callsign"] = record["to"] del record["to"] else: record["to"] = None if record["source"] is None: record["source"] = self.location packets.append(APRSPacket(**record)) self.__last_access_time = datetime.now() return packets
def from_file(cls, filename: PathLike) -> List["APRSTrack"]: tracks = LocationPacketTrack.from_file(filename) for index in range(len(tracks)): track = tracks[index] packets = [] for packet in track.packets: packets.append( APRSPacket( from_callsign=packet.attributes["from"], to_callsign=packet.attributes["to"], time=packet.time, x=packet.coordinates[0], y=packet.coordinates[1], z=packet.coordinates[2], **{ key: value for key, value in packet.attributes.items() if key not in ["from", "to"] }, )) tracks[index] = APRSTrack(packets=packets, crs=track.crs, **track.attributes) return tracks
def test_values(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) track = APRSTrack(packets=[packet_1], callsign="W3EAX-13") assert numpy.all(track.coordinates[-1] == packet_1.coordinates)
def test_sorting(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 39, 28), ) track = APRSTrack(packets=[packet_2, packet_1, packet_3], callsign="W3EAX-13") assert sorted(track) == [packet_1, packet_2, packet_3]
def test_from_frame(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) assert numpy.allclose(packet_1.coordinates, (-77.48778502911327, 39.64903419561805, 16341.5472)) assert packet_1["callsign"] == packet_1["from"] assert packet_1["callsign"] == "W3EAX-13" assert packet_1["comment"] == "|!g| /W3EAX,313,0,21'C,nearspace.umd.edu"
def test_equality(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_4 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) assert packet_2 != packet_1 assert packet_3 == packet_1 assert packet_4 != packet_1
def packets(self) -> List[LocationPacket]: if self.__last_access_time is not None and self.interval is not None: interval = datetime.now() - self.__last_access_time if interval < self.interval: raise TimeIntervalError( f"interval {interval} less than minimum interval {self.interval}" ) if Path(self.location).exists(): with open(Path( self.location).expanduser().resolve()) as file_connection: features = geojson.load(file_connection) else: response = requests.get(self.location, stream=True) features = geojson.loads(response.text) packets = [] for feature in features["features"]: if feature["geometry"]["type"] == "Point": properties = feature["properties"] time = typepigeon.convert_value(properties["time"], datetime) del properties["time"] if "from" in properties: from_callsign = properties["from"] to_callsign = properties["to"] del properties["from"], properties["to"] packet = APRSPacket( from_callsign, to_callsign, time, *feature["geometry"]["coordinates"], source=self.location, **properties, ) else: packet = LocationPacket( time, *feature["geometry"]["coordinates"], source=self.location, **properties, ) packets.append(packet) self.__last_access_time = datetime.now() return packets
def packets(self) -> List[APRSPacket]: if self.__last_access_time is not None and self.interval is not None: interval = datetime.now() - self.__last_access_time if interval < self.interval: raise TimeIntervalError( f"interval {interval} less than minimum interval {self.interval}" ) if Path(self.location).exists(): file_connection = open(Path(self.location).expanduser().resolve()) lines = file_connection.readlines() else: file_connection = requests.get(self.location, stream=True) lines = file_connection.iter_lines() packets = [] for line in lines: if len(line) > 0: if isinstance(line, bytes): line = line.decode() if line not in self.__parsed_lines: self.__parsed_lines.append(line) try: packet_time, raw_aprs = line.split(": ", 1) packet_time = typepigeon.convert_value( packet_time, datetime) except: raw_aprs = line packet_time = datetime.now() raw_aprs = raw_aprs.strip() try: packets.append( APRSPacket.from_frame(raw_aprs, packet_time, source=self.location)) except Exception as error: logging.debug(f"{error.__class__.__name__} - {error}") file_connection.close() if self.callsigns is not None: packets = [ packet for packet in packets if packet.from_callsign in self.callsigns ] self.__last_access_time = datetime.now() return packets
def __getitem__(self, key: (datetime, str)) -> APRSPacket: packet = PacketDatabaseTable.__getitem__(self, key) attributes = packet.attributes from_callsign = attributes["from"] del attributes["from"] if "to" in attributes: to_callsign = attributes["to"] del attributes["to"] else: to_callsign = None return APRSPacket( from_callsign, to_callsign, packet.time, *packet.coordinates, packet.crs, **attributes, )
def packets(self) -> List[APRSPacket]: if self.__last_access_time is not None and self.interval is not None: interval = datetime.now() - self.__last_access_time if interval < self.interval: raise TimeIntervalError( f"interval {interval} less than minimum interval {self.interval}" ) packets = [] for line in self.serial_connection.readlines(): try: packet = APRSPacket.from_frame(line, source=self.location) packets.append(packet) except Exception as error: logging.error(f"{error.__class__.__name__} - {error}") if self.callsigns is not None: packets = [ packet for packet in packets if packet.from_callsign in self.callsigns ] self.__last_access_time = datetime.now() return packets
def send(self, packets: List[APRSPacket]): if not isinstance(packets, Sequence) or isinstance(packets, str): packets = [packets] packets = [ packet if not isinstance(packet, str) else APRSPacket.from_frame(packet) for packet in packets ] if len(self.__send_buffer) > 0: packets.extend(self.__send_buffer) self.__send_buffer.clear() callsigns = {packet.from_callsign for packet in packets} packets = { callsign: [packet for packet in packets if packet.from_callsign == callsign] for callsign in callsigns } if len(packets) > 0: logging.info( f"sending {len(packets)} packet(s) to {self.location}: {packets}" ) for callsign, callsign_packets in packets.items(): try: frames = [packet.frame for packet in callsign_packets] aprs_is = aprslib.IS(callsign, aprslib.passcode(callsign), self.hostname, self.port) aprs_is.connect() if len(frames) > 0: aprs_is.sendall(r"\rn".join(frames)) aprs_is.close() except ConnectionError as error: logging.info( f"could not send packet(s) ({error}); reattempting on next iteration", ) self.__send_buffer.extend(packets)
def packets(self) -> List[APRSPacket]: if self.__last_access_time is not None and self.interval is not None: interval = datetime.now() - self.__last_access_time if interval < self.interval: raise TimeIntervalError( f"interval {interval} less than minimum interval {self.interval}" ) query = { "name": ",".join(self.callsigns), "what": "loc", "apikey": self.api_key, "format": "json", "timerange": int(timedelta(days=1) / timedelta(seconds=1)), "tail": int(timedelta(days=1) / timedelta(seconds=1)), } query = "&".join(f"{key}={value}" for key, value in query.items()) response = self.request_with_backoff(f"{self.location}?{query}") packets = [] if response["result"] == "ok": for packet_candidate in response["entries"]: try: packet = APRSPacket.from_frame(packet_candidate, source=self.location) packets.append(packet) except Exception as error: logging.error(f"{error.__class__.__name__} - {error}") else: logging.warning( f'query failure "{response["code"]}: {response["description"]}"' ) self.__last_access_time = datetime.now() return packets