diff --git a/aprsd/cli_helper.py b/aprsd/cli_helper.py index 2105a46..52e9e02 100644 --- a/aprsd/cli_helper.py +++ b/aprsd/cli_helper.py @@ -1,9 +1,8 @@ +import click from functools import update_wrapper import logging from pathlib import Path import typing as t - -import click from oslo_config import cfg import aprsd @@ -50,9 +49,6 @@ common_options = [ ] -import click - - class AliasedGroup(click.Group): def command(self, *args, **kwargs): """A shortcut decorator for declaring and attaching a command to diff --git a/aprsd/cmds/server.py b/aprsd/cmds/server.py index 886d6b9..b1848ae 100644 --- a/aprsd/cmds/server.py +++ b/aprsd/cmds/server.py @@ -11,7 +11,7 @@ from aprsd import main as aprsd_main from aprsd import packets, plugin, threads, utils from aprsd.main import cli from aprsd.rpc import server as rpc_server -from aprsd.threads import rx +from aprsd.threads import rx, tx CONF = cfg.CONF @@ -107,6 +107,10 @@ def server(ctx, flush): process_thread.start() packets.PacketTrack().restart() + if CONF.enable_beacon: + LOG.info("Beacon Enabled. Starting Beacon thread.") + bcn_thread = tx.BeaconSendThread() + bcn_thread.start() if CONF.rpc_settings.enabled: rpc = rpc_server.APRSDRPCThread() diff --git a/aprsd/conf/common.py b/aprsd/conf/common.py index b31fa31..e615601 100644 --- a/aprsd/conf/common.py +++ b/aprsd/conf/common.py @@ -70,6 +70,32 @@ aprsd_opts = [ default=60, help="The number of seconds before a packet is not considered a duplicate.", ), + cfg.BoolOpt( + "enable_beacon", + default=False, + help="Enable sending of a GPS Beacon packet to locate this service. " + "Requires latitude and longitude to be set.", + ), + cfg.IntOpt( + "beacon_interval", + default=600, + help="The number of seconds between beacon packets.", + ), + cfg.StrOpt( + "beacon_symbol", + default="/", + help="The symbol to use for the GPS Beacon packet. See: http://www.aprs.net/vm/DOS/SYMBOLS.HTM", + ), + cfg.StrOpt( + "latitude", + default=None, + help="Latitude for the GPS Beacon button. If not set, the button will not be enabled.", + ), + cfg.StrOpt( + "longitude", + default=None, + help="Longitude for the GPS Beacon button. If not set, the button will not be enabled.", + ), ] watch_list_opts = [ diff --git a/aprsd/packets/__init__.py b/aprsd/packets/__init__.py index 7fab8bb..dcb2064 100644 --- a/aprsd/packets/__init__.py +++ b/aprsd/packets/__init__.py @@ -1,6 +1,6 @@ from aprsd.packets.core import ( # noqa: F401 - AckPacket, GPSPacket, MessagePacket, MicEPacket, Packet, RejectPacket, - StatusPacket, WeatherPacket, + AckPacket, BeaconPacket, GPSPacket, MessagePacket, MicEPacket, Packet, + RejectPacket, StatusPacket, WeatherPacket, ) from aprsd.packets.packet_list import PacketList # noqa: F401 from aprsd.packets.seen_list import SeenList # noqa: F401 diff --git a/aprsd/packets/core.py b/aprsd/packets/core.py index c9362d8..e8cf57a 100644 --- a/aprsd/packets/core.py +++ b/aprsd/packets/core.py @@ -466,6 +466,26 @@ class GPSPacket(Packet): ) +@dataclass(unsafe_hash=True) +class BeaconPacket(GPSPacket): + def _build_payload(self): + """The payload is the non headers portion of the packet.""" + time_zulu = self._build_time_zulu() + lat = self.convert_latitude(self.latitude) + long = self.convert_longitude(self.longitude) + + self.payload = ( + f"@{time_zulu}z{lat}{self.symbol_table}" + f"{long}{self.symbol}APRSD Beacon" + ) + + def _build_raw(self): + self.raw = ( + f"{self.from_call}>APZ100:" + f"{self.payload}" + ) + + @dataclass class MicEPacket(GPSPacket): messagecapable: bool = False diff --git a/aprsd/threads/tx.py b/aprsd/threads/tx.py index 84dd7dd..d81f1c1 100644 --- a/aprsd/threads/tx.py +++ b/aprsd/threads/tx.py @@ -37,7 +37,6 @@ msg_throttle_decorator = decorator.ThrottleDecorator(throttle=msg_t) ack_throttle_decorator = decorator.ThrottleDecorator(throttle=ack_t) - @msg_throttle_decorator.sleep_and_retry def send(packet: core.Packet, direct=False, aprs_client=None): """Send a packet either in a thread or directly to the client.""" @@ -196,3 +195,38 @@ class SendAckThread(aprsd_threads.APRSDThread): time.sleep(1) self.loop_count += 1 return True + + +class BeaconSendThread(aprsd_threads.APRSDThread): + """Thread that sends a GPS beacon packet periodically. + + Settings are in the [DEFAULT] section of the config file. + """ + _loop_cnt: int = 1 + + def __init__(self): + super().__init__("BeaconSendThread") + self._loop_cnt = 1 + # Make sure Latitude and Longitude are set. + if not CONF.latitude or not CONF.longitude: + LOG.error( + "Latitude and Longitude are not set in the config file." + "Beacon will not be sent and thread is STOPPED.", + ) + self.stop() + + def loop(self): + # Only dump out the stats every N seconds + if self._loop_cnt % CONF.beacon_interval == 0: + pkt = core.BeaconPacket( + from_call=CONF.callsign, + to_call="APRS", + latitude=float(CONF.latitude), + longitude=float(CONF.longitude), + comment="APRSD GPS Beacon", + symbol=CONF.beacon_symbol, + ) + send(pkt, direct=True) + self._loop_cnt += 1 + time.sleep(1) + return True diff --git a/aprsd/utils/objectstore.py b/aprsd/utils/objectstore.py index dec9dca..7431b7e 100644 --- a/aprsd/utils/objectstore.py +++ b/aprsd/utils/objectstore.py @@ -99,7 +99,6 @@ class ObjectStoreMixin: LOG.debug( f"{self.__class__.__name__}::Loaded {len(self)} entries from disk.", ) - #LOG.debug(f"{self.data}") else: LOG.debug(f"{self.__class__.__name__}::No data to load.") except (pickle.UnpicklingError, Exception) as ex: