2022-12-15 17:23:54 -05:00
|
|
|
import logging
|
2024-04-19 12:53:12 -04:00
|
|
|
import threading
|
2022-12-15 17:23:54 -05:00
|
|
|
import time
|
|
|
|
|
2023-01-18 11:46:44 -05:00
|
|
|
from oslo_config import cfg
|
2023-07-08 17:30:22 -04:00
|
|
|
from rush import quota, throttle
|
|
|
|
from rush.contrib import decorator
|
|
|
|
from rush.limiters import periodic
|
|
|
|
from rush.stores import dictionary
|
2024-04-19 12:53:12 -04:00
|
|
|
import wrapt
|
2023-01-18 11:46:44 -05:00
|
|
|
|
2023-05-05 11:06:00 -04:00
|
|
|
from aprsd import conf # noqa
|
2022-12-15 17:23:54 -05:00
|
|
|
from aprsd import threads as aprsd_threads
|
2024-05-18 14:09:53 -04:00
|
|
|
from aprsd.client import client_factory
|
2024-04-17 14:41:43 -04:00
|
|
|
from aprsd.packets import collector, core
|
2024-03-22 23:20:16 -04:00
|
|
|
from aprsd.packets import log as packet_log
|
|
|
|
from aprsd.packets import tracker
|
2022-12-15 17:23:54 -05:00
|
|
|
|
|
|
|
|
2023-01-18 11:46:44 -05:00
|
|
|
CONF = cfg.CONF
|
2022-12-15 17:23:54 -05:00
|
|
|
LOG = logging.getLogger("APRSD")
|
|
|
|
|
2023-07-08 17:30:22 -04:00
|
|
|
msg_t = throttle.Throttle(
|
|
|
|
limiter=periodic.PeriodicLimiter(
|
|
|
|
store=dictionary.DictionaryStore(),
|
|
|
|
),
|
|
|
|
rate=quota.Quota.per_second(
|
|
|
|
count=CONF.msg_rate_limit_period,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
ack_t = throttle.Throttle(
|
|
|
|
limiter=periodic.PeriodicLimiter(
|
|
|
|
store=dictionary.DictionaryStore(),
|
|
|
|
),
|
|
|
|
rate=quota.Quota.per_second(
|
|
|
|
count=CONF.ack_rate_limit_period,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
msg_throttle_decorator = decorator.ThrottleDecorator(throttle=msg_t)
|
|
|
|
ack_throttle_decorator = decorator.ThrottleDecorator(throttle=ack_t)
|
2024-04-19 12:53:12 -04:00
|
|
|
s_lock = threading.Lock()
|
2023-01-18 11:46:44 -05:00
|
|
|
|
|
|
|
|
2024-04-19 12:53:12 -04:00
|
|
|
@wrapt.synchronized(s_lock)
|
2024-02-24 14:26:55 -05:00
|
|
|
@msg_throttle_decorator.sleep_and_retry
|
2022-12-21 16:26:36 -05:00
|
|
|
def send(packet: core.Packet, direct=False, aprs_client=None):
|
|
|
|
"""Send a packet either in a thread or directly to the client."""
|
|
|
|
# prepare the packet for sending.
|
|
|
|
# This constructs the packet.raw
|
|
|
|
packet.prepare()
|
2024-04-19 12:53:12 -04:00
|
|
|
# Have to call the collector to track the packet
|
|
|
|
# After prepare, as prepare assigns the msgNo
|
|
|
|
collector.PacketCollector().tx(packet)
|
2023-01-18 11:46:44 -05:00
|
|
|
if isinstance(packet, core.AckPacket):
|
|
|
|
_send_ack(packet, direct=direct, aprs_client=aprs_client)
|
|
|
|
else:
|
|
|
|
_send_packet(packet, direct=direct, aprs_client=aprs_client)
|
|
|
|
|
|
|
|
|
2023-07-08 17:30:22 -04:00
|
|
|
@msg_throttle_decorator.sleep_and_retry
|
2023-01-18 11:46:44 -05:00
|
|
|
def _send_packet(packet: core.Packet, direct=False, aprs_client=None):
|
2022-12-21 16:26:36 -05:00
|
|
|
if not direct:
|
2023-01-18 11:46:44 -05:00
|
|
|
thread = SendPacketThread(packet=packet)
|
2022-12-21 16:26:36 -05:00
|
|
|
thread.start()
|
|
|
|
else:
|
2023-01-18 11:46:44 -05:00
|
|
|
_send_direct(packet, aprs_client=aprs_client)
|
|
|
|
|
|
|
|
|
2023-07-08 17:30:22 -04:00
|
|
|
@ack_throttle_decorator.sleep_and_retry
|
2023-01-18 11:46:44 -05:00
|
|
|
def _send_ack(packet: core.AckPacket, direct=False, aprs_client=None):
|
|
|
|
if not direct:
|
|
|
|
thread = SendAckThread(packet=packet)
|
|
|
|
thread.start()
|
|
|
|
else:
|
|
|
|
_send_direct(packet, aprs_client=aprs_client)
|
|
|
|
|
|
|
|
|
|
|
|
def _send_direct(packet, aprs_client=None):
|
|
|
|
if aprs_client:
|
|
|
|
cl = aprs_client
|
|
|
|
else:
|
2024-05-18 14:09:53 -04:00
|
|
|
cl = client_factory.create()
|
2023-01-18 11:46:44 -05:00
|
|
|
|
|
|
|
packet.update_timestamp()
|
2024-03-22 23:20:16 -04:00
|
|
|
packet_log.log(packet, tx=True)
|
2024-04-02 14:07:37 -04:00
|
|
|
try:
|
|
|
|
cl.send(packet)
|
|
|
|
except Exception as e:
|
|
|
|
LOG.error(f"Failed to send packet: {packet}")
|
|
|
|
LOG.error(e)
|
2022-12-21 16:26:36 -05:00
|
|
|
|
|
|
|
|
2022-12-15 17:23:54 -05:00
|
|
|
class SendPacketThread(aprsd_threads.APRSDThread):
|
2022-12-17 18:00:26 -05:00
|
|
|
loop_count: int = 1
|
|
|
|
|
2022-12-15 17:23:54 -05:00
|
|
|
def __init__(self, packet):
|
|
|
|
self.packet = packet
|
2024-04-17 14:41:43 -04:00
|
|
|
super().__init__(f"TX-{packet.to_call}-{self.packet.msgNo}")
|
2022-12-15 17:23:54 -05:00
|
|
|
|
|
|
|
def loop(self):
|
|
|
|
"""Loop until a message is acked or it gets delayed.
|
|
|
|
|
|
|
|
We only sleep for 5 seconds between each loop run, so
|
|
|
|
that CTRL-C can exit the app in a short period. Each sleep
|
|
|
|
means the app quitting is blocked until sleep is done.
|
|
|
|
So we keep track of the last send attempt and only send if the
|
|
|
|
last send attempt is old enough.
|
|
|
|
|
|
|
|
"""
|
|
|
|
pkt_tracker = tracker.PacketTrack()
|
|
|
|
# lets see if the message is still in the tracking queue
|
|
|
|
packet = pkt_tracker.get(self.packet.msgNo)
|
|
|
|
if not packet:
|
|
|
|
# The message has been removed from the tracking queue
|
|
|
|
# So it got acked and we are done.
|
2022-12-18 21:44:23 -05:00
|
|
|
LOG.info(
|
2022-12-21 16:26:36 -05:00
|
|
|
f"{self.packet.__class__.__name__}"
|
2022-12-19 10:28:22 -05:00
|
|
|
f"({self.packet.msgNo}) "
|
2022-12-18 21:44:23 -05:00
|
|
|
"Message Send Complete via Ack.",
|
|
|
|
)
|
2022-12-15 17:23:54 -05:00
|
|
|
return False
|
|
|
|
else:
|
|
|
|
send_now = False
|
2024-04-15 11:29:26 -04:00
|
|
|
if packet.send_count >= packet.retry_count:
|
2022-12-15 17:23:54 -05:00
|
|
|
# we reached the send limit, don't send again
|
|
|
|
# TODO(hemna) - Need to put this in a delayed queue?
|
2022-12-18 21:44:23 -05:00
|
|
|
LOG.info(
|
|
|
|
f"{packet.__class__.__name__} "
|
|
|
|
f"({packet.msgNo}) "
|
|
|
|
"Message Send Complete. Max attempts reached"
|
|
|
|
f" {packet.retry_count}",
|
|
|
|
)
|
2024-04-12 11:12:57 -04:00
|
|
|
pkt_tracker.remove(packet.msgNo)
|
2022-12-15 17:23:54 -05:00
|
|
|
return False
|
|
|
|
|
|
|
|
# Message is still outstanding and needs to be acked.
|
2022-12-17 18:00:26 -05:00
|
|
|
if packet.last_send_time:
|
2022-12-15 17:23:54 -05:00
|
|
|
# Message has a last send time tracking
|
2023-10-13 15:35:41 -04:00
|
|
|
now = int(round(time.time()))
|
2022-12-18 21:44:23 -05:00
|
|
|
sleeptime = (packet.send_count + 1) * 31
|
2022-12-17 18:00:26 -05:00
|
|
|
delta = now - packet.last_send_time
|
2023-10-13 15:35:41 -04:00
|
|
|
if delta > sleeptime:
|
2022-12-15 17:23:54 -05:00
|
|
|
# It's time to try to send it again
|
|
|
|
send_now = True
|
|
|
|
else:
|
|
|
|
send_now = True
|
|
|
|
|
|
|
|
if send_now:
|
|
|
|
# no attempt time, so lets send it, and start
|
|
|
|
# tracking the time.
|
2023-10-13 15:35:41 -04:00
|
|
|
packet.last_send_time = int(round(time.time()))
|
2024-04-17 14:41:43 -04:00
|
|
|
_send_direct(packet)
|
2022-12-18 21:44:23 -05:00
|
|
|
packet.send_count += 1
|
2022-12-15 17:23:54 -05:00
|
|
|
|
2022-12-17 18:00:26 -05:00
|
|
|
time.sleep(1)
|
2022-12-15 17:23:54 -05:00
|
|
|
# Make sure we get called again.
|
2022-12-17 18:00:26 -05:00
|
|
|
self.loop_count += 1
|
2022-12-15 17:23:54 -05:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class SendAckThread(aprsd_threads.APRSDThread):
|
2022-12-17 18:00:26 -05:00
|
|
|
loop_count: int = 1
|
2024-04-12 14:36:27 -04:00
|
|
|
max_retries = 3
|
2022-12-17 18:00:26 -05:00
|
|
|
|
2022-12-15 17:23:54 -05:00
|
|
|
def __init__(self, packet):
|
|
|
|
self.packet = packet
|
2024-04-17 14:41:43 -04:00
|
|
|
super().__init__(f"TXAck-{packet.to_call}-{self.packet.msgNo}")
|
2024-04-12 14:36:27 -04:00
|
|
|
self.max_retries = CONF.default_ack_send_count
|
2022-12-15 17:23:54 -05:00
|
|
|
|
|
|
|
def loop(self):
|
|
|
|
"""Separate thread to send acks with retries."""
|
|
|
|
send_now = False
|
2024-04-12 14:36:27 -04:00
|
|
|
if self.packet.send_count == self.max_retries:
|
2022-12-15 17:23:54 -05:00
|
|
|
# we reached the send limit, don't send again
|
|
|
|
# TODO(hemna) - Need to put this in a delayed queue?
|
2024-03-22 23:20:16 -04:00
|
|
|
LOG.debug(
|
2022-12-18 21:44:23 -05:00
|
|
|
f"{self.packet.__class__.__name__}"
|
|
|
|
f"({self.packet.msgNo}) "
|
|
|
|
"Send Complete. Max attempts reached"
|
2024-04-12 14:36:27 -04:00
|
|
|
f" {self.max_retries}",
|
2022-12-18 21:44:23 -05:00
|
|
|
)
|
2022-12-15 17:23:54 -05:00
|
|
|
return False
|
|
|
|
|
2022-12-17 18:00:26 -05:00
|
|
|
if self.packet.last_send_time:
|
2022-12-15 17:23:54 -05:00
|
|
|
# Message has a last send time tracking
|
2023-10-13 15:35:41 -04:00
|
|
|
now = int(round(time.time()))
|
2022-12-15 17:23:54 -05:00
|
|
|
|
|
|
|
# aprs duplicate detection is 30 secs?
|
|
|
|
# (21 only sends first, 28 skips middle)
|
2022-12-17 18:00:26 -05:00
|
|
|
sleep_time = 31
|
|
|
|
delta = now - self.packet.last_send_time
|
2023-10-13 15:35:41 -04:00
|
|
|
if delta > sleep_time:
|
2022-12-15 17:23:54 -05:00
|
|
|
# It's time to try to send it again
|
|
|
|
send_now = True
|
2022-12-17 18:00:26 -05:00
|
|
|
elif self.loop_count % 10 == 0:
|
2022-12-15 17:23:54 -05:00
|
|
|
LOG.debug(f"Still wating. {delta}")
|
|
|
|
else:
|
|
|
|
send_now = True
|
|
|
|
|
|
|
|
if send_now:
|
2024-04-17 14:41:43 -04:00
|
|
|
_send_direct(self.packet)
|
2022-12-18 21:44:23 -05:00
|
|
|
self.packet.send_count += 1
|
2023-10-13 15:35:41 -04:00
|
|
|
self.packet.last_send_time = int(round(time.time()))
|
2022-12-17 18:00:26 -05:00
|
|
|
|
2022-12-15 17:23:54 -05:00
|
|
|
time.sleep(1)
|
2022-12-17 18:00:26 -05:00
|
|
|
self.loop_count += 1
|
2022-12-15 17:23:54 -05:00
|
|
|
return True
|
2024-02-25 14:19:30 -05:00
|
|
|
|
|
|
|
|
|
|
|
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()
|
2024-02-27 16:01:15 -05:00
|
|
|
LOG.info(
|
|
|
|
"Beacon thread is running and will send "
|
|
|
|
f"beacons every {CONF.beacon_interval} seconds.",
|
|
|
|
)
|
2024-02-25 14:19:30 -05:00
|
|
|
|
|
|
|
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,
|
|
|
|
)
|
2024-04-02 14:07:37 -04:00
|
|
|
try:
|
|
|
|
# Only send it once
|
|
|
|
pkt.retry_count = 1
|
|
|
|
send(pkt, direct=True)
|
|
|
|
except Exception as e:
|
|
|
|
LOG.error(f"Failed to send beacon: {e}")
|
2024-05-18 14:09:53 -04:00
|
|
|
client_factory.create().reset()
|
2024-04-02 14:07:37 -04:00
|
|
|
time.sleep(5)
|
|
|
|
|
2024-02-25 14:19:30 -05:00
|
|
|
self._loop_cnt += 1
|
|
|
|
time.sleep(1)
|
|
|
|
return True
|