Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
zacharyburnett committed Sep 29, 2020
2 parents da44f6c + 9b81df2 commit a2293b0
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 58 deletions.
112 changes: 74 additions & 38 deletions packetraven/connections.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
"""
Read serial connection from radio and extract APRS packets.
__authors__ = []
"""

from abc import ABC, abstractmethod
from datetime import datetime
import logging
Expand All @@ -17,23 +11,30 @@
from shapely.geometry import Point

from client import CREDENTIALS_FILENAME
from packetraven.database import DatabaseTable
from packetraven.packets import APRSLocationPacket, LocationPacket
from packetraven.utilities import get_logger, read_configuration
from .database import DatabaseTable
from .packets import APRSLocationPacket, LocationPacket
from .utilities import get_logger, read_configuration

LOGGER = get_logger('packetraven.connection')


class PacketConnection(ABC):
location: str
def __init__(self, location: str):
"""
Create a new generic packet connection.
:param location: location of packets
"""

self.location = location

@property
@abstractmethod
def packets(self) -> [LocationPacket]:
"""
List the most recent packets available from this connection.
:return: list of APRS packets
:return: list of packets
"""

raise NotImplementedError
Expand All @@ -51,27 +52,57 @@ def close(self):
raise NotImplementedError


class APRSPacketRadio(PacketConnection):
def __init__(self, serial_port: str = None):
class APRSPacketConnection(PacketConnection):
def __init__(self, location: str, callsigns: [str]):
"""
Create a new generic APRS packet connection.
:param location:
:param callsigns: list of callsigns to return from source
"""

super().__init__(location)
self.callsigns = callsigns

@property
@abstractmethod
def packets(self) -> [APRSLocationPacket]:
"""
List the most recent packets available from this connection.
:return: list of APRS packets
"""

raise NotImplementedError


class APRSPacketRadio(APRSPacketConnection):
def __init__(self, serial_port: str = None, callsigns: [str] = None):
"""
Connect to radio over given serial port.
:param serial_port: port name
:param callsigns: list of callsigns to return from source
"""

if serial_port is None:
try:
self.location = next_available_port()
serial_port = next_available_port()
except ConnectionError:
raise ConnectionError('could not find radio over serial connection')
else:
self.location = serial_port.strip('"')
serial_port = serial_port.strip('"')

self.connection = Serial(self.location, baudrate=9600, timeout=1)
super().__init__(serial_port, callsigns)
self.connection = Serial(serial_port, baudrate=9600, timeout=1)

@property
def packets(self) -> [APRSLocationPacket]:
return [parse_packet(line, source=self) for line in self.connection.readlines()]
packets = [parse_packet(line, source=self) for line in self.connection.readlines()]
if self.callsigns is not None:
return [packet for packet in packets if packet.callsign in self.callsigns]
else:
return packets

def __enter__(self):
return self.connection
Expand All @@ -86,29 +117,32 @@ def __repr__(self):
return f'{self.__class__.__name__}("{self.location}")'


