mirror of
https://github.com/craigerl/aprsd.git
synced 2026-08-18 17:44:02 -04:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 578062648b | |||
| ecf30d3397 | |||
| 882e90767d | |||
| 14274c93b5 | |||
| 14c0a699cb | |||
| c12c42b876 | |||
| 765e02f5b3 | |||
| 8cdbf18bef | |||
| a65262d2ff | |||
| 9951b12e2d | |||
| 3e9bf2422a | |||
| 5e9f92dfa6 | |||
| 5314856101 | |||
| 758007ea3f |
+691
-668
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
WORKDIR?=.
|
||||
VENVDIR ?= $(WORKDIR)/.aprsd-venv
|
||||
VENVDIR ?= $(WORKDIR)/.venv
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
@@ -24,7 +24,7 @@ run: venv ## Create a virtual environment for running aprsd commands
|
||||
|
||||
changelog: dev
|
||||
npm i -g auto-changelog
|
||||
auto-changelog -l false -o ChangeLog.md
|
||||
auto-changelog -l false --sort-commits date -o ChangeLog.md
|
||||
|
||||
docs: changelog
|
||||
m2r --overwrite ChangeLog.md
|
||||
|
||||
@@ -33,7 +33,11 @@ class Aprsdis(aprslib.IS):
|
||||
|
||||
def stop(self):
|
||||
self.thread_stop = True
|
||||
LOG.info("Shutdown Aprsdis client.")
|
||||
LOG.warning("Shutdown Aprsdis client.")
|
||||
|
||||
def close(self):
|
||||
LOG.warning("Closing Aprsdis client.")
|
||||
super().close()
|
||||
|
||||
@wrapt.synchronized(lock)
|
||||
def send(self, packet: core.Packet):
|
||||
|
||||
@@ -42,7 +42,8 @@ def signal_handler(sig, frame):
|
||||
),
|
||||
)
|
||||
time.sleep(5)
|
||||
LOG.info(collector.Collector().collect())
|
||||
# Last save to disk
|
||||
collector.Collector().collect()
|
||||
|
||||
|
||||
class APRSDListenThread(rx.APRSDRXThread):
|
||||
|
||||
@@ -12,7 +12,9 @@ from aprsd import cli_helper, packets
|
||||
from aprsd import conf # noqa : F401
|
||||
from aprsd.client import client_factory
|
||||
from aprsd.main import cli
|
||||
import aprsd.packets # noqa : F401
|
||||
from aprsd.packets import collector
|
||||
from aprsd.packets import log as packet_log
|
||||
from aprsd.threads import tx
|
||||
|
||||
|
||||
@@ -94,10 +96,6 @@ def send_message(
|
||||
else:
|
||||
LOG.info(f"L'{aprs_login}' To'{tocallsign}' C'{command}'")
|
||||
|
||||
packets.PacketList()
|
||||
packets.WatchList()
|
||||
packets.SeenList()
|
||||
|
||||
got_ack = False
|
||||
got_response = False
|
||||
|
||||
@@ -106,7 +104,7 @@ def send_message(
|
||||
cl = client_factory.create()
|
||||
packet = cl.decode_packet(packet)
|
||||
collector.PacketCollector().rx(packet)
|
||||
packet.log("RX")
|
||||
packet_log.log(packet, tx=False)
|
||||
# LOG.debug("Got packet back {}".format(packet))
|
||||
if isinstance(packet, packets.AckPacket):
|
||||
got_ack = True
|
||||
|
||||
+15
-20
@@ -8,7 +8,7 @@ from oslo_config import cfg
|
||||
import aprsd
|
||||
from aprsd import cli_helper
|
||||
from aprsd import main as aprsd_main
|
||||
from aprsd import packets, plugin, threads, utils
|
||||
from aprsd import plugin, threads, utils
|
||||
from aprsd.client import client_factory
|
||||
from aprsd.main import cli
|
||||
from aprsd.packets import collector as packet_collector
|
||||
@@ -87,29 +87,24 @@ def server(ctx, flush):
|
||||
LOG.error("APRS client is not properly configured in config file.")
|
||||
sys.exit(-1)
|
||||
|
||||
# Now load the msgTrack from disk if any
|
||||
packets.PacketList()
|
||||
if flush:
|
||||
LOG.debug("Deleting saved MsgTrack.")
|
||||
packets.PacketTrack().flush()
|
||||
packets.WatchList().flush()
|
||||
packets.SeenList().flush()
|
||||
packets.PacketList().flush()
|
||||
else:
|
||||
# Try and load saved MsgTrack list
|
||||
LOG.debug("Loading saved MsgTrack object.")
|
||||
packets.PacketTrack().load()
|
||||
packets.WatchList().load()
|
||||
packets.SeenList().load()
|
||||
packets.PacketList().load()
|
||||
|
||||
keepalive = keep_alive.KeepAliveThread()
|
||||
keepalive.start()
|
||||
|
||||
if not CONF.enable_seen_list:
|
||||
# just deregister the class from the packet collector
|
||||
packet_collector.PacketCollector().unregister(seen_list.SeenList)
|
||||
|
||||
# Now load the msgTrack from disk if any
|
||||
if flush:
|
||||
LOG.debug("Flushing All packet tracking objects.")
|
||||
packet_collector.PacketCollector().flush()
|
||||
else:
|
||||
# Try and load saved MsgTrack list
|
||||
LOG.debug("Loading saved packet tracking data.")
|
||||
packet_collector.PacketCollector().load()
|
||||
|
||||
# Now start all the main processing threads.
|
||||
|
||||
keepalive = keep_alive.KeepAliveThread()
|
||||
keepalive.start()
|
||||
|
||||
stats_store_thread = stats_thread.APRSDStatsStoreThread()
|
||||
stats_store_thread.start()
|
||||
|
||||
|
||||
@@ -62,9 +62,7 @@ def signal_handler(sig, frame):
|
||||
threads.APRSDThreadList().stop_all()
|
||||
if "subprocess" not in str(frame):
|
||||
time.sleep(1.5)
|
||||
# packets.WatchList().save()
|
||||
# packets.SeenList().save()
|
||||
LOG.info(stats.stats_collector.collect())
|
||||
stats.stats_collector.collect()
|
||||
LOG.info("Telling flask to bail.")
|
||||
signal.signal(signal.SIGTERM, sys.exit(0))
|
||||
|
||||
@@ -647,11 +645,6 @@ def webchat(ctx, flush, port):
|
||||
LOG.error("APRS client is not properly configured in config file.")
|
||||
sys.exit(-1)
|
||||
|
||||
packets.PacketList()
|
||||
packets.PacketTrack()
|
||||
packets.WatchList()
|
||||
packets.SeenList()
|
||||
|
||||
keepalive = keep_alive.KeepAliveThread()
|
||||
LOG.info("Start KeepAliveThread")
|
||||
keepalive.start()
|
||||
|
||||
@@ -205,7 +205,6 @@ enabled_plugins_opts = [
|
||||
"aprsd.plugins.fortune.FortunePlugin",
|
||||
"aprsd.plugins.location.LocationPlugin",
|
||||
"aprsd.plugins.ping.PingPlugin",
|
||||
"aprsd.plugins.query.QueryPlugin",
|
||||
"aprsd.plugins.time.TimePlugin",
|
||||
"aprsd.plugins.weather.OWMWeatherPlugin",
|
||||
"aprsd.plugins.version.VersionPlugin",
|
||||
|
||||
@@ -31,13 +31,6 @@ aprsfi_opts = [
|
||||
),
|
||||
]
|
||||
|
||||
query_plugin_opts = [
|
||||
cfg.StrOpt(
|
||||
"callsign",
|
||||
help="The Ham callsign to allow access to the query plugin from RF.",
|
||||
),
|
||||
]
|
||||
|
||||
owm_wx_opts = [
|
||||
cfg.StrOpt(
|
||||
"apiKey",
|
||||
@@ -172,7 +165,6 @@ def register_opts(config):
|
||||
config.register_group(aprsfi_group)
|
||||
config.register_opts(aprsfi_opts, group=aprsfi_group)
|
||||
config.register_group(query_group)
|
||||
config.register_opts(query_plugin_opts, group=query_group)
|
||||
config.register_group(owm_wx_group)
|
||||
config.register_opts(owm_wx_opts, group=owm_wx_group)
|
||||
config.register_group(avwx_group)
|
||||
@@ -184,7 +176,6 @@ def register_opts(config):
|
||||
def list_opts():
|
||||
return {
|
||||
aprsfi_group.name: aprsfi_opts,
|
||||
query_group.name: query_plugin_opts,
|
||||
owm_wx_group.name: owm_wx_opts,
|
||||
avwx_group.name: avwx_opts,
|
||||
location_group.name: location_opts,
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ def signal_handler(sig, frame):
|
||||
packets.WatchList().save()
|
||||
packets.SeenList().save()
|
||||
packets.PacketList().save()
|
||||
LOG.info(collector.Collector().collect())
|
||||
collector.Collector().collect()
|
||||
# signal.signal(signal.SIGTERM, sys.exit(0))
|
||||
# sys.exit(0)
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# What to return from a plugin if we have processed the message
|
||||
# and it's ok, but don't send a usage string back
|
||||
|
||||
# REMOVE THIS FILE
|
||||
@@ -1,3 +1,4 @@
|
||||
from aprsd.packets import collector
|
||||
from aprsd.packets.core import ( # noqa: F401
|
||||
AckPacket, BeaconPacket, BulletinPacket, GPSPacket, MessagePacket,
|
||||
MicEPacket, ObjectPacket, Packet, RejectPacket, StatusPacket,
|
||||
@@ -9,4 +10,11 @@ from aprsd.packets.tracker import PacketTrack # noqa: F401
|
||||
from aprsd.packets.watch_list import WatchList # noqa: F401
|
||||
|
||||
|
||||
# Register all the packet tracking objects.
|
||||
collector.PacketCollector().register(PacketList)
|
||||
collector.PacketCollector().register(SeenList)
|
||||
collector.PacketCollector().register(PacketTrack)
|
||||
collector.PacketCollector().register(WatchList)
|
||||
|
||||
|
||||
NULL_MESSAGE = -1
|
||||
|
||||
+38
-15
@@ -20,6 +20,14 @@ class PacketMonitor(Protocol):
|
||||
"""When we send a packet out the network."""
|
||||
...
|
||||
|
||||
def flush(self) -> None:
|
||||
"""Flush out any data."""
|
||||
...
|
||||
|
||||
def load(self) -> None:
|
||||
"""Load any data."""
|
||||
...
|
||||
|
||||
|
||||
@singleton
|
||||
class PacketCollector:
|
||||
@@ -27,30 +35,45 @@ class PacketCollector:
|
||||
self.monitors: list[Callable] = []
|
||||
|
||||
def register(self, monitor: Callable) -> None:
|
||||
if not isinstance(monitor, PacketMonitor):
|
||||
raise TypeError(f"Monitor {monitor} is not a PacketMonitor")
|
||||
self.monitors.append(monitor)
|
||||
|
||||
def unregister(self, monitor: Callable) -> None:
|
||||
if not isinstance(monitor, PacketMonitor):
|
||||
raise TypeError(f"Monitor {monitor} is not a PacketMonitor")
|
||||
self.monitors.remove(monitor)
|
||||
|
||||
def rx(self, packet: type[core.Packet]) -> None:
|
||||
for name in self.monitors:
|
||||
cls = name()
|
||||
if isinstance(cls, PacketMonitor):
|
||||
try:
|
||||
cls.rx(packet)
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in monitor {name} (rx): {e}")
|
||||
|
||||
else:
|
||||
raise TypeError(f"Monitor {name} is not a PacketMonitor")
|
||||
try:
|
||||
cls.rx(packet)
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in monitor {name} (rx): {e}")
|
||||
|
||||
def tx(self, packet: type[core.Packet]) -> None:
|
||||
for name in self.monitors:
|
||||
cls = name()
|
||||
if isinstance(cls, PacketMonitor):
|
||||
try:
|
||||
cls.tx(packet)
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in monitor {name} (tx): {e}")
|
||||
else:
|
||||
raise TypeError(f"Monitor {name} is not a PacketMonitor")
|
||||
try:
|
||||
cls.tx(packet)
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in monitor {name} (tx): {e}")
|
||||
|
||||
def flush(self):
|
||||
"""Call flush on the objects. This is used to flush out any data."""
|
||||
for name in self.monitors:
|
||||
cls = name()
|
||||
try:
|
||||
cls.flush()
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in monitor {name} (flush): {e}")
|
||||
|
||||
def load(self):
|
||||
"""Call load on the objects. This is used to load any data."""
|
||||
for name in self.monitors:
|
||||
cls = name()
|
||||
try:
|
||||
cls.load()
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in monitor {name} (load): {e}")
|
||||
|
||||
+25
-7
@@ -1,10 +1,12 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from geopy.distance import geodesic
|
||||
from loguru import logger
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd.packets.core import AckPacket, RejectPacket
|
||||
from aprsd import utils
|
||||
from aprsd.packets.core import AckPacket, GPSPacket, RejectPacket
|
||||
|
||||
|
||||
LOG = logging.getLogger()
|
||||
@@ -16,6 +18,8 @@ TO_COLOR = "fg #D033FF"
|
||||
TX_COLOR = "red"
|
||||
RX_COLOR = "green"
|
||||
PACKET_COLOR = "cyan"
|
||||
DISTANCE_COLOR = "fg #FF5733"
|
||||
DEGREES_COLOR = "fg #FFA900"
|
||||
|
||||
|
||||
def log_multiline(packet, tx: Optional[bool] = False, header: Optional[bool] = True) -> None:
|
||||
@@ -97,19 +101,19 @@ def log(packet, tx: Optional[bool] = False, header: Optional[bool] = True) -> No
|
||||
if header:
|
||||
if tx:
|
||||
via_color = "red"
|
||||
arrow = f"<{via_color}>-></{via_color}>"
|
||||
arrow = f"<{via_color}>\u2192</{via_color}>"
|
||||
logit.append(
|
||||
f"<red>TX {arrow}</red> "
|
||||
f"<red>TX\u2191</red> "
|
||||
f"<cyan>{name}</cyan>"
|
||||
f":{packet.msgNo}"
|
||||
f" ({packet.send_count + 1} of {pkt_max_send_count})",
|
||||
)
|
||||
else:
|
||||
via_color = "fg #828282"
|
||||
arrow = f"<{via_color}>-></{via_color}>"
|
||||
left_arrow = f"<{via_color}><-</{via_color}>"
|
||||
via_color = "fg #1AA730"
|
||||
arrow = f"<{via_color}>\u2192</{via_color}>"
|
||||
f"<{via_color}><-</{via_color}>"
|
||||
logit.append(
|
||||
f"<fg #1AA730>RX</fg #1AA730> {left_arrow} "
|
||||
f"<fg #1AA730>RX\u2193</fg #1AA730> "
|
||||
f"<cyan>{name}</cyan>"
|
||||
f":{packet.msgNo}",
|
||||
)
|
||||
@@ -139,5 +143,19 @@ def log(packet, tx: Optional[bool] = False, header: Optional[bool] = True) -> No
|
||||
msg = msg.replace("<", "\\<")
|
||||
logit.append(f"<light-yellow><b>{msg}</b></light-yellow>")
|
||||
|
||||
# is there distance information?
|
||||
if isinstance(packet, GPSPacket) and CONF.latitude and CONF.longitude:
|
||||
my_coords = (CONF.latitude, CONF.longitude)
|
||||
packet_coords = (packet.latitude, packet.longitude)
|
||||
try:
|
||||
bearing = utils.calculate_initial_compass_bearing(my_coords, packet_coords)
|
||||
except Exception as e:
|
||||
LOG.error(f"Failed to calculate bearing: {e}")
|
||||
bearing = 0
|
||||
logit.append(
|
||||
f" : <{DEGREES_COLOR}>{utils.degrees_to_cardinal(bearing, full_string=True)}</{DEGREES_COLOR}>"
|
||||
f"<{DISTANCE_COLOR}>@{geodesic(my_coords, packet_coords).miles:.2f}miles</{DISTANCE_COLOR}>",
|
||||
)
|
||||
|
||||
LOGU.opt(colors=True).info(" ".join(logit))
|
||||
log_multiline(packet, tx, header)
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd.packets import collector, core
|
||||
from aprsd.packets import core
|
||||
from aprsd.utils import objectstore
|
||||
|
||||
|
||||
@@ -108,9 +108,3 @@ class PacketList(objectstore.ObjectStoreMixin):
|
||||
"packets": pkts,
|
||||
}
|
||||
return stats
|
||||
|
||||
|
||||
# Now register the PacketList with the collector
|
||||
# every packet we RX and TX goes through the collector
|
||||
# for processing for whatever reason is needed.
|
||||
collector.PacketCollector().register(PacketList)
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd.packets import collector, core
|
||||
from aprsd.packets import core
|
||||
from aprsd.utils import objectstore
|
||||
|
||||
|
||||
@@ -47,8 +47,3 @@ class SeenList(objectstore.ObjectStoreMixin):
|
||||
|
||||
def tx(self, packet: type[core.Packet]):
|
||||
"""We don't care about TX packets."""
|
||||
|
||||
|
||||
# Register with the packet collector so we can process the packet
|
||||
# when we get it off the client (network)
|
||||
collector.PacketCollector().register(SeenList)
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd.packets import collector, core
|
||||
from aprsd.packets import core
|
||||
from aprsd.utils import objectstore
|
||||
|
||||
|
||||
@@ -101,9 +101,3 @@ class PacketTrack(objectstore.ObjectStoreMixin):
|
||||
del self.data[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
# Now register the PacketList with the collector
|
||||
# every packet we RX and TX goes through the collector
|
||||
# for processing for whatever reason is needed.
|
||||
collector.PacketCollector().register(PacketTrack)
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd import utils
|
||||
from aprsd.packets import collector, core
|
||||
from aprsd.packets import core
|
||||
from aprsd.utils import objectstore
|
||||
|
||||
|
||||
@@ -117,6 +117,3 @@ class WatchList(objectstore.ObjectStoreMixin):
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
collector.PacketCollector().register(WatchList)
|
||||
|
||||
@@ -25,7 +25,6 @@ CORE_MESSAGE_PLUGINS = [
|
||||
"aprsd.plugins.fortune.FortunePlugin",
|
||||
"aprsd.plugins.location.LocationPlugin",
|
||||
"aprsd.plugins.ping.PingPlugin",
|
||||
"aprsd.plugins.query.QueryPlugin",
|
||||
"aprsd.plugins.time.TimePlugin",
|
||||
"aprsd.plugins.weather.USWeatherPlugin",
|
||||
"aprsd.plugins.version.VersionPlugin",
|
||||
|
||||
@@ -25,14 +25,13 @@ class Collector:
|
||||
stats = {}
|
||||
for name in self.producers:
|
||||
cls = name()
|
||||
if isinstance(cls, StatsProducer):
|
||||
try:
|
||||
stats[cls.__class__.__name__] = cls.stats(serializable=serializable).copy()
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in producer {name} (stats): {e}")
|
||||
else:
|
||||
raise TypeError(f"{cls} is not an instance of StatsProducer")
|
||||
try:
|
||||
stats[cls.__class__.__name__] = cls.stats(serializable=serializable).copy()
|
||||
except Exception as e:
|
||||
LOG.error(f"Error in producer {name} (stats): {e}")
|
||||
return stats
|
||||
|
||||
def register_producer(self, producer_name: Callable):
|
||||
if not isinstance(producer_name, StatsProducer):
|
||||
raise TypeError(f"Producer {producer_name} is not a StatsProducer")
|
||||
self.producers.append(producer_name)
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
import time
|
||||
import tracemalloc
|
||||
|
||||
from loguru import logger
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd import packets, utils
|
||||
@@ -14,6 +15,7 @@ from aprsd.threads import APRSDThread, APRSDThreadList
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger("APRSD")
|
||||
LOGU = logger
|
||||
|
||||
|
||||
class KeepAliveThread(APRSDThread):
|
||||
@@ -87,7 +89,12 @@ class KeepAliveThread(APRSDThread):
|
||||
key = thread["name"]
|
||||
if not alive:
|
||||
LOG.error(f"Thread {thread}")
|
||||
LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}")
|
||||
|
||||
thread_hex = f"fg {utils.hex_from_name(key)}"
|
||||
t_name = f"<{thread_hex}>{key:<15}</{thread_hex}>"
|
||||
thread_msg = f"{t_name} Alive? {str(alive): <5} {str(age): <20}"
|
||||
LOGU.opt(colors=True).info(thread_msg)
|
||||
# LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}")
|
||||
|
||||
# check the APRS connection
|
||||
cl = client_factory.create()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import errno
|
||||
import functools
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@@ -82,6 +83,16 @@ def rgb_from_name(name):
|
||||
return red, green, blue
|
||||
|
||||
|
||||
def hextriplet(colortuple):
|
||||
"""Convert a color tuple to a hex triplet."""
|
||||
return "#" + "".join(f"{i:02X}" for i in colortuple)
|
||||
|
||||
|
||||
def hex_from_name(name):
|
||||
"""Create a hex color from a string."""
|
||||
return hextriplet(rgb_from_name(name))
|
||||
|
||||
|
||||
def human_size(bytes, units=None):
|
||||
"""Returns a human readable string representation of bytes"""
|
||||
if not units:
|
||||
@@ -161,3 +172,47 @@ def load_entry_points(group):
|
||||
except Exception as e:
|
||||
print(f"Extension {ep.name} of group {group} failed to load with {e}", file=sys.stderr)
|
||||
print(traceback.format_exc(), file=sys.stderr)
|
||||
|
||||
|
||||
def calculate_initial_compass_bearing(start, end):
|
||||
if (type(start) != tuple) or (type(end) != tuple): # noqa: E721
|
||||
raise TypeError("Only tuples are supported as arguments")
|
||||
|
||||
lat1 = math.radians(float(start[0]))
|
||||
lat2 = math.radians(float(end[0]))
|
||||
|
||||
diff_long = math.radians(float(end[1]) - float(start[1]))
|
||||
|
||||
x = math.sin(diff_long) * math.cos(lat2)
|
||||
y = math.cos(lat1) * math.sin(lat2) - (
|
||||
math.sin(lat1)
|
||||
* math.cos(lat2) * math.cos(diff_long)
|
||||
)
|
||||
|
||||
initial_bearing = math.atan2(x, y)
|
||||
|
||||
# Now we have the initial bearing but math.atan2 return values
|
||||
# from -180° to + 180° which is not what we want for a compass bearing
|
||||
# The solution is to normalize the initial bearing as shown below
|
||||
initial_bearing = math.degrees(initial_bearing)
|
||||
compass_bearing = (initial_bearing + 360) % 360
|
||||
|
||||
return compass_bearing
|
||||
|
||||
|
||||
def degrees_to_cardinal(bearing, full_string=False):
|
||||
if full_string:
|
||||
directions = [
|
||||
"North", "North-Northeast", "Northeast", "East-Northeast", "East", "East-Southeast",
|
||||
"Southeast", "South-Southeast", "South", "South-Southwest", "Southwest", "West-Southwest",
|
||||
"West", "West-Northwest", "Northwest", "North-Northwest", "North",
|
||||
]
|
||||
else:
|
||||
directions = [
|
||||
"N", "NNE", "NE", "ENE", "E", "ESE",
|
||||
"SE", "SSE", "S", "SSW", "SW", "WSW",
|
||||
"W", "WNW", "NW", "NNW", "N",
|
||||
]
|
||||
|
||||
cardinal = directions[round(bearing / 22.5)]
|
||||
return cardinal
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
FROM python:3.11-slim as build
|
||||
FROM python:3.11-slim AS build
|
||||
|
||||
ARG VERSION=3.4.0
|
||||
# pass this in as 'dev' if you want to install from github repo vs pypi
|
||||
@@ -40,7 +40,7 @@ RUN set -ex \
|
||||
|
||||
|
||||
### Final stage
|
||||
FROM build as final
|
||||
FROM build AS install
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip3 install -U pip
|
||||
@@ -64,6 +64,8 @@ RUN aprsd --version
|
||||
ADD bin/setup.sh /app
|
||||
ADD bin/admin.sh /app
|
||||
|
||||
|
||||
FROM install AS final
|
||||
# For the web admin interface
|
||||
EXPOSE 8001
|
||||
|
||||
|
||||
+20
-20
@@ -8,23 +8,23 @@ add-trailing-comma==3.1.0 # via gray
|
||||
alabaster==1.0.0 # via sphinx
|
||||
autoflake==1.5.3 # via gray
|
||||
babel==2.16.0 # via sphinx
|
||||
black==24.8.0 # via gray
|
||||
build==1.2.2 # via -r requirements-dev.in, check-manifest, pip-tools
|
||||
black==24.10.0 # via gray
|
||||
build==1.2.2.post1 # via -r requirements-dev.in, check-manifest, pip-tools
|
||||
cachetools==5.5.0 # via tox
|
||||
certifi==2024.8.30 # via requests
|
||||
cfgv==3.4.0 # via pre-commit
|
||||
chardet==5.2.0 # via tox
|
||||
charset-normalizer==3.3.2 # via requests
|
||||
check-manifest==0.49 # via -r requirements-dev.in
|
||||
charset-normalizer==3.4.0 # via requests
|
||||
check-manifest==0.50 # via -r requirements-dev.in
|
||||
click==8.1.7 # via black, fixit, moreorless, pip-tools
|
||||
colorama==0.4.6 # via tox
|
||||
commonmark==0.9.1 # via rich
|
||||
configargparse==1.7 # via gray
|
||||
coverage[toml]==7.6.1 # via pytest-cov
|
||||
distlib==0.3.8 # via virtualenv
|
||||
coverage[toml]==7.6.3 # via pytest-cov
|
||||
distlib==0.3.9 # via virtualenv
|
||||
docutils==0.21.2 # via m2r, sphinx
|
||||
exceptiongroup==1.2.2 # via pytest
|
||||
filelock==3.16.0 # via tox, virtualenv
|
||||
filelock==3.16.1 # via tox, virtualenv
|
||||
fixit==2.1.0 # via gray
|
||||
flake8==7.1.1 # via -r requirements-dev.in, pep8-naming
|
||||
gray==0.15.0 # via -r requirements-dev.in
|
||||
@@ -34,35 +34,35 @@ imagesize==1.4.1 # via sphinx
|
||||
iniconfig==2.0.0 # via pytest
|
||||
isort==5.13.2 # via -r requirements-dev.in, gray
|
||||
jinja2==3.1.4 # via sphinx
|
||||
libcst==1.4.0 # via fixit
|
||||
libcst==1.5.0 # via fixit
|
||||
m2r==0.3.1 # via -r requirements-dev.in
|
||||
markupsafe==2.1.5 # via jinja2
|
||||
markupsafe==3.0.2 # via jinja2
|
||||
mccabe==0.7.0 # via flake8
|
||||
mistune==0.8.4 # via m2r
|
||||
moreorless==0.4.0 # via fixit
|
||||
mypy==1.11.2 # via -r requirements-dev.in
|
||||
mypy==1.12.0 # via -r requirements-dev.in
|
||||
mypy-extensions==1.0.0 # via black, mypy
|
||||
nodeenv==1.9.1 # via pre-commit
|
||||
packaging==24.1 # via black, build, fixit, pyproject-api, pytest, sphinx, tox
|
||||
pathspec==0.12.1 # via black, trailrunner
|
||||
pep8-naming==0.14.1 # via -r requirements-dev.in
|
||||
pip-tools==7.4.1 # via -r requirements-dev.in
|
||||
platformdirs==4.3.3 # via black, tox, virtualenv
|
||||
platformdirs==4.3.6 # via black, tox, virtualenv
|
||||
pluggy==1.5.0 # via pytest, tox
|
||||
pre-commit==3.8.0 # via -r requirements-dev.in
|
||||
pre-commit==4.0.1 # via -r requirements-dev.in
|
||||
pycodestyle==2.12.1 # via flake8
|
||||
pyflakes==3.2.0 # via autoflake, flake8
|
||||
pygments==2.18.0 # via rich, sphinx
|
||||
pyproject-api==1.7.1 # via tox
|
||||
pyproject-hooks==1.1.0 # via build, pip-tools
|
||||
pyproject-api==1.8.0 # via tox
|
||||
pyproject-hooks==1.2.0 # via build, pip-tools
|
||||
pytest==8.3.3 # via -r requirements-dev.in, pytest-cov
|
||||
pytest-cov==5.0.0 # via -r requirements-dev.in
|
||||
pyupgrade==3.17.0 # via gray
|
||||
pyupgrade==3.18.0 # via gray
|
||||
pyyaml==6.0.2 # via libcst, pre-commit
|
||||
requests==2.32.3 # via sphinx
|
||||
rich==12.6.0 # via gray
|
||||
snowballstemmer==2.2.0 # via sphinx
|
||||
sphinx==8.0.2 # via -r requirements-dev.in
|
||||
sphinx==8.1.3 # via -r requirements-dev.in
|
||||
sphinxcontrib-applehelp==2.0.0 # via sphinx
|
||||
sphinxcontrib-devhelp==2.0.0 # via sphinx
|
||||
sphinxcontrib-htmlhelp==2.1.0 # via sphinx
|
||||
@@ -71,14 +71,14 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx
|
||||
sphinxcontrib-serializinghtml==2.0.0 # via sphinx
|
||||
tokenize-rt==6.0.0 # via add-trailing-comma, pyupgrade
|
||||
toml==0.10.2 # via autoflake
|
||||
tomli==2.0.1 # via black, build, check-manifest, coverage, fixit, mypy, pip-tools, pyproject-api, pytest, sphinx, tox
|
||||
tox==4.18.1 # via -r requirements-dev.in
|
||||
tomli==2.0.2 # via black, build, check-manifest, coverage, fixit, mypy, pip-tools, pyproject-api, pytest, sphinx, tox
|
||||
tox==4.23.0 # via -r requirements-dev.in
|
||||
trailrunner==1.4.0 # via fixit
|
||||
typing-extensions==4.12.2 # via black, mypy
|
||||
typing-extensions==4.12.2 # via black, mypy, tox
|
||||
unify==0.5 # via gray
|
||||
untokenize==0.1.1 # via unify
|
||||
urllib3==2.2.3 # via requests
|
||||
virtualenv==20.26.4 # via pre-commit, tox
|
||||
virtualenv==20.27.0 # via pre-commit, tox
|
||||
wheel==0.44.0 # via -r requirements-dev.in, pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
|
||||
+1
-3
@@ -5,12 +5,10 @@ click
|
||||
click-params
|
||||
dataclasses
|
||||
dataclasses-json
|
||||
eventlet
|
||||
flask
|
||||
flask-httpauth
|
||||
flask-socketio
|
||||
geopy
|
||||
gevent
|
||||
imapclient
|
||||
kiss3
|
||||
loguru
|
||||
@@ -18,7 +16,6 @@ oslo.config
|
||||
pluggy
|
||||
python-socketio
|
||||
pyyaml
|
||||
pytz
|
||||
requests
|
||||
# Pinned due to gray needing 12.6.0
|
||||
rich~=12.6.0
|
||||
@@ -30,3 +27,4 @@ thesmuggler
|
||||
tzlocal
|
||||
update_checker
|
||||
wrapt
|
||||
pytz
|
||||
|
||||
+7
-16
@@ -9,10 +9,10 @@ attrs==24.2.0 # via ax253, kiss3, rush
|
||||
ax253==0.1.5.post1 # via kiss3
|
||||
beautifulsoup4==4.12.3 # via -r requirements.in
|
||||
bidict==0.23.1 # via python-socketio
|
||||
bitarray==2.9.2 # via ax253, kiss3
|
||||
bitarray==3.0.0 # via ax253, kiss3
|
||||
blinker==1.8.2 # via flask
|
||||
certifi==2024.8.30 # via requests
|
||||
charset-normalizer==3.3.2 # via requests
|
||||
charset-normalizer==3.4.0 # via requests
|
||||
click==8.1.7 # via -r requirements.in, click-params, flask
|
||||
click-params==0.5.0 # via -r requirements.in
|
||||
commonmark==0.9.1 # via rich
|
||||
@@ -20,15 +20,11 @@ dataclasses==0.6 # via -r requirements.in
|
||||
dataclasses-json==0.6.7 # via -r requirements.in
|
||||
debtcollector==3.0.0 # via oslo-config
|
||||
deprecated==1.2.14 # via click-params
|
||||
dnspython==2.6.1 # via eventlet
|
||||
eventlet==0.37.0 # via -r requirements.in
|
||||
flask==3.0.3 # via -r requirements.in, flask-httpauth, flask-socketio
|
||||
flask-httpauth==4.8.0 # via -r requirements.in
|
||||
flask-socketio==5.3.7 # via -r requirements.in
|
||||
flask-socketio==5.4.1 # via -r requirements.in
|
||||
geographiclib==2.0 # via geopy
|
||||
geopy==2.4.1 # via -r requirements.in
|
||||
gevent==24.2.1 # via -r requirements.in
|
||||
greenlet==3.1.0 # via eventlet, gevent
|
||||
h11==0.14.0 # via wsproto
|
||||
idna==3.10 # via requests
|
||||
imapclient==3.0.1 # via -r requirements.in
|
||||
@@ -37,8 +33,8 @@ itsdangerous==2.2.0 # via flask
|
||||
jinja2==3.1.4 # via flask
|
||||
kiss3==8.0.0 # via -r requirements.in
|
||||
loguru==0.7.2 # via -r requirements.in
|
||||
markupsafe==2.1.5 # via jinja2, werkzeug
|
||||
marshmallow==3.22.0 # via dataclasses-json
|
||||
markupsafe==3.0.2 # via jinja2, werkzeug
|
||||
marshmallow==3.23.0 # via dataclasses-json
|
||||
mypy-extensions==1.0.0 # via typing-inspect
|
||||
netaddr==1.3.0 # via oslo-config
|
||||
oslo-config==9.6.0 # via -r requirements.in
|
||||
@@ -49,7 +45,7 @@ pluggy==1.5.0 # via -r requirements.in
|
||||
pygments==2.18.0 # via rich
|
||||
pyserial==3.5 # via pyserial-asyncio
|
||||
pyserial-asyncio==0.6 # via kiss3
|
||||
python-engineio==4.9.1 # via python-socketio
|
||||
python-engineio==4.10.1 # via python-socketio
|
||||
python-socketio==5.11.4 # via -r requirements.in, flask-socketio
|
||||
pytz==2024.2 # via -r requirements.in
|
||||
pyyaml==6.0.2 # via -r requirements.in, oslo-config
|
||||
@@ -58,7 +54,7 @@ rfc3986==2.0.0 # via oslo-config
|
||||
rich==12.6.0 # via -r requirements.in
|
||||
rush==2021.4.0 # via -r requirements.in
|
||||
shellingham==1.5.4 # via -r requirements.in
|
||||
simple-websocket==1.0.0 # via python-engineio
|
||||
simple-websocket==1.1.0 # via python-engineio
|
||||
six==1.16.0 # via -r requirements.in
|
||||
soupsieve==2.6 # via beautifulsoup4
|
||||
stevedore==5.3.0 # via oslo-config
|
||||
@@ -74,8 +70,3 @@ werkzeug==3.0.4 # via flask
|
||||
wrapt==1.16.0 # via -r requirements.in, debtcollector, deprecated
|
||||
wsproto==1.2.0 # via simple-websocket
|
||||
zipp==3.20.2 # via importlib-metadata
|
||||
zope-event==5.0 # via gevent
|
||||
zope-interface==7.0.3 # via gevent
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# setuptools
|
||||
|
||||
Reference in New Issue
Block a user