mirror of
https://github.com/craigerl/aprsd.git
synced 2024-10-01 09:36:35 -04:00
Merge pull request #50 from craigerl/tcpkiss
Added the ability to use direwolf KISS socket
This commit is contained in:
commit
d243e577f0
@ -90,6 +90,11 @@ class Aprsdis(aprslib.IS):
|
|||||||
self.thread_stop = True
|
self.thread_stop = True
|
||||||
LOG.info("Shutdown Aprsdis client.")
|
LOG.info("Shutdown Aprsdis client.")
|
||||||
|
|
||||||
|
def send(self, msg):
|
||||||
|
"""Send an APRS Message object."""
|
||||||
|
line = str(msg)
|
||||||
|
self.sendall(line)
|
||||||
|
|
||||||
def _socket_readlines(self, blocking=False):
|
def _socket_readlines(self, blocking=False):
|
||||||
"""
|
"""
|
||||||
Generator for complete lines, received from the server
|
Generator for complete lines, received from the server
|
||||||
|
@ -11,7 +11,7 @@ from flask_httpauth import HTTPBasicAuth
|
|||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import messaging, packets, plugin, stats, utils
|
from aprsd import kissclient, messaging, packets, plugin, stats, utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -65,9 +65,38 @@ class APRSDFlask(flask_classful.FlaskView):
|
|||||||
plugins = pm.get_plugins()
|
plugins = pm.get_plugins()
|
||||||
plugin_count = len(plugins)
|
plugin_count = len(plugins)
|
||||||
|
|
||||||
|
if self.config["aprs"].get("enabled", True):
|
||||||
|
transport = "aprs-is"
|
||||||
|
aprs_connection = (
|
||||||
|
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
||||||
|
"{}</a>".format(stats["stats"]["aprs-is"]["server"])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# We might be connected to a KISS socket?
|
||||||
|
if kissclient.KISSClient.kiss_enabled(self.config):
|
||||||
|
transport = kissclient.KISSClient.transport(self.config)
|
||||||
|
if transport == kissclient.TRANSPORT_TCPKISS:
|
||||||
|
aprs_connection = (
|
||||||
|
"TCPKISS://{}:{}".format(
|
||||||
|
self.config["kiss"]["tcp"]["host"],
|
||||||
|
self.config["kiss"]["tcp"]["port"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif transport == kissclient.TRANSPORT_SERIALKISS:
|
||||||
|
aprs_connection = (
|
||||||
|
"SerialKISS://{}@{} baud".format(
|
||||||
|
self.config["kiss"]["serial"]["device"],
|
||||||
|
self.config["kiss"]["serial"]["baudrate"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
stats["transport"] = transport
|
||||||
|
stats["aprs_connection"] = aprs_connection
|
||||||
|
|
||||||
return flask.render_template(
|
return flask.render_template(
|
||||||
"index.html",
|
"index.html",
|
||||||
initial_stats=stats,
|
initial_stats=stats,
|
||||||
|
aprs_connection=aprs_connection,
|
||||||
callsign=self.config["aprs"]["login"],
|
callsign=self.config["aprs"]["login"],
|
||||||
version=aprsd.__version__,
|
version=aprsd.__version__,
|
||||||
config_json=json.dumps(self.config),
|
config_json=json.dumps(self.config),
|
||||||
|
152
aprsd/kissclient.py
Normal file
152
aprsd/kissclient.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from aioax25 import interface
|
||||||
|
from aioax25 import kiss as kiss
|
||||||
|
from aioax25.aprs import APRSInterface
|
||||||
|
|
||||||
|
from aprsd import trace
|
||||||
|
|
||||||
|
|
||||||
|
TRANSPORT_TCPKISS = "tcpkiss"
|
||||||
|
TRANSPORT_SERIALKISS = "serialkiss"
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class KISSClient:
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
config = None
|
||||||
|
ax25client = None
|
||||||
|
loop = None
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
"""Singleton for this class."""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
# initialize shit here
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self, config=None):
|
||||||
|
if config:
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def kiss_enabled(config):
|
||||||
|
"""Return if tcp or serial KISS is enabled."""
|
||||||
|
if "kiss" not in config:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if "serial" in config["kiss"]:
|
||||||
|
if config["kiss"]["serial"].get("enabled", False):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if "tcp" in config["kiss"]:
|
||||||
|
if config["kiss"]["tcp"].get("enabled", False):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def transport(config):
|
||||||
|
if "serial" in config["kiss"]:
|
||||||
|
if config["kiss"]["serial"].get("enabled", False):
|
||||||
|
return TRANSPORT_SERIALKISS
|
||||||
|
|
||||||
|
if "tcp" in config["kiss"]:
|
||||||
|
if config["kiss"]["tcp"].get("enabled", False):
|
||||||
|
return TRANSPORT_TCPKISS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self):
|
||||||
|
if not self.ax25client:
|
||||||
|
self.ax25client = self.setup_connection()
|
||||||
|
return self.ax25client
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Call this to fore a rebuild/reconnect."""
|
||||||
|
self.ax25client.stop()
|
||||||
|
del self.ax25client
|
||||||
|
|
||||||
|
@trace.trace
|
||||||
|
def setup_connection(self):
|
||||||
|
ax25client = Aioax25Client(self.config)
|
||||||
|
LOG.debug("Complete")
|
||||||
|
return ax25client
|
||||||
|
|
||||||
|
|
||||||
|
class Aioax25Client:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
# we can be TCP kiss or Serial kiss
|
||||||
|
|
||||||
|
self.loop = asyncio.get_event_loop()
|
||||||
|
if "serial" in self.config["kiss"] and self.config["kiss"]["serial"].get(
|
||||||
|
"enabled",
|
||||||
|
False,
|
||||||
|
):
|
||||||
|
LOG.debug(
|
||||||
|
"Setting up Serial KISS connection to {}".format(
|
||||||
|
self.config["kiss"]["serial"]["device"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.kissdev = kiss.SerialKISSDevice(
|
||||||
|
device=self.config["kiss"]["serial"]["device"],
|
||||||
|
baudrate=self.config["kiss"]["serial"].get("baudrate", 9600),
|
||||||
|
loop=self.loop,
|
||||||
|
)
|
||||||
|
elif "tcp" in self.config["kiss"] and self.config["kiss"]["tcp"].get(
|
||||||
|
"enabled",
|
||||||
|
False,
|
||||||
|
):
|
||||||
|
LOG.debug(
|
||||||
|
"Setting up KISSTCP Connection to {}:{}".format(
|
||||||
|
self.config["kiss"]["tcp"]["host"],
|
||||||
|
self.config["kiss"]["tcp"]["port"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.kissdev = kiss.TCPKISSDevice(
|
||||||
|
self.config["kiss"]["tcp"]["host"],
|
||||||
|
self.config["kiss"]["tcp"]["port"],
|
||||||
|
loop=self.loop,
|
||||||
|
log=LOG,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.kissdev.open()
|
||||||
|
self.kissport0 = self.kissdev[0]
|
||||||
|
|
||||||
|
LOG.debug("Creating AX25Interface")
|
||||||
|
self.ax25int = interface.AX25Interface(kissport=self.kissport0, loop=self.loop)
|
||||||
|
|
||||||
|
LOG.debug("Creating APRSInterface")
|
||||||
|
self.aprsint = APRSInterface(
|
||||||
|
ax25int=self.ax25int,
|
||||||
|
mycall=self.config["kiss"]["callsign"],
|
||||||
|
log=LOG,
|
||||||
|
)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
LOG.debug(self.kissdev)
|
||||||
|
self.kissdev._close()
|
||||||
|
self.loop.stop()
|
||||||
|
|
||||||
|
def consumer(self, callback, callsign=None):
|
||||||
|
if not callsign:
|
||||||
|
callsign = self.config["ham"]["callsign"]
|
||||||
|
self.aprsint.bind(callback=callback, callsign="WB4BOR", ssid=12, regex=False)
|
||||||
|
|
||||||
|
def send(self, msg):
|
||||||
|
"""Send an APRS Message object."""
|
||||||
|
payload = f"{msg._filter_for_send()}"
|
||||||
|
self.aprsint.send_message(
|
||||||
|
addressee=msg.tocall,
|
||||||
|
message=payload,
|
||||||
|
path=["WIDE1-1", "WIDE2-1"],
|
||||||
|
oneshot=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_client():
|
||||||
|
cl = KISSClient()
|
||||||
|
return cl.client
|
@ -37,7 +37,8 @@ import click_completion
|
|||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import (
|
from aprsd import (
|
||||||
client, flask, messaging, packets, plugin, stats, threads, trace, utils,
|
client, flask, kissclient, messaging, packets, plugin, stats, threads,
|
||||||
|
trace, utils,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -458,15 +459,27 @@ def server(
|
|||||||
trace.setup_tracing(["method", "api"])
|
trace.setup_tracing(["method", "api"])
|
||||||
stats.APRSDStats(config)
|
stats.APRSDStats(config)
|
||||||
|
|
||||||
|
# Create the initial PM singleton and Register plugins
|
||||||
|
plugin_manager = plugin.PluginManager(config)
|
||||||
|
plugin_manager.setup_plugins()
|
||||||
|
|
||||||
|
if config["aprs"].get("enabled", True):
|
||||||
try:
|
try:
|
||||||
cl = client.Client(config)
|
cl = client.Client(config)
|
||||||
cl.client
|
cl.client
|
||||||
except LoginError:
|
except LoginError:
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Create the initial PM singleton and Register plugins
|
rx_thread = threads.APRSDRXThread(
|
||||||
plugin_manager = plugin.PluginManager(config)
|
msg_queues=threads.msg_queues,
|
||||||
plugin_manager.setup_plugins()
|
config=config,
|
||||||
|
)
|
||||||
|
rx_thread.start()
|
||||||
|
else:
|
||||||
|
LOG.info(
|
||||||
|
"APRS network connection Not Enabled in config. This is"
|
||||||
|
" for setups without internet connectivity.",
|
||||||
|
)
|
||||||
|
|
||||||
# Now load the msgTrack from disk if any
|
# Now load the msgTrack from disk if any
|
||||||
if flush:
|
if flush:
|
||||||
@ -478,20 +491,16 @@ def server(
|
|||||||
messaging.MsgTrack().load()
|
messaging.MsgTrack().load()
|
||||||
|
|
||||||
packets.PacketList(config=config)
|
packets.PacketList(config=config)
|
||||||
|
|
||||||
rx_thread = threads.APRSDRXThread(
|
|
||||||
msg_queues=threads.msg_queues,
|
|
||||||
config=config,
|
|
||||||
)
|
|
||||||
|
|
||||||
rx_thread.start()
|
|
||||||
|
|
||||||
if "watch_list" in config["aprsd"] and config["aprsd"]["watch_list"].get(
|
|
||||||
"enabled",
|
|
||||||
True,
|
|
||||||
):
|
|
||||||
packets.WatchList(config=config)
|
packets.WatchList(config=config)
|
||||||
|
|
||||||
|
if kissclient.KISSClient.kiss_enabled(config):
|
||||||
|
kcl = kissclient.KISSClient(config=config)
|
||||||
|
# This initializes the client object.
|
||||||
|
kcl.client
|
||||||
|
|
||||||
|
kissrx_thread = threads.KISSRXThread(msg_queues=threads.msg_queues, config=config)
|
||||||
|
kissrx_thread.start()
|
||||||
|
|
||||||
messaging.MsgTrack().restart()
|
messaging.MsgTrack().restart()
|
||||||
|
|
||||||
keepalive = threads.KeepAliveThread(config=config)
|
keepalive = threads.KeepAliveThread(config=config)
|
||||||
|
@ -9,7 +9,7 @@ import re
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from aprsd import client, packets, stats, threads, trace, utils
|
from aprsd import client, kissclient, packets, stats, threads, trace, utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -18,6 +18,10 @@ LOG = logging.getLogger("APRSD")
|
|||||||
# and it's ok, but don't send a usage string back
|
# and it's ok, but don't send a usage string back
|
||||||
NULL_MESSAGE = -1
|
NULL_MESSAGE = -1
|
||||||
|
|
||||||
|
MESSAGE_TRANSPORT_TCPKISS = "tcpkiss"
|
||||||
|
MESSAGE_TRANSPORT_SERIALKISS = "serialkiss"
|
||||||
|
MESSAGE_TRANSPORT_APRSIS = "aprsis"
|
||||||
|
|
||||||
|
|
||||||
class MsgTrack:
|
class MsgTrack:
|
||||||
"""Class to keep track of outstanding text messages.
|
"""Class to keep track of outstanding text messages.
|
||||||
@ -228,7 +232,15 @@ class Message(metaclass=abc.ABCMeta):
|
|||||||
last_send_time = 0
|
last_send_time = 0
|
||||||
last_send_attempt = 0
|
last_send_attempt = 0
|
||||||
|
|
||||||
def __init__(self, fromcall, tocall, msg_id=None):
|
transport = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
fromcall,
|
||||||
|
tocall,
|
||||||
|
msg_id=None,
|
||||||
|
transport=MESSAGE_TRANSPORT_APRSIS,
|
||||||
|
):
|
||||||
self.fromcall = fromcall
|
self.fromcall = fromcall
|
||||||
self.tocall = tocall
|
self.tocall = tocall
|
||||||
if not msg_id:
|
if not msg_id:
|
||||||
@ -236,11 +248,18 @@ class Message(metaclass=abc.ABCMeta):
|
|||||||
c.increment()
|
c.increment()
|
||||||
msg_id = c.value
|
msg_id = c.value
|
||||||
self.id = msg_id
|
self.id = msg_id
|
||||||
|
self.transport = transport
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def send(self):
|
def send(self):
|
||||||
"""Child class must declare."""
|
"""Child class must declare."""
|
||||||
|
|
||||||
|
def get_transport(self):
|
||||||
|
if self.transport == MESSAGE_TRANSPORT_APRSIS:
|
||||||
|
return client.get_client()
|
||||||
|
elif self.transport == MESSAGE_TRANSPORT_TCPKISS:
|
||||||
|
return kissclient.get_client()
|
||||||
|
|
||||||
|
|
||||||
class RawMessage(Message):
|
class RawMessage(Message):
|
||||||
"""Send a raw message.
|
"""Send a raw message.
|
||||||
@ -252,8 +271,8 @@ class RawMessage(Message):
|
|||||||
|
|
||||||
message = None
|
message = None
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message, transport=MESSAGE_TRANSPORT_APRSIS):
|
||||||
super().__init__(None, None, msg_id=None)
|
super().__init__(None, None, msg_id=None, transport=transport)
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
def dict(self):
|
def dict(self):
|
||||||
@ -282,7 +301,7 @@ class RawMessage(Message):
|
|||||||
|
|
||||||
def send_direct(self):
|
def send_direct(self):
|
||||||
"""Send a message without a separate thread."""
|
"""Send a message without a separate thread."""
|
||||||
cl = client.get_client()
|
cl = self.get_transport()
|
||||||
log_message(
|
log_message(
|
||||||
"Sending Message Direct",
|
"Sending Message Direct",
|
||||||
str(self).rstrip("\n"),
|
str(self).rstrip("\n"),
|
||||||
@ -290,7 +309,7 @@ class RawMessage(Message):
|
|||||||
tocall=self.tocall,
|
tocall=self.tocall,
|
||||||
fromcall=self.fromcall,
|
fromcall=self.fromcall,
|
||||||
)
|
)
|
||||||
cl.sendall(str(self))
|
cl.send(self)
|
||||||
stats.APRSDStats().msgs_sent_inc()
|
stats.APRSDStats().msgs_sent_inc()
|
||||||
|
|
||||||
|
|
||||||
@ -299,8 +318,16 @@ class TextMessage(Message):
|
|||||||
|
|
||||||
message = None
|
message = None
|
||||||
|
|
||||||
def __init__(self, fromcall, tocall, message, msg_id=None, allow_delay=True):
|
def __init__(
|
||||||
super().__init__(fromcall, tocall, msg_id)
|
self,
|
||||||
|
fromcall,
|
||||||
|
tocall,
|
||||||
|
message,
|
||||||
|
msg_id=None,
|
||||||
|
allow_delay=True,
|
||||||
|
transport=MESSAGE_TRANSPORT_APRSIS,
|
||||||
|
):
|
||||||
|
super().__init__(fromcall, tocall, msg_id, transport=transport)
|
||||||
self.message = message
|
self.message = message
|
||||||
# do we try and save this message for later if we don't get
|
# 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.
|
# an ack? Some messages we don't want to do this ever.
|
||||||
@ -354,7 +381,7 @@ class TextMessage(Message):
|
|||||||
|
|
||||||
def send_direct(self):
|
def send_direct(self):
|
||||||
"""Send a message without a separate thread."""
|
"""Send a message without a separate thread."""
|
||||||
cl = client.get_client()
|
cl = self.get_transport()
|
||||||
log_message(
|
log_message(
|
||||||
"Sending Message Direct",
|
"Sending Message Direct",
|
||||||
str(self).rstrip("\n"),
|
str(self).rstrip("\n"),
|
||||||
@ -362,7 +389,7 @@ class TextMessage(Message):
|
|||||||
tocall=self.tocall,
|
tocall=self.tocall,
|
||||||
fromcall=self.fromcall,
|
fromcall=self.fromcall,
|
||||||
)
|
)
|
||||||
cl.sendall(str(self))
|
cl.send(self)
|
||||||
stats.APRSDStats().msgs_tx_inc()
|
stats.APRSDStats().msgs_tx_inc()
|
||||||
|
|
||||||
|
|
||||||
@ -382,7 +409,6 @@ class SendMessageThread(threads.APRSDThread):
|
|||||||
last send attempt is old enough.
|
last send attempt is old enough.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
cl = client.get_client()
|
|
||||||
tracker = MsgTrack()
|
tracker = MsgTrack()
|
||||||
# lets see if the message is still in the tracking queue
|
# lets see if the message is still in the tracking queue
|
||||||
msg = tracker.get(self.msg.id)
|
msg = tracker.get(self.msg.id)
|
||||||
@ -392,6 +418,7 @@ class SendMessageThread(threads.APRSDThread):
|
|||||||
LOG.info("Message Send Complete via Ack.")
|
LOG.info("Message Send Complete via Ack.")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
cl = msg.get_transport()
|
||||||
send_now = False
|
send_now = False
|
||||||
if msg.last_send_attempt == msg.retry_count:
|
if msg.last_send_attempt == msg.retry_count:
|
||||||
# we reached the send limit, don't send again
|
# we reached the send limit, don't send again
|
||||||
@ -422,7 +449,7 @@ class SendMessageThread(threads.APRSDThread):
|
|||||||
retry_number=msg.last_send_attempt,
|
retry_number=msg.last_send_attempt,
|
||||||
msg_num=msg.id,
|
msg_num=msg.id,
|
||||||
)
|
)
|
||||||
cl.sendall(str(msg))
|
cl.send(msg)
|
||||||
stats.APRSDStats().msgs_tx_inc()
|
stats.APRSDStats().msgs_tx_inc()
|
||||||
packets.PacketList().add(msg.dict())
|
packets.PacketList().add(msg.dict())
|
||||||
msg.last_send_time = datetime.datetime.now()
|
msg.last_send_time = datetime.datetime.now()
|
||||||
@ -436,8 +463,8 @@ class SendMessageThread(threads.APRSDThread):
|
|||||||
class AckMessage(Message):
|
class AckMessage(Message):
|
||||||
"""Class for building Acks and sending them."""
|
"""Class for building Acks and sending them."""
|
||||||
|
|
||||||
def __init__(self, fromcall, tocall, msg_id):
|
def __init__(self, fromcall, tocall, msg_id, transport=MESSAGE_TRANSPORT_APRSIS):
|
||||||
super().__init__(fromcall, tocall, msg_id=msg_id)
|
super().__init__(fromcall, tocall, msg_id=msg_id, transport=transport)
|
||||||
|
|
||||||
def dict(self):
|
def dict(self):
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
@ -463,6 +490,9 @@ class AckMessage(Message):
|
|||||||
self.id,
|
self.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _filter_for_send(self):
|
||||||
|
return f"ack{self.id}"
|
||||||
|
|
||||||
def send(self):
|
def send(self):
|
||||||
LOG.debug(f"Send ACK({self.tocall}:{self.id}) to radio.")
|
LOG.debug(f"Send ACK({self.tocall}:{self.id}) to radio.")
|
||||||
thread = SendAckThread(self)
|
thread = SendAckThread(self)
|
||||||
@ -470,7 +500,7 @@ class AckMessage(Message):
|
|||||||
|
|
||||||
def send_direct(self):
|
def send_direct(self):
|
||||||
"""Send an ack message without a separate thread."""
|
"""Send an ack message without a separate thread."""
|
||||||
cl = client.get_client()
|
cl = self.get_transport()
|
||||||
log_message(
|
log_message(
|
||||||
"Sending ack",
|
"Sending ack",
|
||||||
str(self).rstrip("\n"),
|
str(self).rstrip("\n"),
|
||||||
@ -479,7 +509,7 @@ class AckMessage(Message):
|
|||||||
tocall=self.tocall,
|
tocall=self.tocall,
|
||||||
fromcall=self.fromcall,
|
fromcall=self.fromcall,
|
||||||
)
|
)
|
||||||
cl.sendall(str(self))
|
cl.send(self)
|
||||||
|
|
||||||
|
|
||||||
class SendAckThread(threads.APRSDThread):
|
class SendAckThread(threads.APRSDThread):
|
||||||
@ -515,7 +545,7 @@ class SendAckThread(threads.APRSDThread):
|
|||||||
send_now = True
|
send_now = True
|
||||||
|
|
||||||
if send_now:
|
if send_now:
|
||||||
cl = client.get_client()
|
cl = self.ack.get_transport()
|
||||||
log_message(
|
log_message(
|
||||||
"Sending ack",
|
"Sending ack",
|
||||||
str(self.ack).rstrip("\n"),
|
str(self.ack).rstrip("\n"),
|
||||||
@ -524,7 +554,7 @@ class SendAckThread(threads.APRSDThread):
|
|||||||
tocall=self.ack.tocall,
|
tocall=self.ack.tocall,
|
||||||
retry_number=self.ack.last_send_attempt,
|
retry_number=self.ack.last_send_attempt,
|
||||||
)
|
)
|
||||||
cl.sendall(str(self.ack))
|
cl.send(self.ack)
|
||||||
stats.APRSDStats().ack_tx_inc()
|
stats.APRSDStats().ack_tx_inc()
|
||||||
packets.PacketList().add(self.ack.dict())
|
packets.PacketList().add(self.ack.dict())
|
||||||
self.ack.last_send_attempt += 1
|
self.ack.last_send_attempt += 1
|
||||||
|
@ -8,7 +8,7 @@ import tracemalloc
|
|||||||
|
|
||||||
import aprslib
|
import aprslib
|
||||||
|
|
||||||
from aprsd import client, messaging, packets, plugin, stats, utils
|
from aprsd import client, kissclient, messaging, packets, plugin, stats, utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -180,9 +180,10 @@ class APRSDRXThread(APRSDThread):
|
|||||||
|
|
||||||
class APRSDProcessPacketThread(APRSDThread):
|
class APRSDProcessPacketThread(APRSDThread):
|
||||||
|
|
||||||
def __init__(self, packet, config):
|
def __init__(self, packet, config, transport="aprsis"):
|
||||||
self.packet = packet
|
self.packet = packet
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.transport = transport
|
||||||
name = self.packet["raw"][:10]
|
name = self.packet["raw"][:10]
|
||||||
super().__init__(f"RX_PACKET-{name}")
|
super().__init__(f"RX_PACKET-{name}")
|
||||||
|
|
||||||
@ -237,6 +238,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
self.config["aprs"]["login"],
|
self.config["aprs"]["login"],
|
||||||
fromcall,
|
fromcall,
|
||||||
msg_id=msg_id,
|
msg_id=msg_id,
|
||||||
|
transport=self.transport,
|
||||||
)
|
)
|
||||||
ack.send()
|
ack.send()
|
||||||
|
|
||||||
@ -255,6 +257,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
self.config["aprs"]["login"],
|
self.config["aprs"]["login"],
|
||||||
fromcall,
|
fromcall,
|
||||||
subreply,
|
subreply,
|
||||||
|
transport=self.transport,
|
||||||
)
|
)
|
||||||
msg.send()
|
msg.send()
|
||||||
|
|
||||||
@ -271,6 +274,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
self.config["aprs"]["login"],
|
self.config["aprs"]["login"],
|
||||||
fromcall,
|
fromcall,
|
||||||
reply,
|
reply,
|
||||||
|
transport=self.transport,
|
||||||
)
|
)
|
||||||
msg.send()
|
msg.send()
|
||||||
|
|
||||||
@ -283,6 +287,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
self.config["aprs"]["login"],
|
self.config["aprs"]["login"],
|
||||||
fromcall,
|
fromcall,
|
||||||
reply,
|
reply,
|
||||||
|
transport=self.transport,
|
||||||
)
|
)
|
||||||
msg.send()
|
msg.send()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@ -294,6 +299,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
self.config["aprs"]["login"],
|
self.config["aprs"]["login"],
|
||||||
fromcall,
|
fromcall,
|
||||||
reply,
|
reply,
|
||||||
|
transport=self.transport,
|
||||||
)
|
)
|
||||||
msg.send()
|
msg.send()
|
||||||
|
|
||||||
@ -314,3 +320,66 @@ class APRSDTXThread(APRSDThread):
|
|||||||
pass
|
pass
|
||||||
# Continue to loop
|
# Continue to loop
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class KISSRXThread(APRSDThread):
|
||||||
|
"""Thread that connects to direwolf's TCPKISS interface.
|
||||||
|
|
||||||
|
All Packets are processed and sent back out the direwolf
|
||||||
|
interface instead of the aprs-is server.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, msg_queues, config):
|
||||||
|
super().__init__("KISSRX_MSG")
|
||||||
|
self.msg_queues = msg_queues
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.thread_stop = True
|
||||||
|
kissclient.get_client().stop()
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
kiss_client = kissclient.get_client()
|
||||||
|
|
||||||
|
# setup the consumer of messages and block until a messages
|
||||||
|
try:
|
||||||
|
# This will register a packet consumer with aprslib
|
||||||
|
# When new packets come in the consumer will process
|
||||||
|
# the packet
|
||||||
|
|
||||||
|
# Do a partial here because the consumer signature doesn't allow
|
||||||
|
# For kwargs to be passed in to the consumer func we declare
|
||||||
|
# and the aprslib developer didn't want to allow a PR to add
|
||||||
|
# kwargs. :(
|
||||||
|
# https://github.com/rossengeorgiev/aprs-python/pull/56
|
||||||
|
kiss_client.consumer(self.process_packet, callsign=self.config["kiss"]["callsign"])
|
||||||
|
kiss_client.loop.run_forever()
|
||||||
|
|
||||||
|
except aprslib.exceptions.ConnectionDrop:
|
||||||
|
LOG.error("Connection dropped, reconnecting")
|
||||||
|
time.sleep(5)
|
||||||
|
# Force the deletion of the client object connected to aprs
|
||||||
|
# This will cause a reconnect, next time client.get_client()
|
||||||
|
# is called
|
||||||
|
client.Client().reset()
|
||||||
|
# Continue to loop
|
||||||
|
|
||||||
|
def process_packet(self, interface, frame):
|
||||||
|
"""Process a packet recieved from aprs-is server."""
|
||||||
|
|
||||||
|
LOG.debug(f"Got an APRS Frame '{frame}'")
|
||||||
|
# try and nuke the * from the fromcall sign.
|
||||||
|
frame.header._source._ch = False
|
||||||
|
payload = str(frame.payload.decode())
|
||||||
|
msg = f"{str(frame.header)}:{payload}"
|
||||||
|
LOG.debug(f"Decoding {msg}")
|
||||||
|
|
||||||
|
packet = aprslib.parse(msg)
|
||||||
|
LOG.debug(packet)
|
||||||
|
thread = APRSDProcessPacketThread(
|
||||||
|
packet=packet, config=self.config,
|
||||||
|
transport=messaging.MESSAGE_TRANSPORT_TCPKISS,
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
return
|
||||||
|
@ -37,11 +37,24 @@ DEFAULT_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
|
|||||||
DEFAULT_CONFIG_DICT = {
|
DEFAULT_CONFIG_DICT = {
|
||||||
"ham": {"callsign": "NOCALL"},
|
"ham": {"callsign": "NOCALL"},
|
||||||
"aprs": {
|
"aprs": {
|
||||||
"login": "NOCALL",
|
"enabled": True,
|
||||||
|
"login": "CALLSIGN",
|
||||||
"password": "00000",
|
"password": "00000",
|
||||||
"host": "rotate.aprs2.net",
|
"host": "rotate.aprs2.net",
|
||||||
"port": 14580,
|
"port": 14580,
|
||||||
},
|
},
|
||||||
|
"kiss": {
|
||||||
|
"tcp": {
|
||||||
|
"enabled": False,
|
||||||
|
"host": "direwolf.ip.address",
|
||||||
|
"port": "8001",
|
||||||
|
},
|
||||||
|
"serial": {
|
||||||
|
"enabled": False,
|
||||||
|
"device": "/dev/ttyS0",
|
||||||
|
"baudrate": 9600,
|
||||||
|
},
|
||||||
|
},
|
||||||
"aprsd": {
|
"aprsd": {
|
||||||
"logfile": "/tmp/aprsd.log",
|
"logfile": "/tmp/aprsd.log",
|
||||||
"logformat": DEFAULT_LOG_FORMAT,
|
"logformat": DEFAULT_LOG_FORMAT,
|
||||||
@ -172,6 +185,9 @@ def add_config_comments(raw_yaml):
|
|||||||
# lets insert a comment
|
# lets insert a comment
|
||||||
raw_yaml = insert_str(
|
raw_yaml = insert_str(
|
||||||
raw_yaml,
|
raw_yaml,
|
||||||
|
"\n # Set enabled to False if there is no internet connectivity."
|
||||||
|
"\n # This is useful for a direwolf KISS aprs connection only. "
|
||||||
|
"\n"
|
||||||
"\n # Get the passcode for your callsign here: "
|
"\n # Get the passcode for your callsign here: "
|
||||||
"\n # https://apps.magicbug.co.uk/passcode",
|
"\n # https://apps.magicbug.co.uk/passcode",
|
||||||
end_idx,
|
end_idx,
|
||||||
|
@ -220,7 +220,7 @@ function updateQuadData(chart, label, first, second, third, fourth) {
|
|||||||
|
|
||||||
function update_stats( data ) {
|
function update_stats( data ) {
|
||||||
$("#version").text( data["stats"]["aprsd"]["version"] );
|
$("#version").text( data["stats"]["aprsd"]["version"] );
|
||||||
$("#aprsis").html( "APRS-IS Server: <a href='http://status.aprs2.net' >" + data["stats"]["aprs-is"]["server"] + "</a>" );
|
$("#aprs_connection").html( data["aprs_connection"] );
|
||||||
$("#uptime").text( "uptime: " + data["stats"]["aprsd"]["uptime"] );
|
$("#uptime").text( "uptime: " + data["stats"]["aprsd"]["uptime"] );
|
||||||
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
|
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
|
||||||
$("#jsonstats").html(html_pretty);
|
$("#jsonstats").html(html_pretty);
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<div class='left floated ten wide column'>
|
<div class='left floated ten wide column'>
|
||||||
<span style='color: green'>{{ callsign }}</span>
|
<span style='color: green'>{{ callsign }}</span>
|
||||||
connected to
|
connected to
|
||||||
<span style='color: blue' id='aprsis'>NONE</span>
|
<span style='color: blue' id='aprs_connection'>{{ aprs_connection|safe }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='right floated four wide column'>
|
<div class='right floated four wide column'>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
aioax25>=0.0.10
|
||||||
aprslib
|
aprslib
|
||||||
click
|
click
|
||||||
click-completion
|
click-completion
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#
|
#
|
||||||
# pip-compile requirements.in
|
# pip-compile requirements.in
|
||||||
#
|
#
|
||||||
|
aioax25==0.0.10
|
||||||
|
# via -r requirements.in
|
||||||
aprslib==0.6.47
|
aprslib==0.6.47
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
backoff==1.10.0
|
backoff==1.10.0
|
||||||
@ -21,6 +23,8 @@ click==7.1.2
|
|||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# click-completion
|
# click-completion
|
||||||
# flask
|
# flask
|
||||||
|
contexter==0.1.4
|
||||||
|
# via signalslot
|
||||||
cryptography==3.4.7
|
cryptography==3.4.7
|
||||||
# via pyopenssl
|
# via pyopenssl
|
||||||
dnspython==2.1.0
|
dnspython==2.1.0
|
||||||
@ -72,6 +76,8 @@ pycparser==2.20
|
|||||||
# via cffi
|
# via cffi
|
||||||
pyopenssl==20.0.1
|
pyopenssl==20.0.1
|
||||||
# via opencage
|
# via opencage
|
||||||
|
pyserial==3.5
|
||||||
|
# via aioax25
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
# via pandas
|
# via pandas
|
||||||
pytz==2021.1
|
pytz==2021.1
|
||||||
@ -88,6 +94,8 @@ requests==2.25.1
|
|||||||
# yfinance
|
# yfinance
|
||||||
shellingham==1.4.0
|
shellingham==1.4.0
|
||||||
# via click-completion
|
# via click-completion
|
||||||
|
signalslot==0.1.2
|
||||||
|
# via aioax25
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
@ -96,12 +104,15 @@ six==1.15.0
|
|||||||
# opencage
|
# opencage
|
||||||
# pyopenssl
|
# pyopenssl
|
||||||
# python-dateutil
|
# python-dateutil
|
||||||
|
# signalslot
|
||||||
thesmuggler==1.0.1
|
thesmuggler==1.0.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
update-checker==0.18.0
|
update-checker==0.18.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
urllib3==1.26.5
|
urllib3==1.26.5
|
||||||
# via requests
|
# via requests
|
||||||
|
weakrefmethod==1.0.3
|
||||||
|
# via signalslot
|
||||||
werkzeug==1.0.1
|
werkzeug==1.0.1
|
||||||
# via flask
|
# via flask
|
||||||
yfinance==0.1.59
|
yfinance==0.1.59
|
||||||
|
Loading…
Reference in New Issue
Block a user