2020-12-23 13:12:04 -05:00
|
|
|
import abc
|
2020-12-29 10:31:16 -05:00
|
|
|
import datetime
|
2020-12-17 10:00:47 -05:00
|
|
|
import logging
|
2021-01-08 15:47:30 -05:00
|
|
|
from multiprocessing import RawValue
|
2020-12-17 10:00:47 -05:00
|
|
|
import re
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
|
2021-10-22 16:07:20 -04:00
|
|
|
from aprsd import client, objectstore, packets, stats, threads
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2021-08-23 12:14:19 -04:00
|
|
|
|
2020-12-17 10:00:47 -05:00
|
|
|
LOG = logging.getLogger("APRSD")
|
|
|
|
|
|
|
|
# What to return from a plugin if we have processed the message
|
|
|
|
# and it's ok, but don't send a usage string back
|
|
|
|
NULL_MESSAGE = -1
|
|
|
|
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2021-10-22 16:07:20 -04:00
|
|
|
class MsgTrack(objectstore.ObjectStoreMixin):
|
2020-12-29 10:31:16 -05:00
|
|
|
"""Class to keep track of outstanding text messages.
|
|
|
|
|
|
|
|
This is a thread safe class that keeps track of active
|
|
|
|
messages.
|
|
|
|
|
|
|
|
When a message is asked to be sent, it is placed into this
|
|
|
|
class via it's id. The TextMessage class's send() method
|
|
|
|
automatically adds itself to this class. When the ack is
|
|
|
|
recieved from the radio, the message object is removed from
|
|
|
|
this class.
|
|
|
|
"""
|
|
|
|
|
|
|
|
_instance = None
|
2021-01-14 14:32:30 -05:00
|
|
|
_start_time = None
|
2020-12-29 10:31:16 -05:00
|
|
|
lock = None
|
|
|
|
|
2021-10-22 16:07:20 -04:00
|
|
|
data = {}
|
2020-12-30 07:32:20 -05:00
|
|
|
total_messages_tracked = 0
|
2020-12-29 10:31:16 -05:00
|
|
|
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
if cls._instance is None:
|
2021-01-08 15:47:30 -05:00
|
|
|
cls._instance = super().__new__(cls)
|
2020-12-29 10:31:16 -05:00
|
|
|
cls._instance.track = {}
|
2021-01-21 20:58:47 -05:00
|
|
|
cls._instance._start_time = datetime.datetime.now()
|
2020-12-29 10:31:16 -05:00
|
|
|
cls._instance.lock = threading.Lock()
|
2021-10-22 16:07:20 -04:00
|
|
|
cls._instance.config = kwargs["config"]
|
|
|
|
cls._instance._init_store()
|
2020-12-29 10:31:16 -05:00
|
|
|
return cls._instance
|
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
def __getitem__(self, name):
|
2020-12-29 10:31:16 -05:00
|
|
|
with self.lock:
|
2021-10-22 16:07:20 -04:00
|
|
|
return self.data[name]
|
2020-12-29 10:31:16 -05:00
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
def __iter__(self):
|
2020-12-29 10:31:16 -05:00
|
|
|
with self.lock:
|
2021-10-22 16:07:20 -04:00
|
|
|
return iter(self.data)
|
2020-12-29 10:31:16 -05:00
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
def keys(self):
|
2020-12-29 10:31:16 -05:00
|
|
|
with self.lock:
|
2021-10-22 16:07:20 -04:00
|
|
|
return self.data.keys()
|
2021-01-25 11:24:39 -05:00
|
|
|
|
|
|
|
def items(self):
|
|
|
|
with self.lock:
|
2021-10-22 16:07:20 -04:00
|
|
|
return self.data.items()
|
2021-01-25 11:24:39 -05:00
|
|
|
|
|
|
|
def values(self):
|
|
|
|
with self.lock:
|
2021-10-22 16:07:20 -04:00
|
|
|
return self.data.values()
|
2020-12-29 10:31:16 -05:00
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
with self.lock:
|
2021-10-22 16:07:20 -04:00
|
|
|
return len(self.data)
|
2020-12-29 10:31:16 -05:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
with self.lock:
|
|
|
|
result = "{"
|
2021-10-22 16:07:20 -04:00
|
|
|
for key in self.data.keys():
|
|
|
|
result += f"{key}: {str(self.data[key])}, "
|
2020-12-29 10:31:16 -05:00
|
|
|
result += "}"
|
|
|
|
return result
|
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
def add(self, msg):
|
|
|
|
with self.lock:
|
|
|
|
key = int(msg.id)
|
2021-10-22 16:07:20 -04:00
|
|
|
self.data[key] = msg
|
2021-01-25 11:24:39 -05:00
|
|
|
stats.APRSDStats().msgs_tracked_inc()
|
|
|
|
self.total_messages_tracked += 1
|
|
|
|
|
|
|
|
def get(self, id):
|
|
|
|
with self.lock:
|
2021-10-22 16:07:20 -04:00
|
|
|
if id in self.data:
|
|
|
|
return self.data[id]
|
2021-01-25 11:24:39 -05:00
|
|
|
|
|
|
|
def remove(self, id):
|
|
|
|
with self.lock:
|
|
|
|
key = int(id)
|
2021-10-22 16:07:20 -04:00
|
|
|
if key in self.data.keys():
|
|
|
|
del self.data[key]
|
2020-12-30 07:32:20 -05:00
|
|
|
|
|
|
|
def restart(self):
|
|
|
|
"""Walk the list of messages and restart them if any."""
|
2020-12-30 09:10:28 -05:00
|
|
|
|
2021-10-22 16:07:20 -04:00
|
|
|
for key in self.data.keys():
|
|
|
|
msg = self.data[key]
|
2020-12-30 09:10:28 -05:00
|
|
|
if msg.last_send_attempt < msg.retry_count:
|
|
|
|
msg.send()
|
|
|
|
|
2021-01-11 11:03:41 -05:00
|
|
|
def _resend(self, msg):
|
|
|
|
msg.last_send_attempt = 0
|
|
|
|
msg.send()
|
|
|
|
|
|
|
|
def restart_delayed(self, count=None, most_recent=True):
|
2020-12-30 09:10:28 -05:00
|
|
|
"""Walk the list of delayed messages and restart them if any."""
|
2021-01-11 11:03:41 -05:00
|
|
|
if not count:
|
|
|
|
# Send all the delayed messages
|
2021-10-22 16:07:20 -04:00
|
|
|
for key in self.data.keys():
|
|
|
|
msg = self.data[key]
|
2021-01-11 11:03:41 -05:00
|
|
|
if msg.last_send_attempt == msg.retry_count:
|
|
|
|
self._resend(msg)
|
|
|
|
else:
|
|
|
|
# They want to resend <count> delayed messages
|
|
|
|
tmp = sorted(
|
2021-10-22 16:07:20 -04:00
|
|
|
self.data.items(),
|
2021-01-11 11:03:41 -05:00
|
|
|
reverse=most_recent,
|
|
|
|
key=lambda x: x[1].last_send_time,
|
|
|
|
)
|
|
|
|
msg_list = tmp[:count]
|
|
|
|
for (_key, msg) in msg_list:
|
|
|
|
self._resend(msg)
|
2020-12-30 07:32:20 -05:00
|
|
|
|
2020-12-29 10:31:16 -05:00
|
|
|
|
2021-01-08 15:47:30 -05:00
|
|
|
class MessageCounter:
|
2020-12-23 13:12:04 -05:00
|
|
|
"""
|
|
|
|
Global message id counter class.
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
This is a singleton based class that keeps
|
|
|
|
an incrementing counter for all messages to
|
|
|
|
be sent. All new Message objects gets a new
|
|
|
|
message id, which is the next number available
|
|
|
|
from the MessageCounter.
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
"""
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
_instance = None
|
2020-12-29 10:31:16 -05:00
|
|
|
max_count = 9999
|
2021-07-14 20:50:41 -04:00
|
|
|
lock = None
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
"""Make this a singleton class."""
|
|
|
|
if cls._instance is None:
|
2021-07-14 20:50:41 -04:00
|
|
|
cls._instance = super().__new__(cls, *args, **kwargs)
|
2020-12-24 12:39:48 -05:00
|
|
|
cls._instance.val = RawValue("i", 1)
|
2020-12-23 13:12:04 -05:00
|
|
|
cls._instance.lock = threading.Lock()
|
|
|
|
return cls._instance
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
def increment(self):
|
|
|
|
with self.lock:
|
2020-12-29 10:31:16 -05:00
|
|
|
if self.val.value == self.max_count:
|
|
|
|
self.val.value = 1
|
|
|
|
else:
|
|
|
|
self.val.value += 1
|
2020-12-17 10:00:47 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
@property
|
|
|
|
def value(self):
|
|
|
|
with self.lock:
|
|
|
|
return self.val.value
|
2020-12-20 16:33:18 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
def __repr__(self):
|
|
|
|
with self.lock:
|
|
|
|
return str(self.val.value)
|
2020-12-20 16:33:18 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
def __str__(self):
|
|
|
|
with self.lock:
|
|
|
|
return str(self.val.value)
|
|
|
|
|
|
|
|
|
2021-01-08 15:47:30 -05:00
|
|
|
class Message(metaclass=abc.ABCMeta):
|
2020-12-23 13:12:04 -05:00
|
|
|
"""Base Message Class."""
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
# The message id to send over the air
|
|
|
|
id = 0
|
2020-12-17 10:00:47 -05:00
|
|
|
|
|
|
|
retry_count = 3
|
2021-07-09 15:59:21 -04:00
|
|
|
last_send_time = 0
|
2020-12-29 10:31:16 -05:00
|
|
|
last_send_attempt = 0
|
2020-12-23 13:12:04 -05:00
|
|
|
|
2021-02-25 21:01:52 -05:00
|
|
|
transport = None
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
fromcall,
|
|
|
|
tocall,
|
|
|
|
msg_id=None,
|
2021-12-10 14:20:57 -05:00
|
|
|
allow_delay=True,
|
2021-02-25 21:01:52 -05:00
|
|
|
):
|
2020-12-23 13:12:04 -05:00
|
|
|
self.fromcall = fromcall
|
|
|
|
self.tocall = tocall
|
|
|
|
if not msg_id:
|
|
|
|
c = MessageCounter()
|
|
|
|
c.increment()
|
|
|
|
msg_id = c.value
|
|
|
|
self.id = msg_id
|
|
|
|
|
2021-12-10 14:20:57 -05:00
|
|
|
# do we try and save this message for later if we don't get
|
|
|
|
# an ack? Some messages we don't want to do this ever.
|
|
|
|
self.allow_delay = allow_delay
|
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
@abc.abstractmethod
|
|
|
|
def send(self):
|
|
|
|
"""Child class must declare."""
|
|
|
|
|
|
|
|
|
2021-01-12 14:50:49 -05:00
|
|
|
class RawMessage(Message):
|
|
|
|
"""Send a raw message.
|
|
|
|
|
|
|
|
This class is used for custom messages that contain the entire
|
|
|
|
contents of an APRS message in the message field.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
message = None
|
|
|
|
|
2021-12-10 14:20:57 -05:00
|
|
|
def __init__(self, message, allow_delay=True):
|
|
|
|
super().__init__(fromcall=None, tocall=None, msg_id=None, allow_delay=allow_delay)
|
2021-01-12 14:50:49 -05:00
|
|
|
self.message = message
|
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
def dict(self):
|
|
|
|
now = datetime.datetime.now()
|
2021-07-09 15:59:21 -04:00
|
|
|
last_send_age = None
|
|
|
|
if self.last_send_time:
|
|
|
|
last_send_age = str(now - self.last_send_time)
|
2021-01-25 11:24:39 -05:00
|
|
|
return {
|
|
|
|
"type": "raw",
|
|
|
|
"message": self.message.rstrip("\n"),
|
|
|
|
"raw": self.message.rstrip("\n"),
|
|
|
|
"retry_count": self.retry_count,
|
|
|
|
"last_send_attempt": self.last_send_attempt,
|
|
|
|
"last_send_time": str(self.last_send_time),
|
2021-07-09 15:59:21 -04:00
|
|
|
"last_send_age": last_send_age,
|
2021-01-25 11:24:39 -05:00
|
|
|
}
|
2021-01-12 14:50:49 -05:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.message
|
|
|
|
|
|
|
|
def send(self):
|
|
|
|
tracker = MsgTrack()
|
|
|
|
tracker.add(self)
|
|
|
|
thread = SendMessageThread(message=self)
|
|
|
|
thread.start()
|
|
|
|
|
2021-05-03 10:28:31 -04:00
|
|
|
def send_direct(self, aprsis_client=None):
|
2021-01-12 14:50:49 -05:00
|
|
|
"""Send a message without a separate thread."""
|
2021-09-17 09:32:30 -04:00
|
|
|
cl = client.factory.create().client
|
2021-01-12 14:50:49 -05:00
|
|
|
log_message(
|
|
|
|
"Sending Message Direct",
|
2021-01-25 11:24:39 -05:00
|
|
|
str(self).rstrip("\n"),
|
2021-01-12 14:50:49 -05:00
|
|
|
self.message,
|
|
|
|
tocall=self.tocall,
|
|
|
|
fromcall=self.fromcall,
|
|
|
|
)
|
2021-02-25 21:01:52 -05:00
|
|
|
cl.send(self)
|
2021-09-17 09:32:30 -04:00
|
|
|
stats.APRSDStats().msgs_tx_inc()
|
2021-01-12 14:50:49 -05:00
|
|
|
|
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
class TextMessage(Message):
|
|
|
|
"""Send regular ARPS text/command messages/replies."""
|
|
|
|
|
|
|
|
message = None
|
|
|
|
|
2021-02-25 21:01:52 -05:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
fromcall,
|
|
|
|
tocall,
|
|
|
|
message,
|
|
|
|
msg_id=None,
|
|
|
|
allow_delay=True,
|
|
|
|
):
|
2021-12-10 14:20:57 -05:00
|
|
|
super().__init__(
|
|
|
|
fromcall=fromcall, tocall=tocall,
|
|
|
|
msg_id=msg_id, allow_delay=allow_delay,
|
|
|
|
)
|
2020-12-23 13:12:04 -05:00
|
|
|
self.message = message
|
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
def dict(self):
|
|
|
|
now = datetime.datetime.now()
|
2021-07-09 15:59:21 -04:00
|
|
|
|
|
|
|
last_send_age = None
|
|
|
|
if self.last_send_time:
|
|
|
|
last_send_age = str(now - self.last_send_time)
|
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
return {
|
|
|
|
"id": self.id,
|
|
|
|
"type": "text-message",
|
|
|
|
"fromcall": self.fromcall,
|
|
|
|
"tocall": self.tocall,
|
|
|
|
"message": self.message.rstrip("\n"),
|
|
|
|
"raw": str(self).rstrip("\n"),
|
|
|
|
"retry_count": self.retry_count,
|
|
|
|
"last_send_attempt": self.last_send_attempt,
|
|
|
|
"last_send_time": str(self.last_send_time),
|
2021-07-09 15:59:21 -04:00
|
|
|
"last_send_age": last_send_age,
|
2021-01-25 11:24:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
def __str__(self):
|
2020-12-23 13:12:04 -05:00
|
|
|
"""Build raw string to send over the air."""
|
2021-01-21 20:58:47 -05:00
|
|
|
return "{}>APZ100::{}:{}{{{}\n".format(
|
2021-01-08 15:47:30 -05:00
|
|
|
self.fromcall,
|
|
|
|
self.tocall.ljust(9),
|
|
|
|
self._filter_for_send(),
|
|
|
|
str(self.id),
|
2020-12-24 12:39:48 -05:00
|
|
|
)
|
2020-12-23 13:12:04 -05:00
|
|
|
|
|
|
|
def _filter_for_send(self):
|
|
|
|
"""Filter and format message string for FCC."""
|
|
|
|
# max? ftm400 displays 64, raw msg shows 74
|
|
|
|
# and ftm400-send is max 64. setting this to
|
|
|
|
# 67 displays 64 on the ftm400. (+3 {01 suffix)
|
|
|
|
# feature req: break long ones into two msgs
|
|
|
|
message = self.message[:67]
|
|
|
|
# We all miss George Carlin
|
|
|
|
return re.sub("fuck|shit|cunt|piss|cock|bitch", "****", message)
|
|
|
|
|
|
|
|
def send(self):
|
2020-12-29 10:31:16 -05:00
|
|
|
tracker = MsgTrack()
|
|
|
|
tracker.add(self)
|
2021-08-23 12:14:19 -04:00
|
|
|
LOG.debug(f"Length of MsgTrack is {len(tracker)}")
|
2020-12-29 10:31:16 -05:00
|
|
|
thread = SendMessageThread(message=self)
|
2020-12-23 13:12:04 -05:00
|
|
|
thread.start()
|
|
|
|
|
2021-05-03 10:28:31 -04:00
|
|
|
def send_direct(self, aprsis_client=None):
|
2020-12-23 13:12:04 -05:00
|
|
|
"""Send a message without a separate thread."""
|
2021-09-02 11:17:15 -04:00
|
|
|
if aprsis_client:
|
|
|
|
cl = aprsis_client
|
|
|
|
else:
|
2021-09-17 09:32:30 -04:00
|
|
|
cl = client.factory.create().client
|
2020-12-23 13:12:04 -05:00
|
|
|
log_message(
|
2020-12-24 12:39:48 -05:00
|
|
|
"Sending Message Direct",
|
2021-01-25 11:24:39 -05:00
|
|
|
str(self).rstrip("\n"),
|
2020-12-24 12:39:48 -05:00
|
|
|
self.message,
|
|
|
|
tocall=self.tocall,
|
|
|
|
fromcall=self.fromcall,
|
2020-12-17 10:00:47 -05:00
|
|
|
)
|
2021-02-25 21:01:52 -05:00
|
|
|
cl.send(self)
|
2021-01-21 20:58:47 -05:00
|
|
|
stats.APRSDStats().msgs_tx_inc()
|
2021-08-26 20:58:07 -04:00
|
|
|
packets.PacketList().add(self.dict())
|
2020-12-23 13:12:04 -05:00
|
|
|
|
|
|
|
|
2020-12-29 10:31:16 -05:00
|
|
|
class SendMessageThread(threads.APRSDThread):
|
|
|
|
def __init__(self, message):
|
|
|
|
self.msg = message
|
|
|
|
name = self.msg.message[:5]
|
2021-08-23 12:14:19 -04:00
|
|
|
super().__init__(f"SendMessage-{self.msg.id}-{name}")
|
2020-12-29 10:31:16 -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.
|
|
|
|
|
|
|
|
"""
|
|
|
|
tracker = MsgTrack()
|
|
|
|
# lets see if the message is still in the tracking queue
|
|
|
|
msg = tracker.get(self.msg.id)
|
|
|
|
if not msg:
|
|
|
|
# The message has been removed from the tracking queue
|
|
|
|
# So it got acked and we are done.
|
|
|
|
LOG.info("Message Send Complete via Ack.")
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
send_now = False
|
|
|
|
if msg.last_send_attempt == msg.retry_count:
|
|
|
|
# we reached the send limit, don't send again
|
|
|
|
# TODO(hemna) - Need to put this in a delayed queue?
|
|
|
|
LOG.info("Message Send Complete. Max attempts reached.")
|
2021-12-10 14:20:57 -05:00
|
|
|
if not msg.allow_delay:
|
|
|
|
tracker.remove(msg.id)
|
2020-12-29 10:31:16 -05:00
|
|
|
return False
|
|
|
|
|
|
|
|
# Message is still outstanding and needs to be acked.
|
|
|
|
if msg.last_send_time:
|
|
|
|
# Message has a last send time tracking
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
sleeptime = (msg.last_send_attempt + 1) * 31
|
|
|
|
delta = now - msg.last_send_time
|
|
|
|
if delta > datetime.timedelta(seconds=sleeptime):
|
|
|
|
# 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.
|
|
|
|
log_message(
|
|
|
|
"Sending Message",
|
2021-01-25 11:24:39 -05:00
|
|
|
str(msg).rstrip("\n"),
|
2020-12-29 10:31:16 -05:00
|
|
|
msg.message,
|
|
|
|
tocall=self.msg.tocall,
|
|
|
|
retry_number=msg.last_send_attempt,
|
|
|
|
msg_num=msg.id,
|
|
|
|
)
|
2021-09-17 09:32:30 -04:00
|
|
|
cl = client.factory.create().client
|
2021-02-25 21:01:52 -05:00
|
|
|
cl.send(msg)
|
2021-01-21 20:58:47 -05:00
|
|
|
stats.APRSDStats().msgs_tx_inc()
|
2021-08-19 11:39:29 -04:00
|
|
|
packets.PacketList().add(msg.dict())
|
2020-12-29 10:31:16 -05:00
|
|
|
msg.last_send_time = datetime.datetime.now()
|
|
|
|
msg.last_send_attempt += 1
|
|
|
|
|
|
|
|
time.sleep(5)
|
|
|
|
# Make sure we get called again.
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
class AckMessage(Message):
|
|
|
|
"""Class for building Acks and sending them."""
|
|
|
|
|
2021-09-17 09:32:30 -04:00
|
|
|
def __init__(self, fromcall, tocall, msg_id):
|
|
|
|
super().__init__(fromcall, tocall, msg_id=msg_id)
|
2020-12-23 13:12:04 -05:00
|
|
|
|
2021-01-25 11:24:39 -05:00
|
|
|
def dict(self):
|
|
|
|
now = datetime.datetime.now()
|
2021-07-09 15:59:21 -04:00
|
|
|
last_send_age = None
|
|
|
|
if self.last_send_time:
|
|
|
|
last_send_age = str(now - self.last_send_time)
|
2021-01-25 11:24:39 -05:00
|
|
|
return {
|
|
|
|
"id": self.id,
|
|
|
|
"type": "ack",
|
|
|
|
"fromcall": self.fromcall,
|
|
|
|
"tocall": self.tocall,
|
|
|
|
"raw": str(self).rstrip("\n"),
|
|
|
|
"retry_count": self.retry_count,
|
|
|
|
"last_send_attempt": self.last_send_attempt,
|
|
|
|
"last_send_time": str(self.last_send_time),
|
2021-07-09 15:59:21 -04:00
|
|
|
"last_send_age": last_send_age,
|
2021-01-25 11:24:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
def __str__(self):
|
2021-01-21 20:58:47 -05:00
|
|
|
return "{}>APZ100::{}:ack{}\n".format(
|
2021-01-08 15:47:30 -05:00
|
|
|
self.fromcall,
|
|
|
|
self.tocall.ljust(9),
|
|
|
|
self.id,
|
2020-12-24 12:39:48 -05:00
|
|
|
)
|
2020-12-23 13:12:04 -05:00
|
|
|
|
2021-09-01 14:48:22 -04:00
|
|
|
def _filter_for_send(self):
|
|
|
|
return f"ack{self.id}"
|
|
|
|
|
2020-12-23 13:12:04 -05:00
|
|
|
def send(self):
|
2021-08-23 12:14:19 -04:00
|
|
|
LOG.debug(f"Send ACK({self.tocall}:{self.id}) to radio.")
|
2020-12-29 10:31:16 -05:00
|
|
|
thread = SendAckThread(self)
|
2020-12-23 13:12:04 -05:00
|
|
|
thread.start()
|
2020-12-24 12:39:48 -05:00
|
|
|
|
2021-05-03 10:28:31 -04:00
|
|
|
def send_direct(self, aprsis_client=None):
|
2020-12-23 13:12:04 -05:00
|
|
|
"""Send an ack message without a separate thread."""
|
2021-09-02 11:17:15 -04:00
|
|
|
if aprsis_client:
|
|
|
|
cl = aprsis_client
|
|
|
|
else:
|
2021-09-17 09:32:30 -04:00
|
|
|
cl = client.factory.create().client
|
2020-12-23 13:12:04 -05:00
|
|
|
log_message(
|
|
|
|
"Sending ack",
|
2021-01-25 11:24:39 -05:00
|
|
|
str(self).rstrip("\n"),
|
2020-12-23 13:12:04 -05:00
|
|
|
None,
|
|
|
|
ack=self.id,
|
|
|
|
tocall=self.tocall,
|
|
|
|
fromcall=self.fromcall,
|
|
|
|
)
|
2021-02-25 21:01:52 -05:00
|
|
|
cl.send(self)
|
2020-12-20 16:33:18 -05:00
|
|
|
|
|
|
|
|
2020-12-29 10:31:16 -05:00
|
|
|
class SendAckThread(threads.APRSDThread):
|
|
|
|
def __init__(self, ack):
|
|
|
|
self.ack = ack
|
2021-08-23 12:14:19 -04:00
|
|
|
super().__init__(f"SendAck-{self.ack.id}")
|
2020-12-29 10:31:16 -05:00
|
|
|
|
|
|
|
def loop(self):
|
|
|
|
"""Separate thread to send acks with retries."""
|
|
|
|
send_now = False
|
|
|
|
if self.ack.last_send_attempt == self.ack.retry_count:
|
|
|
|
# we reached the send limit, don't send again
|
|
|
|
# TODO(hemna) - Need to put this in a delayed queue?
|
|
|
|
LOG.info("Ack Send Complete. Max attempts reached.")
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self.ack.last_send_time:
|
|
|
|
# Message has a last send time tracking
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
|
|
|
|
# aprs duplicate detection is 30 secs?
|
|
|
|
# (21 only sends first, 28 skips middle)
|
|
|
|
sleeptime = 31
|
|
|
|
delta = now - self.ack.last_send_time
|
|
|
|
if delta > datetime.timedelta(seconds=sleeptime):
|
|
|
|
# It's time to try to send it again
|
|
|
|
send_now = True
|
|
|
|
else:
|
2021-08-23 12:14:19 -04:00
|
|
|
LOG.debug(f"Still wating. {delta}")
|
2020-12-29 10:31:16 -05:00
|
|
|
else:
|
|
|
|
send_now = True
|
|
|
|
|
|
|
|
if send_now:
|
2021-09-17 09:32:30 -04:00
|
|
|
cl = client.factory.create().client
|
2020-12-29 10:31:16 -05:00
|
|
|
log_message(
|
|
|
|
"Sending ack",
|
2021-01-25 11:24:39 -05:00
|
|
|
str(self.ack).rstrip("\n"),
|
2020-12-29 10:31:16 -05:00
|
|
|
None,
|
|
|
|
ack=self.ack.id,
|
|
|
|
tocall=self.ack.tocall,
|
|
|
|
retry_number=self.ack.last_send_attempt,
|
|
|
|
)
|
2021-02-25 21:01:52 -05:00
|
|
|
cl.send(self.ack)
|
2021-01-21 20:58:47 -05:00
|
|
|
stats.APRSDStats().ack_tx_inc()
|
2021-08-19 11:39:29 -04:00
|
|
|
packets.PacketList().add(self.ack.dict())
|
2020-12-29 10:31:16 -05:00
|
|
|
self.ack.last_send_attempt += 1
|
|
|
|
self.ack.last_send_time = datetime.datetime.now()
|
|
|
|
time.sleep(5)
|
2021-07-15 14:11:30 -04:00
|
|
|
return True
|
2020-12-29 10:31:16 -05:00
|
|
|
|
|
|
|
|
2020-12-19 16:35:53 -05:00
|
|
|
def log_packet(packet):
|
|
|
|
fromcall = packet.get("from", None)
|
|
|
|
tocall = packet.get("to", None)
|
|
|
|
|
|
|
|
response_type = packet.get("response", None)
|
|
|
|
msg = packet.get("message_text", None)
|
|
|
|
msg_num = packet.get("msgNo", None)
|
|
|
|
ack = packet.get("ack", None)
|
|
|
|
|
|
|
|
log_message(
|
2021-08-24 13:31:33 -04:00
|
|
|
"Packet", packet["raw"], msg, fromcall=fromcall, tocall=tocall,
|
|
|
|
ack=ack, packet_type=response_type, msg_num=msg_num, )
|
2020-12-19 16:35:53 -05:00
|
|
|
|
|
|
|
|
|
|
|
def log_message(
|
2021-08-23 12:14:19 -04:00
|
|
|
header, raw, message, tocall=None, fromcall=None, msg_num=None,
|
|
|
|
retry_number=None, ack=None, packet_type=None, uuid=None,
|
2021-12-10 10:45:51 -05:00
|
|
|
console=None,
|
2020-12-19 16:35:53 -05:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
|
|
|
|
Log a message entry.
|
|
|
|
|
|
|
|
This builds a long string with newlines for the log entry, so that
|
|
|
|
it's thread safe. If we log each item as a separate log.debug() call
|
|
|
|
Then the message information could get multiplexed with other log
|
|
|
|
messages. Each python log call is automatically synchronized.
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
log_list = [""]
|
|
|
|
if retry_number:
|
2021-12-06 14:35:49 -05:00
|
|
|
log_list.append(f"{header} _______________(TX:{retry_number})")
|
2020-12-19 16:35:53 -05:00
|
|
|
else:
|
2021-12-06 14:35:49 -05:00
|
|
|
log_list.append(f"{header} _______________")
|
2020-12-19 16:35:53 -05:00
|
|
|
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" Raw : {raw}")
|
2020-12-19 16:35:53 -05:00
|
|
|
|
|
|
|
if packet_type:
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" Packet : {packet_type}")
|
2020-12-19 16:35:53 -05:00
|
|
|
if tocall:
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" To : {tocall}")
|
2020-12-19 16:35:53 -05:00
|
|
|
if fromcall:
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" From : {fromcall}")
|
2020-12-19 16:35:53 -05:00
|
|
|
|
|
|
|
if ack:
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" Ack : {ack}")
|
2020-12-19 16:35:53 -05:00
|
|
|
else:
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" Message : {message}")
|
2020-12-19 16:35:53 -05:00
|
|
|
if msg_num:
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" Msg # : {msg_num}")
|
2020-12-29 10:31:16 -05:00
|
|
|
if uuid:
|
2021-12-07 15:00:38 -05:00
|
|
|
log_list.append(f" UUID : {uuid}")
|
2021-12-06 14:35:49 -05:00
|
|
|
log_list.append(f"{header} _______________ Complete")
|
2020-12-19 16:35:53 -05:00
|
|
|
|
2021-12-10 10:45:51 -05:00
|
|
|
if console:
|
|
|
|
console.log("\n".join(log_list))
|
|
|
|
else:
|
|
|
|
LOG.info("\n".join(log_list))
|