Skip to content

Commit

Permalink
cleaned up front-end and updated README with CLI usage
Browse files Browse the repository at this point in the history
  • Loading branch information
zacharyburnett committed Sep 21, 2020
1 parent d3c29f0 commit e847f79
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 155 deletions.
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ to start the client, run the following:
```bash
packetraven
```
for usage, do
```bash
packetraven -h
```
usage: packetraven [-h] [-k APIKEY] [-c CALLSIGNS] [-s] [-p PORT] [-l LOG] [-o OUTPUT] [-t INTERVAL] [-g]

optional arguments:
-h, --help show this help message and exit
-k APIKEY, --apikey APIKEY
API key from https://aprs.fi/page/api
-c CALLSIGNS, --callsigns CALLSIGNS
comma-separated list of callsigns to track
-s, --skipserial skip attempting to connect to APRS packet radio
-p PORT, --port PORT name of serial port connected to APRS packet radio
-l LOG, --log LOG path to log file to save log messages
-o OUTPUT, --output OUTPUT
path to output file to save packets
-t INTERVAL, --interval INTERVAL
seconds between each main loop
-g, --gui start the graphical interface

there is also a graphical interface:
```bash
packetraven_gui
```

#### Python API:
Expand All @@ -47,6 +57,22 @@ radio_packets = radio.packets

print(radio_packets)
```
or connect to a PostGreSQL database running PostGIS:
```python
from packetraven import APRSPacketDatabaseTable

hostname = 'bpp.umd.edu:5432'
database = 'bpp'
table = 'packets'

username = 'username'
password = '1234'

table = APRSPacketDatabaseTable(hostname, database, table, username=username, password=password)
table_packets = table.packets