class APRSPacketTextFile(PacketConnection):
def __init__(self, filename: PathLike = None):
class APRSPacketTextFile(APRSPacketConnection):
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 (`%Y-%m-%d %H:%M:%S`) followed by
the raw APRS string
:param filename: path to text file
:param callsigns: list of callsigns to return from source
"""

if not isinstance(filename, Path):
if isinstance(filename, str):
filename = filename.strip('"')
filename = Path(filename)

self.location = filename

# open text file
self.connection = open(self.location)
super().__init__(filename, callsigns)
self.connection = open(filename)

@property
def packets(self) -> [APRSLocationPacket]:
return [parse_packet(line[25:].strip('\n'), datetime.strptime(line[:19], '%Y-%m-%d %H:%M:%S'), source=self)
for line in self.connection.readlines() if len(line) > 0]
packets = [parse_packet(line[25:].strip('\n'), datetime.strptime(line[:19], '%Y-%m-%d %H:%M:%S'), source=self)
for line in self.connection.readlines() if len(line) > 0]
if self.callsigns is not None:
return [packet for packet in packets if packet.callsign in self.callsigns]
else:
return packets

def __enter__(self):
return self.connection
Expand All @@ -123,19 +157,19 @@ def __repr__(self):
return f'{self.__class__.__name__}("{self.location}")'


class APRSfiConnection(PacketConnection):
location = 'https://api.aprs.fi/api/get'

class APRSfiConnection(APRSPacketConnection):
def __init__(self, callsigns: [str], api_key: str = None):
"""
connect to https://aprs.fi
:param callsigns: list of callsigns to watch
:param callsigns: list of callsigns to return from source
:param api_key: API key for aprs.fi
"""

url = 'https://api.aprs.fi/api/get'
if callsigns is None:
raise ConnectionError(f'queries to {self.location} require a list of callsigns')
raise ConnectionError(f'queries to {url} require a list of callsigns')
super().__init__(url, callsigns)

if api_key is None or api_key == '':
configuration = read_configuration(CREDENTIALS_FILENAME)
Expand All @@ -145,7 +179,6 @@ def __init__(self, callsigns: [str], api_key: str = None):
else:
raise ConnectionError(f'no APRS.fi API key specified')

self.callsigns = callsigns
self.api_key = api_key

if not self.connected:
Expand Down Expand Up @@ -213,8 +246,8 @@ def __init__(self, hostname: str, database: str, table: str, **kwargs):
if 'fields' not in kwargs:
kwargs['fields'] = {}
kwargs['fields'] = {**self.__default_fields, **kwargs['fields']}
super().__init__(hostname, database, table, primary_key='time', **kwargs)
self.location = f'{self.hostname}:{self.port}/{self.database}/{self.table}'
DatabaseTable.__init__(self, hostname, database, table, primary_key='time', **kwargs)
PacketConnection.__init__(self, f'{self.hostname}:{self.port}/{self.database}/{self.table}')

@property
def packets(self) -> [LocationPacket]:
Expand Down Expand Up @@ -247,7 +280,7 @@ def close(self):
self.tunnel.stop()


class APRSPacketDatabaseTable(PacketDatabaseTable):
class APRSPacketDatabaseTable(PacketDatabaseTable, APRSPacketConnection):
__aprs_fields = {
'from' : str,
'to' : str,
Expand All @@ -265,8 +298,8 @@ def __init__(self, hostname: str, database: str, table: str, callsigns: [str] =
if 'fields' not in kwargs:
kwargs['fields'] = self.__aprs_fields
kwargs['fields'] = {f'packet_{field}': field_type for field, field_type in kwargs['fields'].items()}
super().__init__(hostname, database, table, **kwargs)
self.callsigns = callsigns
PacketDatabaseTable.__init__(self, hostname, database, table, **kwargs)
APRSPacketConnection.__init__(self, f'{self.hostname}:{self.port}/{self.database}/{self.table}', callsigns)

@property
def packets(self) -> [APRSLocationPacket]:
Expand All @@ -292,11 +325,14 @@ def __setitem__(self, time: datetime, packet: APRSLocationPacket):
'point' : Point(*packet.coordinates),
**{f'packet_{field}': value for field, value in packet.attributes.items()}
}
super(super()).__setitem__(time, record)
super().__setitem__(time, record)

@property
def records(self) -> [{str: Any}]:
return self.records_where({'callsign': self.callsigns} if self.callsigns is not None else None)
if self.callsigns is not None:
return self.records_where({'callsign': self.callsigns})
else:
return self.records_where(None)

def insert(self, packets: [APRSLocationPacket]):
records = [{
Expand Down
2 changes: 1 addition & 1 deletion packetraven/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from shapely.geometry.base import BaseGeometry
from sshtunnel import SSHTunnelForwarder

from packetraven.utilities import get_logger
from .utilities import get_logger

LOGGER = get_logger('packetraven.connection')

Expand Down
9 changes: 6 additions & 3 deletions packetraven/packets.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
from datetime import datetime, timedelta
import math
from typing import Any, Union
from typing import Any, TYPE_CHECKING, Union

import numpy
from pyproj import CRS, Geod, Transformer

from packetraven.parsing import parse_raw_aprs
from .parsing import parse_raw_aprs

if TYPE_CHECKING:
from .connections import PacketConnection

DEFAULT_CRS = CRS.from_epsg(4326)


class LocationPacket:
""" location packet encoding (x, y, z) and time """

def __init__(self, time: datetime, x: float, y: float, z: float = None, crs: CRS = None, source=None,
def __init__(self, time: datetime, x: float, y: float, z: float = None, crs: CRS = None, source: 'PacketConnection' = None,
**kwargs):
self.time = time
self.coordinates = numpy.array((x, y, z if z is not None else 0))
Expand Down
6 changes: 0 additions & 6 deletions packetraven/parsing.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
"""
Parse a APRS packets from raw strings.
__authors__ = ['Zachary Burnett', 'Nick Rossomando']
"""

from typing import Union

import aprslib
Expand Down
7 changes: 0 additions & 7 deletions packetraven/structures.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
"""
Doubly linked list for generic value types.
__authors__ = ['Zachary Burnett']
"""


class DoublyLinkedList:
"""
A linked list is a series of node objects, each with a link (object reference) to the next node in the series.
Expand Down
4 changes: 2 additions & 2 deletions packetraven/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import numpy
from pyproj import CRS, Geod

from packetraven.packets import APRSLocationPacket, DEFAULT_CRS, LocationPacket
from packetraven.structures import DoublyLinkedList
from .packets import APRSLocationPacket, DEFAULT_CRS, LocationPacket
from .structures import DoublyLinkedList


class LocationPacketTrack:
Expand Down
2 changes: 1 addition & 1 deletion packetraven/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from geojson import Point
from shapely.geometry import LineString

from packetraven.tracks import APRSTrack
from .tracks import APRSTrack

KML_STANDARD = '{http://www.opengis.net/kml/2.2}'

Expand Down

0 comments on commit a2293b0

Please sign in to comment.