Skip to content

Commit

Permalink
move members into submodules
Browse files Browse the repository at this point in the history
  • Loading branch information
zacharyburnett committed May 9, 2021
1 parent 9688931 commit 09fa1a3
Show file tree
Hide file tree
Showing 22 changed files with 292 additions and 249 deletions.
2 changes: 1 addition & 1 deletion examples/read_text_file.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from packetraven import RawAPRSTextFile
from packetraven.tracks import APRSTrack
from packetraven.packets.tracks import APRSTrack

if __name__ == '__main__':
filename = 'http://bpp.umd.edu/archives/Launches/NS-95_2020-11-07/APRS/W3EAX-11/W3EAX-11_raw_NS95.txt'
Expand Down
31 changes: 18 additions & 13 deletions packetraven/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import humanize as humanize
import numpy

from packetraven.base import PacketSource
from packetraven.connections import (
APRSDatabaseTable,
APRSfi,
Expand All @@ -22,11 +21,12 @@
SerialTNC,
TimeIntervalError,
)
from packetraven.connections.base import PacketSource
from packetraven.packets import APRSPacket
from packetraven.packets.tracks import APRSTrack, LocationPacketTrack
from packetraven.packets.writer import write_packet_tracks
from packetraven.predicts import PredictionAPIURL, PredictionError, get_predictions
from packetraven.tracks import APRSTrack, LocationPacketTrack
from packetraven.utilities import get_logger, read_configuration, repository_root
from packetraven.writer import write_packet_tracks

LOGGER = get_logger('packetraven', log_format='%(asctime)s | %(message)s')

Expand Down Expand Up @@ -490,8 +490,9 @@ def retrieve_packets(
)
packet_track.sort()
try:
coordinate_string = ', '.join(f'{coordinate:.3f}°'
for coordinate in packet_track.coordinates[-1, :2])
coordinate_string = ', '.join(
f'{coordinate:.3f}°' for coordinate in packet_track.coordinates[-1, :2]
)
logger.info(
f'{callsign:8} - packet #{len(packet_track):<3} - ({coordinate_string}, {packet_track.coordinates[-1, 2]:9.2f}m)'
f'; packet time is {packet_time} ({humanize.naturaltime(current_time - packet_time)}, {packet_track.intervals[-1]:6.1f} s interval)'
Expand All @@ -508,18 +509,22 @@ def retrieve_packets(
/ numpy.timedelta64(1, 's')
)
try:
message = f'{callsign:8} - ' \
f'altitude: {packet_track.altitudes[-1]:6.1f} m' \
f'; avg. ascent rate: {numpy.mean(packet_track.ascent_rates[packet_track.ascent_rates > 0]):5.1f} m/s' \
f'; avg. descent rate: {numpy.mean(packet_track.ascent_rates[packet_track.ascent_rates < 0]):5.1f} m/s' \
f'; avg. ground speed: {numpy.mean(packet_track.ground_speeds):5.1f} m/s' \
f'; avg. packet interval: {numpy.mean(packet_track.intervals):6.1f} s'
message = (
f'{callsign:8} - '
f'altitude: {packet_track.altitudes[-1]:6.1f} m'
f'; avg. ascent rate: {numpy.mean(packet_track.ascent_rates[packet_track.ascent_rates > 0]):5.1f} m/s'
f'; avg. descent rate: {numpy.mean(packet_track.ascent_rates[packet_track.ascent_rates < 0]):5.1f} m/s'
f'; avg. ground speed: {numpy.mean(packet_track.ground_speeds):5.1f} m/s'
f'; avg. packet interval: {numpy.mean(packet_track.intervals):6.1f} s'
)

if packet_track.time_to_ground >= timedelta(seconds=0):
landing_time = packet_time + packet_track.time_to_ground
time_to_ground = current_time - landing_time
message += f'; estimated landing: {landing_time:%Y-%m-%d %H:%M:%S} ({humanize.naturaltime(time_to_ground)})' \
f'; max altitude: {packet_track.coordinates[:, 2].max():.2f} m'
message += (
f'; estimated landing: {landing_time:%Y-%m-%d %H:%M:%S} ({humanize.naturaltime(time_to_ground)})'
f'; max altitude: {packet_track.coordinates[:, 2].max():.2f} m'
)

logger.info(message)

Expand Down
9 changes: 9 additions & 0 deletions packetraven/connections/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from packetraven.connections.base import TimeIntervalError
from packetraven.connections.file import PacketGeoJSON, RawAPRSTextFile
from packetraven.connections.internet import (
APRSDatabaseTable,
APRSfi,
APRSis,
PacketDatabaseTable,
)
from packetraven.connections.serial import SerialTNC
9 changes: 9 additions & 0 deletions packetraven/base.py → packetraven/connections/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
from serial.tools import list_ports

from packetraven.packets import APRSPacket, LocationPacket
from packetraven.utilities import get_logger, repository_root

LOGGER = get_logger('connection')

CREDENTIALS_FILENAME = repository_root() / 'credentials.config'


class TimeIntervalError(Exception):
pass


class Connection(ABC):
Expand Down
164 changes: 164 additions & 0 deletions packetraven/connections/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from datetime import datetime
from os import PathLike
from pathlib import Path
from urllib.parse import urlparse

from dateutil.parser import parse as parse_date
import geojson
import requests

from packetraven.connections.base import (
APRSPacketSource,
LOGGER,
PacketSource,
TimeIntervalError,
)
from packetraven.packets import APRSPacket, LocationPacket


class RawAPRSTextFile(APRSPacketSource):
def __init__(self, filename: PathLike = None, callsigns: str = None):
"""
read APRS packets from a given text file where each line consists of the time sent (`YYYY-MM-DDTHH:MM:SS`) followed by
a colon `:` and then the raw APRS string
:param filename: path to text file
:param callsigns: list of callsigns to return from source
"""

if not urlparse(str(filename)).scheme in ['http', 'https', 'ftp', 'sftp']:
if not isinstance(filename, Path):
if isinstance(filename, str):
filename = filename.strip('"')
filename = Path(filename)
filename = str(filename)

super().__init__(filename, callsigns)
self.__last_access_time = None
self.__parsed_lines = []

@property
def packets(self) -> [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 = parse_date(packet_time)
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:
LOGGER.error(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 close(self):
pass

def __repr__(self):
return f'{self.__class__.__name__}({repr(self.location)}, {repr(self.callsigns)})'


class PacketGeoJSON(PacketSource):
def __init__(self, filename: PathLike = None):
"""
read location packets from a given GeoJSON file
:param filename: path to GeoJSON file
"""

if not urlparse(str(filename)).scheme in ['http', 'https', 'ftp', 'sftp']:
if not isinstance(filename, Path):
if isinstance(filename, str):
filename = filename.strip('"')
filename = Path(filename)
filename = str(filename)

super().__init__(filename)
self.__last_access_time = None

@property
def packets(self) -> [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 = parse_date(properties['time'])
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 close(self):
pass

def __repr__(self):
return f'{self.__class__.__name__}({repr(self.location)})'
Loading

0 comments on commit 09fa1a3

Please sign in to comment.