print(table_packets)
```

#### Features:
###### current:
Expand Down
7 changes: 7 additions & 0 deletions client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pathlib import Path

from packetraven.utilities import get_logger

LOGGER = get_logger('packetraven')
DEFAULT_INTERVAL_SECONDS = 5
DESKTOP_PATH = Path('~').expanduser() / 'Desktop'
125 changes: 125 additions & 0 deletions client/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import argparse
from datetime import datetime
import os
from pathlib import Path
import time

from packetraven import DEFAULT_CALLSIGNS
from packetraven.connections import APRS_fi, PacketRadio, PacketTextFile
from packetraven.tracks import APRSTrack
from packetraven.utilities import get_logger
from packetraven.writer import write_aprs_packet_tracks
from . import DEFAULT_INTERVAL_SECONDS, LOGGER
from .gui import PacketRavenGUI


def main(args: [str] = None):
parser = argparse.ArgumentParser()
parser.add_argument('-k', '--apikey', help='API key from https://aprs.fi/page/api')
parser.add_argument('-c', '--callsigns', help='comma-separated list of callsigns to track')
parser.add_argument('-s', '--skipserial', action='store_true', help='skip attempting to connect to APRS packet radio')
parser.add_argument('-p', '--port', help='name of serial port connected to APRS packet radio')
parser.add_argument('-l', '--log', help='path to log file to save log messages')
parser.add_argument('-o', '--output', help='path to output file to save packets')
parser.add_argument('-t', '--interval', type=float, help='seconds between each main loop')
parser.add_argument('-g', '--gui', action='store_true', help='start the graphical interface')
args = parser.parse_args(args)

aprs_fi_api_key = args.apikey
callsigns = args.callsigns.strip('"').split(',') if args.callsigns is not None else DEFAULT_CALLSIGNS

skip_serial = args.skipserial

interval_seconds = args.interval if args.interval is not None else DEFAULT_INTERVAL_SECONDS

if args.log is not None:
log_filename = Path(args.log).expanduser()
if log_filename.is_dir():
if not log_filename.exists():
os.makedirs(log_filename, exist_ok=True)
log_filename = log_filename / f'{datetime.now():%Y%m%dT%H%M%S}_packetraven_log.txt'
get_logger(LOGGER.name, log_filename)
else:
log_filename = None

if args.output is not None:
output_filename = Path(args.output).expanduser()
output_directory = output_filename.parent
if not output_directory.exists():
os.makedirs(output_directory, exist_ok=True)
else:
output_filename = None

serial_port = args.port

if args.gui:
PacketRavenGUI(aprs_fi_api_key, callsigns, skip_serial, serial_port, log_filename, output_filename, interval_seconds)
else:
connections = []
if not skip_serial:
if serial_port is not None and 'txt' in serial_port:
try:
text_file = PacketTextFile(serial_port)
LOGGER.info(f'reading file {text_file.location}')
connections.append(text_file)
except ConnectionError as error:
LOGGER.warning(f'{error.__class__.__name__} - {error}')
else:
try:
radio = PacketRadio(serial_port)
LOGGER.info(f'opened port {radio.location}')
serial_port = radio.location
connections.append(radio)
except ConnectionError as error:
LOGGER.warning(f'{error.__class__.__name__} - {error}')

try:
aprs_api = APRS_fi(callsigns, api_key=aprs_fi_api_key)
LOGGER.info(f'connected to {aprs_api.location} with selected callsigns: {", ".join(callsigns)}')
connections.append(aprs_api)
except ConnectionError as error:
LOGGER.warning(f'{error.__class__.__name__} - {error}')

packet_tracks = {}
while len(connections) > 0:
LOGGER.debug(f'receiving packets from {len(connections)} source(s)')

parsed_packets = []
for aprs_connection in connections:
parsed_packets.extend(aprs_connection.packets)

LOGGER.debug(f'received {len(parsed_packets)} packets')

if len(parsed_packets) > 0:
for parsed_packet in parsed_packets:
callsign = parsed_packet['callsign']

if callsign in packet_tracks:
if parsed_packet not in packet_tracks[callsign]:
packet_tracks[callsign].append(parsed_packet)
else:
LOGGER.debug(f'{callsign:8} - received duplicate packet: {parsed_packet}')
continue
else:
packet_tracks[callsign] = APRSTrack(callsign, [parsed_packet])
LOGGER.debug(f'{callsign:8} - started tracking')

LOGGER.info(f'{callsign:8} - received new packet: {parsed_packet}')

if 'longitude' in parsed_packet and 'latitude' in parsed_packet:
ascent_rate = packet_tracks[callsign].ascent_rate[-1]
ground_speed = packet_tracks[callsign].ground_speed[-1]
seconds_to_impact = packet_tracks[callsign].seconds_to_impact
LOGGER.info(f'{callsign:8} - Ascent rate (m/s): {ascent_rate}')
LOGGER.info(f'{callsign:8} - Ground speed (m/s): {ground_speed}')
if seconds_to_impact >= 0:
LOGGER.info(f'{callsign:8} - Estimated time until landing (s): {seconds_to_impact}')

if output_filename is not None:
write_aprs_packet_tracks(packet_tracks.values(), output_filename)

time.sleep(interval_seconds)


if __name__ == '__main__':
main()
55 changes: 28 additions & 27 deletions client/packetraven_gui.py → client/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@
from packetraven.tracks import APRSTrack
from packetraven.utilities import get_logger
from packetraven.writer import write_aprs_packet_tracks

LOGGER = get_logger('packetraven_gui')

DEFAULT_INTERVAL_SECONDS = 5
DESKTOP_PATH = Path('~').expanduser() / 'Desktop'
from . import DEFAULT_INTERVAL_SECONDS, DESKTOP_PATH, LOGGER


class PacketRavenGUI:
def __init__(self):
def __init__(self, aprs_fi_api_key: str = None, callsigns: [str] = None, skip_serial: bool = False, serial_port: str = None,
log_filename: str = None, output_filename: str = None, interval_seconds: int = None):
self.aprs_fi_api_key = aprs_fi_api_key
self.callsigns = callsigns if callsigns is not None else DEFAULT_CALLSIGNS
self.skip_serial = skip_serial
self.serial_port = serial_port
self.log_filename = log_filename if log_filename is not None else DESKTOP_PATH / f'packetraven_log_{datetime.now():%Y%m%dT%H%M%S}.txt'
self.output_filename = output_filename if output_filename is not None else DESKTOP_PATH / f'packetraven_output_' \
f'{datetime.now():%Y%m%dT%H%M%S}.geojson'
self.interval_seconds = interval_seconds if interval_seconds is not None else DEFAULT_INTERVAL_SECONDS

self.main_window = tkinter.Tk()
self.main_window.title('packetraven main')

Expand All @@ -42,12 +48,12 @@ def __init__(self):
self.__add_entry_box(self.frames['top'], 'port')

self.__add_entry_box(self.frames['top'], title='log_file', width=45)
self.elements['log_file'].insert(0, DESKTOP_PATH / f'packetraven_log_{datetime.now():%Y%m%dT%H%M%S}.txt')
self.elements['log_file'].insert(0, self.log_filename)
log_file_button = tkinter.Button(self.frames['top'], text='...', command=self.__select_log_file)
log_file_button.grid(row=self.last_row, column=2)

self.__add_entry_box(self.frames['top'], title='output_file', width=45)
self.elements['output_file'].insert(0, DESKTOP_PATH / f'packetraven_output_{datetime.now():%Y%m%dT%H%M%S}.geojson')
self.elements['output_file'].insert(0, self.output_filename)
output_file_button = tkinter.Button(self.frames['top'], text='...', command=self.__select_output_file)
output_file_button.grid(row=self.last_row, column=2)

Expand All @@ -66,11 +72,12 @@ def __init__(self):
for element in self.frames['bottom'].winfo_children():
element.configure(state=tkinter.DISABLED)

try:
self.serial_port = next_available_port()
self.replace_text(self.elements['port'], self.serial_port)
except OSError:
self.serial_port = None
if self.serial_port is None:
try:
self.serial_port = next_available_port()
self.replace_text(self.elements['port'], self.serial_port)
except OSError:
LOGGER.debug(f'could not automatically determine radio serial port')

self.main_window.mainloop()

Expand Down Expand Up @@ -157,27 +164,29 @@ def toggle(self):
LOGGER.exception(f'{error.__class__.__name__} - {error}')
self.serial_port = None

if self.serial_port is not None:
if not self.skip_serial and self.serial_port is not None:
if 'txt' in self.serial_port:
try:
text_file = PacketTextFile(self.serial_port)
LOGGER.info(f'reading file {text_file.location}')
self.connections.append(text_file)
except Exception as error:
LOGGER.exception(f'{error.__class__.__name__} - {error}')
else:
try:
radio = PacketRadio(self.serial_port)
LOGGER.info(f'opened port {radio.location}')
self.serial_port = radio.location
LOGGER.info(f'opened port {self.serial_port}')
self.connections.append(radio)
except Exception as error:
LOGGER.exception(f'{error.__class__.__name__} - {error}')

aprs_fi_api_key = simpledialog.askstring('APRS.fi API Key', 'enter API key for https://aprs.fi', parent=self.main_window)
if self.aprs_fi_api_key is None:
self.aprs_fi_api_key = simpledialog.askstring('APRS.fi API Key', 'enter API key for https://aprs.fi', parent=self.main_window)

try:
aprs_api = APRS_fi(DEFAULT_CALLSIGNS, api_key=aprs_fi_api_key)
LOGGER.info(f'established connection to API')
aprs_api = APRS_fi(self.callsigns, api_key=self.aprs_fi_api_key)
LOGGER.info(f'established connection to {aprs_api.location}')
self.connections.append(aprs_api)
except Exception as error:
LOGGER.exception(f'{error.__class__.__name__} - {error}')
Expand Down Expand Up @@ -246,7 +255,7 @@ def run(self):
write_aprs_packet_tracks(self.packet_tracks.values(), output_filename)

if self.active:
self.main_window.after(DEFAULT_INTERVAL_SECONDS * 1000, self.run)
self.main_window.after(self.interval_seconds * 1000, self.run)

@staticmethod
def replace_text(element, value):
Expand All @@ -257,11 +266,3 @@ def replace_text(element, value):

element.delete(start_index, tkinter.END)
element.insert(start_index, value)


def main():
PacketRavenGUI()


if __name__ == '__main__':
main()
Loading

0 comments on commit e847f79

Please sign in to comment.