mirror of
https://github.com/craigerl/aprsd.git
synced 2024-09-27 15:46:53 -04:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
a74a66d9c3 | |||
a5dc322066 | |||
9b843eead9 | |||
e5662b95f8 | |||
a6f84e42bc | |||
e3ab6e7f59 | |||
af3d741833 | |||
b172c6dbde | |||
9d3f45ac30 | |||
49e8a622a7 | |||
92cb92f89c | |||
37415557b5 | |||
5ebbb52a2c | |||
673b34c78b | |||
ffa28fa28a | |||
93f752cd6d | |||
b5aa187d54 | |||
616cd69a2d | |||
4b26e2b7f7 | |||
f07ef71ce0 | |||
|
ee0c546231 | ||
|
ba4d9bb565 | ||
|
6d294113f8 | ||
8f1733e493 | |||
f7a9f7aaab | |||
1828342ef2 | |||
b317d0eb63 | |||
63962acfe6 | |||
44a72e813e | |||
afeb11a085 | |||
|
18fb2a9e2b | ||
fa2d2d965d | |||
2abf8bc750 | |||
f15974131c | |||
4d1dfadbde | |||
93a9cce0c0 | |||
|
321260ff7a |
3
.github/workflows/manual_build.yml
vendored
3
.github/workflows/manual_build.yml
vendored
@ -43,8 +43,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: "{{defaultContext}}:docker"
|
context: "{{defaultContext}}:docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
file: ./Dockerfile-dev
|
file: ./Dockerfile
|
||||||
build-args: |
|
build-args: |
|
||||||
|
INSTALL_TYPE=github
|
||||||
BRANCH=${{ steps.extract_branch.outputs.branch }}
|
BRANCH=${{ steps.extract_branch.outputs.branch }}
|
||||||
BUILDX_QEMU_ENV=true
|
BUILDX_QEMU_ENV=true
|
||||||
push: true
|
push: true
|
||||||
|
5
.github/workflows/master-build.yml
vendored
5
.github/workflows/master-build.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11"]
|
python-version: ["3.10", "3.11"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
@ -53,8 +53,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: "{{defaultContext}}:docker"
|
context: "{{defaultContext}}:docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
file: ./Dockerfile-dev
|
file: ./Dockerfile
|
||||||
build-args: |
|
build-args: |
|
||||||
|
INSTALL_TYPE=github
|
||||||
BRANCH=${{ steps.branch-name.outputs.current_branch }}
|
BRANCH=${{ steps.branch-name.outputs.current_branch }}
|
||||||
BUILDX_QEMU_ENV=true
|
BUILDX_QEMU_ENV=true
|
||||||
push: true
|
push: true
|
||||||
|
2
.github/workflows/python.yml
vendored
2
.github/workflows/python.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11"]
|
python-version: ["3.10", "3.11"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
1171
ChangeLog.md
Normal file
1171
ChangeLog.md
Normal file
File diff suppressed because it is too large
Load Diff
22
Makefile
22
Makefile
@ -17,14 +17,19 @@ Makefile.venv:
|
|||||||
help: # Help for the Makefile
|
help: # Help for the Makefile
|
||||||
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
dev: REQUIREMENTS_TXT = requirements.txt dev-requirements.txt
|
dev: REQUIREMENTS_TXT = requirements.txt requirements-dev.txt
|
||||||
dev: venv ## Create a python virtual environment for development of aprsd
|
dev: venv ## Create a python virtual environment for development of aprsd
|
||||||
|
|
||||||
run: venv ## Create a virtual environment for running aprsd commands
|
run: venv ## Create a virtual environment for running aprsd commands
|
||||||
|
|
||||||
docs: dev
|
changelog: dev
|
||||||
|
npm i -g auto-changelog
|
||||||
|
auto-changelog -l false -o ChangeLog.md
|
||||||
|
|
||||||
|
docs: changelog
|
||||||
|
m2r --overwrite ChangeLog.md
|
||||||
cp README.rst docs/readme.rst
|
cp README.rst docs/readme.rst
|
||||||
cp Changelog docs/changelog.rst
|
mv ChangeLog.rst docs/changelog.rst
|
||||||
tox -edocs
|
tox -edocs
|
||||||
|
|
||||||
clean: clean-build clean-pyc clean-test clean-dev ## remove all build, test, coverage and Python artifacts
|
clean: clean-build clean-pyc clean-test clean-dev ## remove all build, test, coverage and Python artifacts
|
||||||
@ -39,7 +44,6 @@ clean-build: ## remove build artifacts
|
|||||||
clean-pyc: ## remove Python file artifacts
|
clean-pyc: ## remove Python file artifacts
|
||||||
find . -name '*.pyc' -exec rm -f {} +
|
find . -name '*.pyc' -exec rm -f {} +
|
||||||
find . -name '*.pyo' -exec rm -f {} +
|
find . -name '*.pyo' -exec rm -f {} +
|
||||||
find . -name '*~' -exec rm -f {} +
|
|
||||||
find . -name '__pycache__' -exec rm -fr {} +
|
find . -name '__pycache__' -exec rm -fr {} +
|
||||||
|
|
||||||
clean-test: ## remove test and coverage artifacts
|
clean-test: ## remove test and coverage artifacts
|
||||||
@ -55,9 +59,9 @@ clean-dev:
|
|||||||
test: dev ## Run all the tox tests
|
test: dev ## Run all the tox tests
|
||||||
tox -p all
|
tox -p all
|
||||||
|
|
||||||
build: test ## Make the build artifact prior to doing an upload
|
build: test changelog ## Make the build artifact prior to doing an upload
|
||||||
$(VENV)/pip install twine
|
$(VENV)/pip install twine
|
||||||
$(VENV)/python3 setup.py sdist bdist_wheel
|
$(VENV)/python3 -m build
|
||||||
$(VENV)/twine check dist/*
|
$(VENV)/twine check dist/*
|
||||||
|
|
||||||
upload: build ## Upload a new version of the plugin
|
upload: build ## Upload a new version of the plugin
|
||||||
@ -81,8 +85,8 @@ docker-dev: test ## Make a development docker container tagged with hemna6969/a
|
|||||||
|
|
||||||
update-requirements: dev ## Update the requirements.txt and dev-requirements.txt files
|
update-requirements: dev ## Update the requirements.txt and dev-requirements.txt files
|
||||||
rm requirements.txt
|
rm requirements.txt
|
||||||
rm dev-requirements.txt
|
rm requirements-dev.txt
|
||||||
touch requirements.txt
|
touch requirements.txt
|
||||||
touch dev-requirements.txt
|
touch requirements-dev.txt
|
||||||
$(VENV)/pip-compile --resolver backtracking --annotation-style=line requirements.in
|
$(VENV)/pip-compile --resolver backtracking --annotation-style=line requirements.in
|
||||||
$(VENV)/pip-compile --resolver backtracking --annotation-style=line dev-requirements.in
|
$(VENV)/pip-compile --resolver backtracking --annotation-style=line requirements-dev.in
|
||||||
|
@ -69,6 +69,7 @@ Help
|
|||||||
====
|
====
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
||||||
└─> aprsd -h
|
└─> aprsd -h
|
||||||
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
@ -78,9 +79,11 @@ Help
|
|||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
check-version Check this version against the latest in pypi.org.
|
check-version Check this version against the latest in pypi.org.
|
||||||
completion Click Completion subcommands
|
completion Show the shell completion code
|
||||||
dev Development type subcommands
|
dev Development type subcommands
|
||||||
|
fetch-stats Fetch stats from a APRSD admin web interface.
|
||||||
healthcheck Check the health of the running aprsd server.
|
healthcheck Check the health of the running aprsd server.
|
||||||
|
list-extensions List the built in plugins available to APRSD.
|
||||||
list-plugins List the built in plugins available to APRSD.
|
list-plugins List the built in plugins available to APRSD.
|
||||||
listen Listen to packets on the APRS-IS Network based on FILTER.
|
listen Listen to packets on the APRS-IS Network based on FILTER.
|
||||||
sample-config Generate a sample Config file from aprsd and all...
|
sample-config Generate a sample Config file from aprsd and all...
|
||||||
@ -90,7 +93,6 @@ Help
|
|||||||
webchat Web based HAM Radio chat program!
|
webchat Web based HAM Radio chat program!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Commands
|
Commands
|
||||||
========
|
========
|
||||||
|
|
||||||
|
@ -10,7 +10,10 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import pbr.version
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
|
|
||||||
|
|
||||||
__version__ = pbr.version.VersionInfo("aprsd").version_string()
|
try:
|
||||||
|
__version__ = version("aprsd")
|
||||||
|
except PackageNotFoundError:
|
||||||
|
pass
|
||||||
|
461
aprsd/client.py
461
aprsd/client.py
@ -1,461 +0,0 @@
|
|||||||
import abc
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
import aprslib
|
|
||||||
from aprslib.exceptions import LoginError
|
|
||||||
from oslo_config import cfg
|
|
||||||
import wrapt
|
|
||||||
|
|
||||||
from aprsd import exception
|
|
||||||
from aprsd.clients import aprsis, fake, kiss
|
|
||||||
from aprsd.packets import core
|
|
||||||
from aprsd.utils import singleton, trace
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
LOG = logging.getLogger("APRSD")
|
|
||||||
TRANSPORT_APRSIS = "aprsis"
|
|
||||||
TRANSPORT_TCPKISS = "tcpkiss"
|
|
||||||
TRANSPORT_SERIALKISS = "serialkiss"
|
|
||||||
TRANSPORT_FAKE = "fake"
|
|
||||||
|
|
||||||
# Main must create this from the ClientFactory
|
|
||||||
# object such that it's populated with the
|
|
||||||
# Correct config
|
|
||||||
factory = None
|
|
||||||
|
|
||||||
|
|
||||||
@singleton
|
|
||||||
class APRSClientStats:
|
|
||||||
|
|
||||||
lock = threading.Lock()
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def stats(self, serializable=False):
|
|
||||||
client = factory.create()
|
|
||||||
stats = {
|
|
||||||
"transport": client.transport(),
|
|
||||||
"filter": client.filter,
|
|
||||||
"connected": client.connected,
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.transport() == TRANSPORT_APRSIS:
|
|
||||||
stats["server_string"] = client.client.server_string
|
|
||||||
keepalive = client.client.aprsd_keepalive
|
|
||||||
if serializable:
|
|
||||||
keepalive = keepalive.isoformat()
|
|
||||||
stats["server_keepalive"] = keepalive
|
|
||||||
elif client.transport() == TRANSPORT_TCPKISS:
|
|
||||||
stats["host"] = CONF.kiss_tcp.host
|
|
||||||
stats["port"] = CONF.kiss_tcp.port
|
|
||||||
elif client.transport() == TRANSPORT_SERIALKISS:
|
|
||||||
stats["device"] = CONF.kiss_serial.device
|
|
||||||
return stats
|
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
|
||||||
"""Singleton client class that constructs the aprslib connection."""
|
|
||||||
|
|
||||||
_instance = None
|
|
||||||
_client = None
|
|
||||||
|
|
||||||
connected = False
|
|
||||||
filter = None
|
|
||||||
lock = threading.Lock()
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
"""This magic turns this into a singleton."""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super().__new__(cls)
|
|
||||||
# Put any initialization here.
|
|
||||||
cls._instance._create_client()
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def stats(self) -> dict:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_filter(self, filter):
|
|
||||||
self.filter = filter
|
|
||||||
if self._client:
|
|
||||||
self._client.set_filter(filter)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def client(self):
|
|
||||||
if not self._client:
|
|
||||||
self._create_client()
|
|
||||||
return self._client
|
|
||||||
|
|
||||||
def _create_client(self):
|
|
||||||
self._client = self.setup_connection()
|
|
||||||
if self.filter:
|
|
||||||
LOG.info("Creating APRS client filter")
|
|
||||||
self._client.set_filter(self.filter)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
if self._client:
|
|
||||||
LOG.info("Stopping client connection.")
|
|
||||||
self._client.stop()
|
|
||||||
|
|
||||||
def send(self, packet: core.Packet):
|
|
||||||
"""Send a packet to the network."""
|
|
||||||
self.client.send(packet)
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def reset(self):
|
|
||||||
"""Call this to force a rebuild/reconnect."""
|
|
||||||
LOG.info("Resetting client connection.")
|
|
||||||
if self._client:
|
|
||||||
self._client.close()
|
|
||||||
del self._client
|
|
||||||
self._create_client()
|
|
||||||
else:
|
|
||||||
LOG.warning("Client not initialized, nothing to reset.")
|
|
||||||
|
|
||||||
# Recreate the client
|
|
||||||
LOG.info(f"Creating new client {self.client}")
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def setup_connection(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@abc.abstractmethod
|
|
||||||
def is_enabled():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@abc.abstractmethod
|
|
||||||
def transport():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def decode_packet(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def is_alive(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class APRSISClient(Client):
|
|
||||||
|
|
||||||
_client = None
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
|
|
||||||
self.max_delta = datetime.timedelta(**max_timeout)
|
|
||||||
|
|
||||||
def stats(self) -> dict:
|
|
||||||
stats = {}
|
|
||||||
if self.is_configured():
|
|
||||||
stats = {
|
|
||||||
"server_string": self._client.server_string,
|
|
||||||
"sever_keepalive": self._client.aprsd_keepalive,
|
|
||||||
"filter": self.filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_enabled():
|
|
||||||
# Defaults to True if the enabled flag is non existent
|
|
||||||
try:
|
|
||||||
return CONF.aprs_network.enabled
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_configured():
|
|
||||||
if APRSISClient.is_enabled():
|
|
||||||
# Ensure that the config vars are correctly set
|
|
||||||
if not CONF.aprs_network.login:
|
|
||||||
LOG.error("Config aprs_network.login not set.")
|
|
||||||
raise exception.MissingConfigOptionException(
|
|
||||||
"aprs_network.login is not set.",
|
|
||||||
)
|
|
||||||
if not CONF.aprs_network.password:
|
|
||||||
LOG.error("Config aprs_network.password not set.")
|
|
||||||
raise exception.MissingConfigOptionException(
|
|
||||||
"aprs_network.password is not set.",
|
|
||||||
)
|
|
||||||
if not CONF.aprs_network.host:
|
|
||||||
LOG.error("Config aprs_network.host not set.")
|
|
||||||
raise exception.MissingConfigOptionException(
|
|
||||||
"aprs_network.host is not set.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _is_stale_connection(self):
|
|
||||||
delta = datetime.datetime.now() - self._client.aprsd_keepalive
|
|
||||||
if delta > self.max_delta:
|
|
||||||
LOG.error(f"Connection is stale, last heard {delta} ago.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_alive(self):
|
|
||||||
if self._client:
|
|
||||||
return self._client.is_alive() and not self._is_stale_connection()
|
|
||||||
else:
|
|
||||||
LOG.warning(f"APRS_CLIENT {self._client} alive? NO!!!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._client:
|
|
||||||
self._client.stop()
|
|
||||||
self._client.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def transport():
|
|
||||||
return TRANSPORT_APRSIS
|
|
||||||
|
|
||||||
def decode_packet(self, *args, **kwargs):
|
|
||||||
"""APRS lib already decodes this."""
|
|
||||||
return core.factory(args[0])
|
|
||||||
|
|
||||||
def setup_connection(self):
|
|
||||||
user = CONF.aprs_network.login
|
|
||||||
password = CONF.aprs_network.password
|
|
||||||
host = CONF.aprs_network.host
|
|
||||||
port = CONF.aprs_network.port
|
|
||||||
self.connected = False
|
|
||||||
backoff = 1
|
|
||||||
aprs_client = None
|
|
||||||
while not self.connected:
|
|
||||||
try:
|
|
||||||
LOG.info(f"Creating aprslib client({host}:{port}) and logging in {user}.")
|
|
||||||
aprs_client = aprsis.Aprsdis(user, passwd=password, host=host, port=port)
|
|
||||||
# Force the log to be the same
|
|
||||||
aprs_client.logger = LOG
|
|
||||||
aprs_client.connect()
|
|
||||||
self.connected = True
|
|
||||||
backoff = 1
|
|
||||||
except LoginError as e:
|
|
||||||
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
|
|
||||||
self.connected = False
|
|
||||||
time.sleep(backoff)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
|
|
||||||
self.connected = False
|
|
||||||
time.sleep(backoff)
|
|
||||||
# Don't allow the backoff to go to inifinity.
|
|
||||||
if backoff > 5:
|
|
||||||
backoff = 5
|
|
||||||
else:
|
|
||||||
backoff += 1
|
|
||||||
continue
|
|
||||||
self._client = aprs_client
|
|
||||||
return aprs_client
|
|
||||||
|
|
||||||
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
|
||||||
self._client.consumer(
|
|
||||||
callback, blocking=blocking,
|
|
||||||
immortal=immortal, raw=raw,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KISSClient(Client):
|
|
||||||
|
|
||||||
_client = None
|
|
||||||
|
|
||||||
def stats(self) -> dict:
|
|
||||||
stats = {}
|
|
||||||
if self.is_configured():
|
|
||||||
return {
|
|
||||||
"transport": self.transport(),
|
|
||||||
}
|
|
||||||
return stats
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_enabled():
|
|
||||||
"""Return if tcp or serial KISS is enabled."""
|
|
||||||
if CONF.kiss_serial.enabled:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if CONF.kiss_tcp.enabled:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_configured():
|
|
||||||
# Ensure that the config vars are correctly set
|
|
||||||
if KISSClient.is_enabled():
|
|
||||||
transport = KISSClient.transport()
|
|
||||||
if transport == TRANSPORT_SERIALKISS:
|
|
||||||
if not CONF.kiss_serial.device:
|
|
||||||
LOG.error("KISS serial enabled, but no device is set.")
|
|
||||||
raise exception.MissingConfigOptionException(
|
|
||||||
"kiss_serial.device is not set.",
|
|
||||||
)
|
|
||||||
elif transport == TRANSPORT_TCPKISS:
|
|
||||||
if not CONF.kiss_tcp.host:
|
|
||||||
LOG.error("KISS TCP enabled, but no host is set.")
|
|
||||||
raise exception.MissingConfigOptionException(
|
|
||||||
"kiss_tcp.host is not set.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_alive(self):
|
|
||||||
if self._client:
|
|
||||||
return self._client.is_alive()
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._client:
|
|
||||||
self._client.stop()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def transport():
|
|
||||||
if CONF.kiss_serial.enabled:
|
|
||||||
return TRANSPORT_SERIALKISS
|
|
||||||
|
|
||||||
if CONF.kiss_tcp.enabled:
|
|
||||||
return TRANSPORT_TCPKISS
|
|
||||||
|
|
||||||
def decode_packet(self, *args, **kwargs):
|
|
||||||
"""We get a frame, which has to be decoded."""
|
|
||||||
LOG.debug(f"kwargs {kwargs}")
|
|
||||||
frame = kwargs["frame"]
|
|
||||||
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}"
|
|
||||||
# msg = frame.tnc2
|
|
||||||
# LOG.debug(f"Decoding {msg}")
|
|
||||||
|
|
||||||
raw = aprslib.parse(str(frame))
|
|
||||||
packet = core.factory(raw)
|
|
||||||
if isinstance(packet, core.ThirdParty):
|
|
||||||
return packet.subpacket
|
|
||||||
else:
|
|
||||||
return packet
|
|
||||||
|
|
||||||
def setup_connection(self):
|
|
||||||
self._client = kiss.KISS3Client()
|
|
||||||
self.connected = True
|
|
||||||
return self._client
|
|
||||||
|
|
||||||
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
|
||||||
self._client.consumer(callback)
|
|
||||||
|
|
||||||
|
|
||||||
class APRSDFakeClient(Client, metaclass=trace.TraceWrapperMetaclass):
|
|
||||||
|
|
||||||
def stats(self) -> dict:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_enabled():
|
|
||||||
if CONF.fake_client.enabled:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_configured():
|
|
||||||
return APRSDFakeClient.is_enabled()
|
|
||||||
|
|
||||||
def is_alive(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setup_connection(self):
|
|
||||||
self.connected = True
|
|
||||||
return fake.APRSDFakeClient()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def transport():
|
|
||||||
return TRANSPORT_FAKE
|
|
||||||
|
|
||||||
def decode_packet(self, *args, **kwargs):
|
|
||||||
LOG.debug(f"kwargs {kwargs}")
|
|
||||||
pkt = kwargs["packet"]
|
|
||||||
LOG.debug(f"Got an APRS Fake Packet '{pkt}'")
|
|
||||||
return pkt
|
|
||||||
|
|
||||||
|
|
||||||
class ClientFactory:
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
"""This magic turns this into a singleton."""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super().__new__(cls)
|
|
||||||
# Put any initialization here.
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._builders = {}
|
|
||||||
|
|
||||||
def register(self, key, builder):
|
|
||||||
self._builders[key] = builder
|
|
||||||
|
|
||||||
def create(self, key=None):
|
|
||||||
if not key:
|
|
||||||
if APRSISClient.is_enabled():
|
|
||||||
key = TRANSPORT_APRSIS
|
|
||||||
elif KISSClient.is_enabled():
|
|
||||||
key = KISSClient.transport()
|
|
||||||
elif APRSDFakeClient.is_enabled():
|
|
||||||
key = TRANSPORT_FAKE
|
|
||||||
|
|
||||||
builder = self._builders.get(key)
|
|
||||||
if not builder:
|
|
||||||
raise ValueError(key)
|
|
||||||
return builder()
|
|
||||||
|
|
||||||
def is_client_enabled(self):
|
|
||||||
"""Make sure at least one client is enabled."""
|
|
||||||
enabled = False
|
|
||||||
for key in self._builders.keys():
|
|
||||||
try:
|
|
||||||
enabled |= self._builders[key].is_enabled()
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return enabled
|
|
||||||
|
|
||||||
def is_client_configured(self):
|
|
||||||
enabled = False
|
|
||||||
for key in self._builders.keys():
|
|
||||||
try:
|
|
||||||
enabled |= self._builders[key].is_configured()
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
except exception.MissingConfigOptionException as ex:
|
|
||||||
LOG.error(ex.message)
|
|
||||||
return False
|
|
||||||
except exception.ConfigOptionBogusDefaultException as ex:
|
|
||||||
LOG.error(ex.message)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return enabled
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def setup():
|
|
||||||
"""Create and register all possible client objects."""
|
|
||||||
global factory
|
|
||||||
|
|
||||||
factory = ClientFactory()
|
|
||||||
factory.register(TRANSPORT_APRSIS, APRSISClient)
|
|
||||||
factory.register(TRANSPORT_TCPKISS, KISSClient)
|
|
||||||
factory.register(TRANSPORT_SERIALKISS, KISSClient)
|
|
||||||
factory.register(TRANSPORT_FAKE, APRSDFakeClient)
|
|
13
aprsd/client/__init__.py
Normal file
13
aprsd/client/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from aprsd.client import aprsis, factory, fake, kiss
|
||||||
|
|
||||||
|
|
||||||
|
TRANSPORT_APRSIS = "aprsis"
|
||||||
|
TRANSPORT_TCPKISS = "tcpkiss"
|
||||||
|
TRANSPORT_SERIALKISS = "serialkiss"
|
||||||
|
TRANSPORT_FAKE = "fake"
|
||||||
|
|
||||||
|
|
||||||
|
client_factory = factory.ClientFactory()
|
||||||
|
client_factory.register(aprsis.APRSISClient)
|
||||||
|
client_factory.register(kiss.KISSClient)
|
||||||
|
client_factory.register(fake.APRSDFakeClient)
|
132
aprsd/client/aprsis.py
Normal file
132
aprsd/client/aprsis.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from aprslib.exceptions import LoginError
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import client, exception
|
||||||
|
from aprsd.client import base
|
||||||
|
from aprsd.client.drivers import aprsis
|
||||||
|
from aprsd.packets import core
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class APRSISClient(base.APRSClient):
|
||||||
|
|
||||||
|
_client = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
|
||||||
|
self.max_delta = datetime.timedelta(**max_timeout)
|
||||||
|
|
||||||
|
def stats(self) -> dict:
|
||||||
|
stats = {}
|
||||||
|
if self.is_configured():
|
||||||
|
stats = {
|
||||||
|
"server_string": self._client.server_string,
|
||||||
|
"sever_keepalive": self._client.aprsd_keepalive,
|
||||||
|
"filter": self.filter,
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_enabled():
|
||||||
|
# Defaults to True if the enabled flag is non existent
|
||||||
|
try:
|
||||||
|
return CONF.aprs_network.enabled
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_configured():
|
||||||
|
if APRSISClient.is_enabled():
|
||||||
|
# Ensure that the config vars are correctly set
|
||||||
|
if not CONF.aprs_network.login:
|
||||||
|
LOG.error("Config aprs_network.login not set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"aprs_network.login is not set.",
|
||||||
|
)
|
||||||
|
if not CONF.aprs_network.password:
|
||||||
|
LOG.error("Config aprs_network.password not set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"aprs_network.password is not set.",
|
||||||
|
)
|
||||||
|
if not CONF.aprs_network.host:
|
||||||
|
LOG.error("Config aprs_network.host not set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"aprs_network.host is not set.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _is_stale_connection(self):
|
||||||
|
delta = datetime.datetime.now() - self._client.aprsd_keepalive
|
||||||
|
if delta > self.max_delta:
|
||||||
|
LOG.error(f"Connection is stale, last heard {delta} ago.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_alive(self):
|
||||||
|
if self._client:
|
||||||
|
return self._client.is_alive() and not self._is_stale_connection()
|
||||||
|
else:
|
||||||
|
LOG.warning(f"APRS_CLIENT {self._client} alive? NO!!!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._client:
|
||||||
|
self._client.stop()
|
||||||
|
self._client.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def transport():
|
||||||
|
return client.TRANSPORT_APRSIS
|
||||||
|
|
||||||
|
def decode_packet(self, *args, **kwargs):
|
||||||
|
"""APRS lib already decodes this."""
|
||||||
|
return core.factory(args[0])
|
||||||
|
|
||||||
|
def setup_connection(self):
|
||||||
|
user = CONF.aprs_network.login
|
||||||
|
password = CONF.aprs_network.password
|
||||||
|
host = CONF.aprs_network.host
|
||||||
|
port = CONF.aprs_network.port
|
||||||
|
self.connected = False
|
||||||
|
backoff = 1
|
||||||
|
aprs_client = None
|
||||||
|
while not self.connected:
|
||||||
|
try:
|
||||||
|
LOG.info(f"Creating aprslib client({host}:{port}) and logging in {user}.")
|
||||||
|
aprs_client = aprsis.Aprsdis(user, passwd=password, host=host, port=port)
|
||||||
|
# Force the log to be the same
|
||||||
|
aprs_client.logger = LOG
|
||||||
|
aprs_client.connect()
|
||||||
|
self.connected = True
|
||||||
|
backoff = 1
|
||||||
|
except LoginError as e:
|
||||||
|
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
|
||||||
|
self.connected = False
|
||||||
|
time.sleep(backoff)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
|
||||||
|
self.connected = False
|
||||||
|
time.sleep(backoff)
|
||||||
|
# Don't allow the backoff to go to inifinity.
|
||||||
|
if backoff > 5:
|
||||||
|
backoff = 5
|
||||||
|
else:
|
||||||
|
backoff += 1
|
||||||
|
continue
|
||||||
|
self._client = aprs_client
|
||||||
|
return aprs_client
|
||||||
|
|
||||||
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
||||||
|
self._client.consumer(
|
||||||
|
callback, blocking=blocking,
|
||||||
|
immortal=immortal, raw=raw,
|
||||||
|
)
|
105
aprsd/client/base.py
Normal file
105
aprsd/client/base.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import abc
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
import wrapt
|
||||||
|
|
||||||
|
from aprsd.packets import core
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class APRSClient:
|
||||||
|
"""Singleton client class that constructs the aprslib connection."""
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
_client = None
|
||||||
|
|
||||||
|
connected = False
|
||||||
|
filter = None
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
"""This magic turns this into a singleton."""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
# Put any initialization here.
|
||||||
|
cls._instance._create_client()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def stats(self) -> dict:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_filter(self, filter):
|
||||||
|
self.filter = filter
|
||||||
|
if self._client:
|
||||||
|
self._client.set_filter(filter)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self):
|
||||||
|
if not self._client:
|
||||||
|
self._create_client()
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def _create_client(self):
|
||||||
|
self._client = self.setup_connection()
|
||||||
|
if self.filter:
|
||||||
|
LOG.info("Creating APRS client filter")
|
||||||
|
self._client.set_filter(self.filter)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self._client:
|
||||||
|
LOG.info("Stopping client connection.")
|
||||||
|
self._client.stop()
|
||||||
|
|
||||||
|
def send(self, packet: core.Packet):
|
||||||
|
"""Send a packet to the network."""
|
||||||
|
self.client.send(packet)
|
||||||
|
|
||||||
|
@wrapt.synchronized(lock)
|
||||||
|
def reset(self):
|
||||||
|
"""Call this to force a rebuild/reconnect."""
|
||||||
|
LOG.info("Resetting client connection.")
|
||||||
|
if self._client:
|
||||||
|
self._client.close()
|
||||||
|
del self._client
|
||||||
|
self._create_client()
|
||||||
|
else:
|
||||||
|
LOG.warning("Client not initialized, nothing to reset.")
|
||||||
|
|
||||||
|
# Recreate the client
|
||||||
|
LOG.info(f"Creating new client {self.client}")
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def setup_connection(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abc.abstractmethod
|
||||||
|
def is_enabled():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abc.abstractmethod
|
||||||
|
def transport():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def decode_packet(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def is_alive(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def close(self):
|
||||||
|
pass
|
88
aprsd/client/factory.py
Normal file
88
aprsd/client/factory.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Callable, Protocol, runtime_checkable
|
||||||
|
|
||||||
|
from aprsd import exception
|
||||||
|
from aprsd.packets import core
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class Client(Protocol):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def disconnect(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def decode_packet(self, *args, **kwargs) -> type[core.Packet]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_enabled(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def transport(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def send(self, message: str) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setup_connection(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ClientFactory:
|
||||||
|
_instance = None
|
||||||
|
clients = []
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
"""This magic turns this into a singleton."""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
# Put any initialization here.
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.clients: list[Callable] = []
|
||||||
|
|
||||||
|
def register(self, aprsd_client: Callable):
|
||||||
|
if isinstance(aprsd_client, Client):
|
||||||
|
raise ValueError("Client must be a subclass of Client protocol")
|
||||||
|
|
||||||
|
self.clients.append(aprsd_client)
|
||||||
|
|
||||||
|
def create(self, key=None):
|
||||||
|
for client in self.clients:
|
||||||
|
if client.is_enabled():
|
||||||
|
return client()
|
||||||
|
raise Exception("No client is configured!!")
|
||||||
|
|
||||||
|
def is_client_enabled(self):
|
||||||
|
"""Make sure at least one client is enabled."""
|
||||||
|
enabled = False
|
||||||
|
for client in self.clients:
|
||||||
|
if client.is_enabled():
|
||||||
|
enabled = True
|
||||||
|
return enabled
|
||||||
|
|
||||||
|
def is_client_configured(self):
|
||||||
|
enabled = False
|
||||||
|
for client in self.clients:
|
||||||
|
try:
|
||||||
|
if client.is_configured():
|
||||||
|
enabled = True
|
||||||
|
except exception.MissingConfigOptionException as ex:
|
||||||
|
LOG.error(ex.message)
|
||||||
|
return False
|
||||||
|
except exception.ConfigOptionBogusDefaultException as ex:
|
||||||
|
LOG.error(ex.message)
|
||||||
|
return False
|
||||||
|
return enabled
|
48
aprsd/client/fake.py
Normal file
48
aprsd/client/fake.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import client
|
||||||
|
from aprsd.client import base
|
||||||
|
from aprsd.client.drivers import fake as fake_driver
|
||||||
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class APRSDFakeClient(base.APRSClient, metaclass=trace.TraceWrapperMetaclass):
|
||||||
|
|
||||||
|
def stats(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_enabled():
|
||||||
|
if CONF.fake_client.enabled:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_configured():
|
||||||
|
return APRSDFakeClient.is_enabled()
|
||||||
|
|
||||||
|
def is_alive(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setup_connection(self):
|
||||||
|
self.connected = True
|
||||||
|
return fake_driver.APRSDFakeClient()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def transport():
|
||||||
|
return client.TRANSPORT_FAKE
|
||||||
|
|
||||||
|
def decode_packet(self, *args, **kwargs):
|
||||||
|
LOG.debug(f"kwargs {kwargs}")
|
||||||
|
pkt = kwargs["packet"]
|
||||||
|
LOG.debug(f"Got an APRS Fake Packet '{pkt}'")
|
||||||
|
return pkt
|
103
aprsd/client/kiss.py
Normal file
103
aprsd/client/kiss.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import aprslib
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import client, exception
|
||||||
|
from aprsd.client import base
|
||||||
|
from aprsd.client.drivers import kiss
|
||||||
|
from aprsd.packets import core
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class KISSClient(base.APRSClient):
|
||||||
|
|
||||||
|
_client = None
|
||||||
|
|
||||||
|
def stats(self) -> dict:
|
||||||
|
stats = {}
|
||||||
|
if self.is_configured():
|
||||||
|
return {
|
||||||
|
"transport": self.transport(),
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_enabled():
|
||||||
|
"""Return if tcp or serial KISS is enabled."""
|
||||||
|
if CONF.kiss_serial.enabled:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if CONF.kiss_tcp.enabled:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_configured():
|
||||||
|
# Ensure that the config vars are correctly set
|
||||||
|
if KISSClient.is_enabled():
|
||||||
|
transport = KISSClient.transport()
|
||||||
|
if transport == client.TRANSPORT_SERIALKISS:
|
||||||
|
if not CONF.kiss_serial.device:
|
||||||
|
LOG.error("KISS serial enabled, but no device is set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"kiss_serial.device is not set.",
|
||||||
|
)
|
||||||
|
elif transport == client.TRANSPORT_TCPKISS:
|
||||||
|
if not CONF.kiss_tcp.host:
|
||||||
|
LOG.error("KISS TCP enabled, but no host is set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"kiss_tcp.host is not set.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_alive(self):
|
||||||
|
if self._client:
|
||||||
|
return self._client.is_alive()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._client:
|
||||||
|
self._client.stop()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def transport():
|
||||||
|
if CONF.kiss_serial.enabled:
|
||||||
|
return client.TRANSPORT_SERIALKISS
|
||||||
|
|
||||||
|
if CONF.kiss_tcp.enabled:
|
||||||
|
return client.TRANSPORT_TCPKISS
|
||||||
|
|
||||||
|
def decode_packet(self, *args, **kwargs):
|
||||||
|
"""We get a frame, which has to be decoded."""
|
||||||
|
LOG.debug(f"kwargs {kwargs}")
|
||||||
|
frame = kwargs["frame"]
|
||||||
|
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}"
|
||||||
|
# msg = frame.tnc2
|
||||||
|
# LOG.debug(f"Decoding {msg}")
|
||||||
|
|
||||||
|
raw = aprslib.parse(str(frame))
|
||||||
|
packet = core.factory(raw)
|
||||||
|
if isinstance(packet, core.ThirdPartyPacket):
|
||||||
|
return packet.subpacket
|
||||||
|
else:
|
||||||
|
return packet
|
||||||
|
|
||||||
|
def setup_connection(self):
|
||||||
|
self._client = kiss.KISS3Client()
|
||||||
|
self.connected = True
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
||||||
|
self._client.consumer(callback)
|
38
aprsd/client/stats.py
Normal file
38
aprsd/client/stats.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
import wrapt
|
||||||
|
|
||||||
|
from aprsd import client
|
||||||
|
from aprsd.utils import singleton
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class APRSClientStats:
|
||||||
|
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
@wrapt.synchronized(lock)
|
||||||
|
def stats(self, serializable=False):
|
||||||
|
cl = client.client_factory.create()
|
||||||
|
stats = {
|
||||||
|
"transport": cl.transport(),
|
||||||
|
"filter": cl.filter,
|
||||||
|
"connected": cl.connected,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cl.transport() == client.TRANSPORT_APRSIS:
|
||||||
|
stats["server_string"] = cl.client.server_string
|
||||||
|
keepalive = cl.client.aprsd_keepalive
|
||||||
|
if serializable:
|
||||||
|
keepalive = keepalive.isoformat()
|
||||||
|
stats["server_keepalive"] = keepalive
|
||||||
|
elif cl.transport() == client.TRANSPORT_TCPKISS:
|
||||||
|
stats["host"] = CONF.kiss_tcp.host
|
||||||
|
stats["port"] = CONF.kiss_tcp.port
|
||||||
|
elif cl.transport() == client.TRANSPORT_SERIALKISS:
|
||||||
|
stats["device"] = CONF.kiss_serial.device
|
||||||
|
return stats
|
@ -8,8 +8,9 @@ import logging
|
|||||||
import click
|
import click
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import cli_helper, conf, packets, plugin
|
||||||
# local imports here
|
# local imports here
|
||||||
from aprsd import cli_helper, client, conf, packets, plugin
|
from aprsd.client import base
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ def test_plugin(
|
|||||||
if CONF.trace_enabled:
|
if CONF.trace_enabled:
|
||||||
trace.setup_tracing(["method", "api"])
|
trace.setup_tracing(["method", "api"])
|
||||||
|
|
||||||
client.Client()
|
base.APRSClient()
|
||||||
|
|
||||||
pm = plugin.PluginManager()
|
pm = plugin.PluginManager()
|
||||||
if load_all:
|
if load_all:
|
||||||
|
@ -15,7 +15,8 @@ from rich.console import Console
|
|||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import cli_helper, client, packets, plugin, threads
|
from aprsd import cli_helper, packets, plugin, threads
|
||||||
|
from aprsd.client import client_factory
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.packets import collector as packet_collector
|
from aprsd.packets import collector as packet_collector
|
||||||
from aprsd.packets import log as packet_log
|
from aprsd.packets import log as packet_log
|
||||||
@ -179,15 +180,14 @@ def listen(
|
|||||||
|
|
||||||
# Initialize the client factory and create
|
# Initialize the client factory and create
|
||||||
# The correct client object ready for use
|
# The correct client object ready for use
|
||||||
client.ClientFactory.setup()
|
|
||||||
# Make sure we have 1 client transport enabled
|
# Make sure we have 1 client transport enabled
|
||||||
if not client.factory.is_client_enabled():
|
if not client_factory.is_client_enabled():
|
||||||
LOG.error("No Clients are enabled in config.")
|
LOG.error("No Clients are enabled in config.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Creates the client object
|
# Creates the client object
|
||||||
LOG.info("Creating client connection")
|
LOG.info("Creating client connection")
|
||||||
aprs_client = client.factory.create()
|
aprs_client = client_factory.create()
|
||||||
LOG.info(aprs_client)
|
LOG.info(aprs_client)
|
||||||
|
|
||||||
LOG.debug(f"Filter by '{filter}'")
|
LOG.debug(f"Filter by '{filter}'")
|
||||||
|
@ -8,8 +8,9 @@ import click
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import cli_helper, client, packets
|
from aprsd import cli_helper, packets
|
||||||
from aprsd import conf # noqa : F401
|
from aprsd import conf # noqa : F401
|
||||||
|
from aprsd.client import client_factory
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.packets import collector
|
from aprsd.packets import collector
|
||||||
from aprsd.threads import tx
|
from aprsd.threads import tx
|
||||||
@ -102,7 +103,7 @@ def send_message(
|
|||||||
|
|
||||||
def rx_packet(packet):
|
def rx_packet(packet):
|
||||||
global got_ack, got_response
|
global got_ack, got_response
|
||||||
cl = client.factory.create()
|
cl = client_factory.create()
|
||||||
packet = cl.decode_packet(packet)
|
packet = cl.decode_packet(packet)
|
||||||
collector.PacketCollector().rx(packet)
|
collector.PacketCollector().rx(packet)
|
||||||
packet.log("RX")
|
packet.log("RX")
|
||||||
@ -130,8 +131,7 @@ def send_message(
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.ClientFactory.setup()
|
client_factory.create().client
|
||||||
client.factory.create().client
|
|
||||||
except LoginError:
|
except LoginError:
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ def send_message(
|
|||||||
# This will register a packet consumer with aprslib
|
# This will register a packet consumer with aprslib
|
||||||
# When new packets come in the consumer will process
|
# When new packets come in the consumer will process
|
||||||
# the packet
|
# the packet
|
||||||
aprs_client = client.factory.create().client
|
aprs_client = client_factory.create().client
|
||||||
aprs_client.consumer(rx_packet, raw=False)
|
aprs_client.consumer(rx_packet, raw=False)
|
||||||
except aprslib.exceptions.ConnectionDrop:
|
except aprslib.exceptions.ConnectionDrop:
|
||||||
LOG.error("Connection dropped, reconnecting")
|
LOG.error("Connection dropped, reconnecting")
|
||||||
|
@ -6,9 +6,10 @@ import click
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import cli_helper, client
|
from aprsd import cli_helper
|
||||||
from aprsd import main as aprsd_main
|
from aprsd import main as aprsd_main
|
||||||
from aprsd import packets, plugin, threads, utils
|
from aprsd import packets, plugin, threads, utils
|
||||||
|
from aprsd.client import client_factory
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.packets import collector as packet_collector
|
from aprsd.packets import collector as packet_collector
|
||||||
from aprsd.packets import seen_list
|
from aprsd.packets import seen_list
|
||||||
@ -49,14 +50,13 @@ def server(ctx, flush):
|
|||||||
|
|
||||||
# Initialize the client factory and create
|
# Initialize the client factory and create
|
||||||
# The correct client object ready for use
|
# The correct client object ready for use
|
||||||
client.ClientFactory.setup()
|
if not client_factory.is_client_enabled():
|
||||||
if not client.factory.is_client_enabled():
|
|
||||||
LOG.error("No Clients are enabled in config.")
|
LOG.error("No Clients are enabled in config.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Creates the client object
|
# Creates the client object
|
||||||
LOG.info("Creating client connection")
|
LOG.info("Creating client connection")
|
||||||
aprs_client = client.factory.create()
|
aprs_client = client_factory.create()
|
||||||
LOG.info(aprs_client)
|
LOG.info(aprs_client)
|
||||||
|
|
||||||
# Create the initial PM singleton and Register plugins
|
# Create the initial PM singleton and Register plugins
|
||||||
@ -79,18 +79,14 @@ def server(ctx, flush):
|
|||||||
LOG.info(p)
|
LOG.info(p)
|
||||||
|
|
||||||
# Make sure we have 1 client transport enabled
|
# Make sure we have 1 client transport enabled
|
||||||
if not client.factory.is_client_enabled():
|
if not client_factory.is_client_enabled():
|
||||||
LOG.error("No Clients are enabled in config.")
|
LOG.error("No Clients are enabled in config.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
if not client.factory.is_client_configured():
|
if not client_factory.is_client_configured():
|
||||||
LOG.error("APRS client is not properly configured in config file.")
|
LOG.error("APRS client is not properly configured in config file.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Creates the client object
|
|
||||||
# LOG.info("Creating client connection")
|
|
||||||
# client.factory.create().client
|
|
||||||
|
|
||||||
# Now load the msgTrack from disk if any
|
# Now load the msgTrack from disk if any
|
||||||
packets.PacketList()
|
packets.PacketList()
|
||||||
if flush:
|
if flush:
|
||||||
|
@ -21,6 +21,7 @@ import aprsd
|
|||||||
from aprsd import (
|
from aprsd import (
|
||||||
cli_helper, client, packets, plugin_utils, stats, threads, utils,
|
cli_helper, client, packets, plugin_utils, stats, threads, utils,
|
||||||
)
|
)
|
||||||
|
from aprsd.client import client_factory, kiss
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.threads import aprsd as aprsd_threads
|
from aprsd.threads import aprsd as aprsd_threads
|
||||||
from aprsd.threads import keep_alive, rx, tx
|
from aprsd.threads import keep_alive, rx, tx
|
||||||
@ -380,8 +381,8 @@ def _get_transport(stats):
|
|||||||
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
||||||
"{}</a>".format(stats["APRSClientStats"]["server_string"])
|
"{}</a>".format(stats["APRSClientStats"]["server_string"])
|
||||||
)
|
)
|
||||||
elif client.KISSClient.is_enabled():
|
elif kiss.KISSClient.is_enabled():
|
||||||
transport = client.KISSClient.transport()
|
transport = kiss.KISSClient.transport()
|
||||||
if transport == client.TRANSPORT_TCPKISS:
|
if transport == client.TRANSPORT_TCPKISS:
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
"TCPKISS://{}:{}".format(
|
"TCPKISS://{}:{}".format(
|
||||||
@ -637,13 +638,12 @@ def webchat(ctx, flush, port):
|
|||||||
|
|
||||||
# Initialize the client factory and create
|
# Initialize the client factory and create
|
||||||
# The correct client object ready for use
|
# The correct client object ready for use
|
||||||
client.ClientFactory.setup()
|
|
||||||
# Make sure we have 1 client transport enabled
|
# Make sure we have 1 client transport enabled
|
||||||
if not client.factory.is_client_enabled():
|
if not client_factory.is_client_enabled():
|
||||||
LOG.error("No Clients are enabled in config.")
|
LOG.error("No Clients are enabled in config.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
if not client.factory.is_client_configured():
|
if not client_factory.is_client_configured():
|
||||||
LOG.error("APRS client is not properly configured in config file.")
|
LOG.error("APRS client is not properly configured in config file.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
@ -12,7 +12,22 @@ from aprsd.conf import log as conf_log
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
# LOG = logging.getLogger("APRSD")
|
# LOG = logging.getLogger("APRSD")
|
||||||
LOG = logger
|
LOG = logger
|
||||||
logging_queue = queue.Queue()
|
|
||||||
|
|
||||||
|
class QueueLatest(queue.Queue):
|
||||||
|
"""Custom Queue to keep only the latest N items.
|
||||||
|
|
||||||
|
This prevents the queue from blowing up in size.
|
||||||
|
"""
|
||||||
|
def put(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
super().put(*args, **kwargs)
|
||||||
|
except queue.Full:
|
||||||
|
self.queue.popleft()
|
||||||
|
super().put(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
logging_queue = QueueLatest(maxsize=200)
|
||||||
|
|
||||||
|
|
||||||
class InterceptHandler(logging.Handler):
|
class InterceptHandler(logging.Handler):
|
||||||
@ -59,6 +74,10 @@ def setup_logging(loglevel=None, quiet=False):
|
|||||||
"werkzeug._internal",
|
"werkzeug._internal",
|
||||||
"socketio",
|
"socketio",
|
||||||
"urllib3.connectionpool",
|
"urllib3.connectionpool",
|
||||||
|
"chardet",
|
||||||
|
"chardet.charsetgroupprober",
|
||||||
|
"chardet.eucjpprober",
|
||||||
|
"chardet.mbcharsetprober",
|
||||||
]
|
]
|
||||||
|
|
||||||
# We don't really want to see the aprslib parsing debug output.
|
# We don't really want to see the aprslib parsing debug output.
|
||||||
|
@ -37,7 +37,7 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|||||||
self._total_rx += 1
|
self._total_rx += 1
|
||||||
self._add(packet)
|
self._add(packet)
|
||||||
ptype = packet.__class__.__name__
|
ptype = packet.__class__.__name__
|
||||||
if not ptype in self.data["types"]:
|
if ptype not in self.data["types"]:
|
||||||
self.data["types"][ptype] = {"tx": 0, "rx": 0}
|
self.data["types"][ptype] = {"tx": 0, "rx": 0}
|
||||||
self.data["types"][ptype]["rx"] += 1
|
self.data["types"][ptype]["rx"] += 1
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|||||||
self._total_tx += 1
|
self._total_tx += 1
|
||||||
self._add(packet)
|
self._add(packet)
|
||||||
ptype = packet.__class__.__name__
|
ptype = packet.__class__.__name__
|
||||||
if not ptype in self.data["types"]:
|
if ptype not in self.data["types"]:
|
||||||
self.data["types"][ptype] = {"tx": 0, "rx": 0}
|
self.data["types"][ptype] = {"tx": 0, "rx": 0}
|
||||||
self.data["types"][ptype]["tx"] += 1
|
self.data["types"][ptype]["tx"] += 1
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|||||||
watch_list = CONF.watch_list.callsigns
|
watch_list = CONF.watch_list.callsigns
|
||||||
# make sure the timeout is set or this doesn't work
|
# make sure the timeout is set or this doesn't work
|
||||||
if watch_list:
|
if watch_list:
|
||||||
aprs_client = client.factory.create().client
|
aprs_client = client.client_factory.create().client
|
||||||
filter_str = "b/{}".format("/".join(watch_list))
|
filter_str = "b/{}".format("/".join(watch_list))
|
||||||
aprs_client.set_filter(filter_str)
|
aprs_client.set_filter(filter_str)
|
||||||
else:
|
else:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from aprsd import client as aprs_client
|
|
||||||
from aprsd import plugin
|
from aprsd import plugin
|
||||||
|
from aprsd.client import stats as client_stats
|
||||||
from aprsd.packets import packet_list, seen_list, tracker, watch_list
|
from aprsd.packets import packet_list, seen_list, tracker, watch_list
|
||||||
from aprsd.plugins import email
|
from aprsd.plugins import email
|
||||||
from aprsd.stats import app, collector
|
from aprsd.stats import app, collector
|
||||||
@ -16,5 +16,5 @@ stats_collector.register_producer(tracker.PacketTrack)
|
|||||||
stats_collector.register_producer(plugin.PluginManager)
|
stats_collector.register_producer(plugin.PluginManager)
|
||||||
stats_collector.register_producer(aprsd.APRSDThreadList)
|
stats_collector.register_producer(aprsd.APRSDThreadList)
|
||||||
stats_collector.register_producer(email.EmailStats)
|
stats_collector.register_producer(email.EmailStats)
|
||||||
stats_collector.register_producer(aprs_client.APRSClientStats)
|
stats_collector.register_producer(client_stats.APRSClientStats)
|
||||||
stats_collector.register_producer(seen_list.SeenList)
|
stats_collector.register_producer(seen_list.SeenList)
|
||||||
|
@ -5,6 +5,7 @@ from oslo_config import cfg
|
|||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import utils
|
from aprsd import utils
|
||||||
|
from aprsd.log import log as aprsd_log
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -32,6 +33,7 @@ class APRSDStats:
|
|||||||
def stats(self, serializable=False) -> dict:
|
def stats(self, serializable=False) -> dict:
|
||||||
current, peak = tracemalloc.get_traced_memory()
|
current, peak = tracemalloc.get_traced_memory()
|
||||||
uptime = self.uptime()
|
uptime = self.uptime()
|
||||||
|
qsize = aprsd_log.logging_queue.qsize()
|
||||||
if serializable:
|
if serializable:
|
||||||
uptime = str(uptime)
|
uptime = str(uptime)
|
||||||
stats = {
|
stats = {
|
||||||
@ -42,5 +44,6 @@ class APRSDStats:
|
|||||||
"memory_current_str": utils.human_size(current),
|
"memory_current_str": utils.human_size(current),
|
||||||
"memory_peak": int(peak),
|
"memory_peak": int(peak),
|
||||||
"memory_peak_str": utils.human_size(peak),
|
"memory_peak_str": utils.human_size(peak),
|
||||||
|
"loging_queue": qsize,
|
||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
|
@ -5,7 +5,9 @@ import tracemalloc
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import client, packets, utils
|
from aprsd import packets, utils
|
||||||
|
from aprsd.client import client_factory
|
||||||
|
from aprsd.log import log as aprsd_log
|
||||||
from aprsd.stats import collector
|
from aprsd.stats import collector
|
||||||
from aprsd.threads import APRSDThread, APRSDThreadList
|
from aprsd.threads import APRSDThread, APRSDThreadList
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ class KeepAliveThread(APRSDThread):
|
|||||||
|
|
||||||
keepalive = (
|
keepalive = (
|
||||||
"{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
|
"{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
|
||||||
"Last:{} Email: {} - RAM Current:{} Peak:{} Threads:{}"
|
"Last:{} Email: {} - RAM Current:{} Peak:{} Threads:{} LoggingQueue:{}"
|
||||||
).format(
|
).format(
|
||||||
stats_json["APRSDStats"]["callsign"],
|
stats_json["APRSDStats"]["callsign"],
|
||||||
stats_json["APRSDStats"]["uptime"],
|
stats_json["APRSDStats"]["uptime"],
|
||||||
@ -73,6 +75,7 @@ class KeepAliveThread(APRSDThread):
|
|||||||
stats_json["APRSDStats"]["memory_current_str"],
|
stats_json["APRSDStats"]["memory_current_str"],
|
||||||
stats_json["APRSDStats"]["memory_peak_str"],
|
stats_json["APRSDStats"]["memory_peak_str"],
|
||||||
len(thread_list),
|
len(thread_list),
|
||||||
|
aprsd_log.logging_queue.qsize(),
|
||||||
)
|
)
|
||||||
LOG.info(keepalive)
|
LOG.info(keepalive)
|
||||||
if "APRSDThreadList" in stats_json:
|
if "APRSDThreadList" in stats_json:
|
||||||
@ -87,7 +90,7 @@ class KeepAliveThread(APRSDThread):
|
|||||||
LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}")
|
LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}")
|
||||||
|
|
||||||
# check the APRS connection
|
# check the APRS connection
|
||||||
cl = client.factory.create()
|
cl = client_factory.create()
|
||||||
# Reset the connection if it's dead and this isn't our
|
# Reset the connection if it's dead and this isn't our
|
||||||
# First time through the loop.
|
# First time through the loop.
|
||||||
# The first time through the loop can happen at startup where
|
# The first time through the loop can happen at startup where
|
||||||
@ -95,7 +98,7 @@ class KeepAliveThread(APRSDThread):
|
|||||||
# to make it's connection the first time.
|
# to make it's connection the first time.
|
||||||
if not cl.is_alive() and self.cntr > 0:
|
if not cl.is_alive() and self.cntr > 0:
|
||||||
LOG.error(f"{cl.__class__.__name__} is not alive!!! Resetting")
|
LOG.error(f"{cl.__class__.__name__} is not alive!!! Resetting")
|
||||||
client.factory.create().reset()
|
client_factory.create().reset()
|
||||||
# else:
|
# else:
|
||||||
# # See if we should reset the aprs-is client
|
# # See if we should reset the aprs-is client
|
||||||
# # Due to losing a keepalive from them
|
# # Due to losing a keepalive from them
|
||||||
|
@ -19,7 +19,6 @@ def send_log_entries(force=False):
|
|||||||
if CONF.admin.web_enabled:
|
if CONF.admin.web_enabled:
|
||||||
if force or LogEntries().is_purge_ready():
|
if force or LogEntries().is_purge_ready():
|
||||||
entries = LogEntries().get_all_and_purge()
|
entries = LogEntries().get_all_and_purge()
|
||||||
print(f"Sending log entries {len(entries)}")
|
|
||||||
if entries:
|
if entries:
|
||||||
try:
|
try:
|
||||||
requests.post(
|
requests.post(
|
||||||
@ -27,9 +26,8 @@ def send_log_entries(force=False):
|
|||||||
json=entries,
|
json=entries,
|
||||||
auth=(CONF.admin.user, CONF.admin.password),
|
auth=(CONF.admin.user, CONF.admin.password),
|
||||||
)
|
)
|
||||||
except Exception as ex:
|
except Exception:
|
||||||
LOG.warning(f"Failed to send log entries {len(entries)}")
|
LOG.warning(f"Failed to send log entries. len={len(entries)}")
|
||||||
LOG.warning(ex)
|
|
||||||
|
|
||||||
|
|
||||||
class LogEntries:
|
class LogEntries:
|
||||||
|
@ -6,7 +6,8 @@ import time
|
|||||||
import aprslib
|
import aprslib
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import client, packets, plugin
|
from aprsd import packets, plugin
|
||||||
|
from aprsd.client import client_factory
|
||||||
from aprsd.packets import collector
|
from aprsd.packets import collector
|
||||||
from aprsd.packets import log as packet_log
|
from aprsd.packets import log as packet_log
|
||||||
from aprsd.threads import APRSDThread, tx
|
from aprsd.threads import APRSDThread, tx
|
||||||
@ -20,7 +21,7 @@ class APRSDRXThread(APRSDThread):
|
|||||||
def __init__(self, packet_queue):
|
def __init__(self, packet_queue):
|
||||||
super().__init__("RX_PKT")
|
super().__init__("RX_PKT")
|
||||||
self.packet_queue = packet_queue
|
self.packet_queue = packet_queue
|
||||||
self._client = client.factory.create()
|
self._client = client_factory.create()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.thread_stop = True
|
self.thread_stop = True
|
||||||
@ -29,7 +30,7 @@ class APRSDRXThread(APRSDThread):
|
|||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
if not self._client:
|
if not self._client:
|
||||||
self._client = client.factory.create()
|
self._client = client_factory.create()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return True
|
return True
|
||||||
# setup the consumer of messages and block until a messages
|
# setup the consumer of messages and block until a messages
|
||||||
|
@ -9,9 +9,9 @@ from rush.limiters import periodic
|
|||||||
from rush.stores import dictionary
|
from rush.stores import dictionary
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
from aprsd import client
|
|
||||||
from aprsd import conf # noqa
|
from aprsd import conf # noqa
|
||||||
from aprsd import threads as aprsd_threads
|
from aprsd import threads as aprsd_threads
|
||||||
|
from aprsd.client import client_factory
|
||||||
from aprsd.packets import collector, core
|
from aprsd.packets import collector, core
|
||||||
from aprsd.packets import log as packet_log
|
from aprsd.packets import log as packet_log
|
||||||
from aprsd.packets import tracker
|
from aprsd.packets import tracker
|
||||||
@ -80,7 +80,7 @@ def _send_direct(packet, aprs_client=None):
|
|||||||
if aprs_client:
|
if aprs_client:
|
||||||
cl = aprs_client
|
cl = aprs_client
|
||||||
else:
|
else:
|
||||||
cl = client.factory.create()
|
cl = client_factory.create()
|
||||||
|
|
||||||
packet.update_timestamp()
|
packet.update_timestamp()
|
||||||
packet_log.log(packet, tx=True)
|
packet_log.log(packet, tx=True)
|
||||||
@ -247,7 +247,7 @@ class BeaconSendThread(aprsd_threads.APRSDThread):
|
|||||||
send(pkt, direct=True)
|
send(pkt, direct=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Failed to send beacon: {e}")
|
LOG.error(f"Failed to send beacon: {e}")
|
||||||
client.factory.create().reset()
|
client_factory.create().reset()
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
self._loop_cnt += 1
|
self._loop_cnt += 1
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
#
|
|
||||||
# This file is autogenerated by pip-compile with Python 3.10
|
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile --annotation-style=line dev-requirements.in
|
|
||||||
#
|
|
||||||
add-trailing-comma==3.1.0 # via gray
|
|
||||||
alabaster==0.7.16 # via sphinx
|
|
||||||
autoflake==1.5.3 # via gray
|
|
||||||
babel==2.14.0 # via sphinx
|
|
||||||
black==24.4.0 # via gray
|
|
||||||
build==1.2.1 # via pip-tools
|
|
||||||
cachetools==5.3.3 # via tox
|
|
||||||
certifi==2024.2.2 # via requests
|
|
||||||
cfgv==3.4.0 # via pre-commit
|
|
||||||
chardet==5.2.0 # via tox
|
|
||||||
charset-normalizer==3.3.2 # via requests
|
|
||||||
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.5.0 # via pytest-cov
|
|
||||||
distlib==0.3.8 # via virtualenv
|
|
||||||
docutils==0.21.2 # via sphinx
|
|
||||||
exceptiongroup==1.2.1 # via pytest
|
|
||||||
filelock==3.13.4 # via tox, virtualenv
|
|
||||||
fixit==2.1.0 # via gray
|
|
||||||
flake8==7.0.0 # via -r dev-requirements.in, pep8-naming
|
|
||||||
gray==0.14.0 # via -r dev-requirements.in
|
|
||||||
identify==2.5.36 # via pre-commit
|
|
||||||
idna==3.7 # via requests
|
|
||||||
imagesize==1.4.1 # via sphinx
|
|
||||||
iniconfig==2.0.0 # via pytest
|
|
||||||
isort==5.13.2 # via -r dev-requirements.in, gray
|
|
||||||
jinja2==3.1.3 # via sphinx
|
|
||||||
libcst==1.3.1 # via fixit
|
|
||||||
markupsafe==2.1.5 # via jinja2
|
|
||||||
mccabe==0.7.0 # via flake8
|
|
||||||
moreorless==0.4.0 # via fixit
|
|
||||||
mypy==1.9.0 # via -r dev-requirements.in
|
|
||||||
mypy-extensions==1.0.0 # via black, mypy
|
|
||||||
nodeenv==1.8.0 # via pre-commit
|
|
||||||
packaging==24.0 # via black, build, fixit, pyproject-api, pytest, sphinx, tox
|
|
||||||
pathspec==0.12.1 # via black, trailrunner
|
|
||||||
pep8-naming==0.13.3 # via -r dev-requirements.in
|
|
||||||
pip-tools==7.4.1 # via -r dev-requirements.in
|
|
||||||
platformdirs==4.2.1 # via black, tox, virtualenv
|
|
||||||
pluggy==1.5.0 # via pytest, tox
|
|
||||||
pre-commit==3.7.0 # via -r dev-requirements.in
|
|
||||||
pycodestyle==2.11.1 # via flake8
|
|
||||||
pyflakes==3.2.0 # via autoflake, flake8
|
|
||||||
pygments==2.17.2 # via rich, sphinx
|
|
||||||
pyproject-api==1.6.1 # via tox
|
|
||||||
pyproject-hooks==1.0.0 # via build, pip-tools
|
|
||||||
pytest==8.1.1 # via -r dev-requirements.in, pytest-cov
|
|
||||||
pytest-cov==5.0.0 # via -r dev-requirements.in
|
|
||||||
pyupgrade==3.15.2 # via gray
|
|
||||||
pyyaml==6.0.1 # via libcst, pre-commit
|
|
||||||
requests==2.31.0 # via sphinx
|
|
||||||
rich==12.6.0 # via gray
|
|
||||||
snowballstemmer==2.2.0 # via sphinx
|
|
||||||
sphinx==7.3.7 # via -r dev-requirements.in
|
|
||||||
sphinxcontrib-applehelp==1.0.8 # via sphinx
|
|
||||||
sphinxcontrib-devhelp==1.0.6 # via sphinx
|
|
||||||
sphinxcontrib-htmlhelp==2.0.5 # via sphinx
|
|
||||||
sphinxcontrib-jsmath==1.0.1 # via sphinx
|
|
||||||
sphinxcontrib-qthelp==1.0.7 # via sphinx
|
|
||||||
sphinxcontrib-serializinghtml==1.1.10 # via sphinx
|
|
||||||
tokenize-rt==5.2.0 # via add-trailing-comma, pyupgrade
|
|
||||||
toml==0.10.2 # via autoflake
|
|
||||||
tomli==2.0.1 # via black, build, coverage, fixit, mypy, pip-tools, pyproject-api, pyproject-hooks, pytest, sphinx, tox
|
|
||||||
tox==4.14.2 # via -r dev-requirements.in
|
|
||||||
trailrunner==1.4.0 # via fixit
|
|
||||||
typing-extensions==4.11.0 # via black, mypy
|
|
||||||
unify==0.5 # via gray
|
|
||||||
untokenize==0.1.1 # via unify
|
|
||||||
urllib3==2.2.1 # via requests
|
|
||||||
virtualenv==20.26.0 # via pre-commit, tox
|
|
||||||
wheel==0.43.0 # via pip-tools
|
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
|
||||||
# pip
|
|
||||||
# setuptools
|
|
@ -1,10 +1,18 @@
|
|||||||
FROM python:3.11-slim as build
|
FROM python:3.11-slim as build
|
||||||
|
|
||||||
ARG VERSION=3.4.0
|
ARG VERSION=3.4.0
|
||||||
|
# pass this in as 'dev' if you want to install from github repo vs pypi
|
||||||
|
ARG INSTALL_TYPE=pypi
|
||||||
|
|
||||||
|
ARG BRANCH=master
|
||||||
|
ARG BUILDX_QEMU_ENV
|
||||||
|
|
||||||
|
ENV APRSD_BRANCH=${BRANCH:-master}
|
||||||
ENV TZ=${TZ:-US/Eastern}
|
ENV TZ=${TZ:-US/Eastern}
|
||||||
ENV LC_ALL=C.UTF-8
|
ENV LC_ALL=C.UTF-8
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
ENV APRSD_PIP_VERSION=${VERSION}
|
ENV APRSD_PIP_VERSION=${VERSION}
|
||||||
|
ENV PATH="${PATH}:/app/.local/bin"
|
||||||
|
|
||||||
ENV PIP_DEFAULT_TIMEOUT=100 \
|
ENV PIP_DEFAULT_TIMEOUT=100 \
|
||||||
# Allow statements and log messages to immediately appear
|
# Allow statements and log messages to immediately appear
|
||||||
@ -19,6 +27,7 @@ RUN set -ex \
|
|||||||
# Create a non-root user
|
# Create a non-root user
|
||||||
&& addgroup --system --gid 1001 appgroup \
|
&& addgroup --system --gid 1001 appgroup \
|
||||||
&& useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
|
&& useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
|
||||||
|
&& usermod -aG sudo appuser \
|
||||||
# Upgrade the package index and install security upgrades
|
# Upgrade the package index and install security upgrades
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get upgrade -y \
|
&& apt-get upgrade -y \
|
||||||
@ -35,16 +44,22 @@ FROM build as final
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN pip3 install -U pip
|
RUN pip3 install -U pip
|
||||||
RUN pip3 install aprsd==$APRSD_PIP_VERSION
|
|
||||||
RUN pip install gevent uwsgi
|
|
||||||
RUN which aprsd
|
|
||||||
RUN mkdir /config
|
RUN mkdir /config
|
||||||
RUN chown -R appuser:appgroup /app
|
RUN chown -R appuser:appgroup /app
|
||||||
RUN chown -R appuser:appgroup /config
|
RUN chown -R appuser:appgroup /config
|
||||||
USER appuser
|
USER appuser
|
||||||
RUN echo "PATH=\$PATH:/usr/games" >> /app/.bashrc
|
RUN if [ "$INSTALL_TYPE" = "pypi" ]; then \
|
||||||
|
pip3 install aprsd==$APRSD_PIP_VERSION; \
|
||||||
|
elif [ "$INSTALL_TYPE" = "github" ]; then \
|
||||||
|
git clone -b $APRSD_BRANCH https://github.com/craigerl/aprsd; \
|
||||||
|
cd /app/aprsd && pip install .; \
|
||||||
|
ls -al /app/.local/lib/python3.11/site-packages/aprsd*; \
|
||||||
|
fi
|
||||||
|
RUN pip install gevent uwsgi
|
||||||
|
RUN echo "PATH=\$PATH:/usr/games:/app/.local/bin" >> /app/.bashrc
|
||||||
RUN which aprsd
|
RUN which aprsd
|
||||||
RUN aprsd sample-config > /config/aprsd.conf
|
RUN aprsd sample-config > /config/aprsd.conf
|
||||||
|
RUN aprsd --version
|
||||||
|
|
||||||
ADD bin/setup.sh /app
|
ADD bin/setup.sh /app
|
||||||
ADD bin/admin.sh /app
|
ADD bin/admin.sh /app
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
FROM python:3.11-slim as build
|
|
||||||
|
|
||||||
ARG BRANCH=master
|
|
||||||
ARG BUILDX_QEMU_ENV
|
|
||||||
ENV APRSD_BRANCH=${BRANCH:-master}
|
|
||||||
|
|
||||||
ENV PIP_DEFAULT_TIMEOUT=100 \
|
|
||||||
# Allow statements and log messages to immediately appear
|
|
||||||
PYTHONUNBUFFERED=1 \
|
|
||||||
# disable a pip version check to reduce run-time & log-spam
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
||||||
# cache is useless in docker image, so disable to reduce image size
|
|
||||||
PIP_NO_CACHE_DIR=1
|
|
||||||
|
|
||||||
|
|
||||||
RUN set -ex \
|
|
||||||
# Create a non-root user
|
|
||||||
&& addgroup --system --gid 1001 appgroup \
|
|
||||||
&& useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
|
|
||||||
# Upgrade the package index and install security upgrades
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get upgrade -y \
|
|
||||||
&& apt-get install -y git build-essential curl libffi-dev fortune \
|
|
||||||
python3-dev libssl-dev libxml2-dev libxslt-dev telnet sudo \
|
|
||||||
# Install dependencies
|
|
||||||
# Clean up
|
|
||||||
&& apt-get autoremove -y \
|
|
||||||
&& apt-get clean -y
|
|
||||||
|
|
||||||
|
|
||||||
### Final stage
|
|
||||||
FROM build as final
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN git clone -b $APRSD_BRANCH https://github.com/craigerl/aprsd
|
|
||||||
RUN pip install -U pip
|
|
||||||
RUN cd aprsd && pip install --no-cache-dir .
|
|
||||||
RUN pip install gevent uwsgi==2.0.24
|
|
||||||
RUN which aprsd
|
|
||||||
RUN mkdir /config
|
|
||||||
RUN chown -R appuser:appgroup /app
|
|
||||||
RUN chown -R appuser:appgroup /config
|
|
||||||
USER appuser
|
|
||||||
RUN echo "PATH=\$PATH:/usr/games" >> /app/.bashrc
|
|
||||||
RUN which aprsd
|
|
||||||
RUN aprsd sample-config > /config/aprsd.conf
|
|
||||||
|
|
||||||
ADD bin/setup.sh /app
|
|
||||||
ADD bin/admin.sh /app
|
|
||||||
|
|
||||||
EXPOSE 8000
|
|
||||||
EXPOSE 8001
|
|
||||||
|
|
||||||
VOLUME ["/config"]
|
|
||||||
# CMD ["gunicorn", "aprsd.wsgi:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
||||||
ENTRYPOINT ["/app/setup.sh"]
|
|
||||||
CMD ["server"]
|
|
||||||
|
|
||||||
# Set the user to run the application
|
|
||||||
USER appuser
|
|
@ -90,7 +90,8 @@ then
|
|||||||
# Use this script to locally build the docker image
|
# Use this script to locally build the docker image
|
||||||
docker buildx build --push --platform $PLATFORMS \
|
docker buildx build --push --platform $PLATFORMS \
|
||||||
-t hemna6969/aprsd:$TAG \
|
-t hemna6969/aprsd:$TAG \
|
||||||
-f Dockerfile-dev --build-arg branch=$BRANCH \
|
--build-arg INSTALL_TYPE=github \
|
||||||
|
--build-arg branch=$BRANCH \
|
||||||
--build-arg BUILDX_QEMU_ENV=true \
|
--build-arg BUILDX_QEMU_ENV=true \
|
||||||
--no-cache .
|
--no-cache .
|
||||||
else
|
else
|
||||||
@ -101,6 +102,5 @@ else
|
|||||||
--build-arg BUILDX_QEMU_ENV=true \
|
--build-arg BUILDX_QEMU_ENV=true \
|
||||||
-t hemna6969/aprsd:$VERSION \
|
-t hemna6969/aprsd:$VERSION \
|
||||||
-t hemna6969/aprsd:$TAG \
|
-t hemna6969/aprsd:$TAG \
|
||||||
-t hemna6969/aprsd:latest \
|
-t hemna6969/aprsd:latest .
|
||||||
-f Dockerfile .
|
|
||||||
fi
|
fi
|
||||||
|
37
docs/apidoc/aprsd.client.drivers.rst
Normal file
37
docs/apidoc/aprsd.client.drivers.rst
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
aprsd.client.drivers package
|
||||||
|
============================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
aprsd.client.drivers.aprsis module
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.drivers.aprsis
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.client.drivers.fake module
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.drivers.fake
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.client.drivers.kiss module
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.drivers.kiss
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.drivers
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
69
docs/apidoc/aprsd.client.rst
Normal file
69
docs/apidoc/aprsd.client.rst
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
aprsd.client package
|
||||||
|
====================
|
||||||
|
|
||||||
|
Subpackages
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
aprsd.client.drivers
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
aprsd.client.aprsis module
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.aprsis
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.client.base module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.base
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.client.factory module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.factory
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.client.fake module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.fake
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.client.kiss module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.kiss
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.client.stats module
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client.stats
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.client
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
@ -1,29 +0,0 @@
|
|||||||
aprsd.clients package
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
aprsd.clients.aprsis module
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.clients.aprsis
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.clients.kiss module
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.clients.kiss
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.clients
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
21
docs/apidoc/aprsd.log.rst
Normal file
21
docs/apidoc/aprsd.log.rst
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
aprsd.log package
|
||||||
|
=================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
aprsd.log.log module
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.log.log
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.log
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
@ -4,6 +4,14 @@ aprsd.packets package
|
|||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
aprsd.packets.collector module
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.packets.collector
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.packets.core module
|
aprsd.packets.core module
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
@ -12,6 +20,14 @@ aprsd.packets.core module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.packets.log module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.packets.log
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.packets.packet\_list module
|
aprsd.packets.packet\_list module
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
@ -44,14 +44,6 @@ aprsd.plugins.ping module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.plugins.query module
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.plugins.query
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.plugins.time module
|
aprsd.plugins.time module
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
aprsd.rpc package
|
|
||||||
=================
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
aprsd.rpc.client module
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.rpc.client
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.rpc.server module
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.rpc.server
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.rpc
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -7,13 +7,13 @@ Subpackages
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
aprsd.clients
|
aprsd.client
|
||||||
aprsd.cmds
|
aprsd.cmds
|
||||||
aprsd.conf
|
aprsd.conf
|
||||||
aprsd.log
|
aprsd.log
|
||||||
aprsd.packets
|
aprsd.packets
|
||||||
aprsd.plugins
|
aprsd.plugins
|
||||||
aprsd.rpc
|
aprsd.stats
|
||||||
aprsd.threads
|
aprsd.threads
|
||||||
aprsd.utils
|
aprsd.utils
|
||||||
aprsd.web
|
aprsd.web
|
||||||
@ -29,14 +29,6 @@ aprsd.cli\_helper module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.client module
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.client
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.exception module
|
aprsd.exception module
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@ -77,14 +69,6 @@ aprsd.plugin\_utils module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.stats module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.stats
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.wsgi module
|
aprsd.wsgi module
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
29
docs/apidoc/aprsd.stats.rst
Normal file
29
docs/apidoc/aprsd.stats.rst
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
aprsd.stats package
|
||||||
|
===================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
aprsd.stats.app module
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.stats.app
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.stats.collector module
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.stats.collector
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.stats
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
@ -28,6 +28,14 @@ aprsd.threads.log\_monitor module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.threads.registry module
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.threads.registry
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.threads.rx module
|
aprsd.threads.rx module
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
@ -36,6 +44,14 @@ aprsd.threads.rx module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.threads.stats module
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.threads.stats
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.threads.tx module
|
aprsd.threads.tx module
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
1804
docs/changelog.rst
1804
docs/changelog.rst
File diff suppressed because it is too large
Load Diff
153
docs/readme.rst
153
docs/readme.rst
@ -10,32 +10,38 @@ ____________________
|
|||||||
|
|
||||||
`APRSD <http://github.com/craigerl/aprsd>`_ is a Ham radio `APRS <http://aprs.org>`_ message command gateway built on python.
|
`APRSD <http://github.com/craigerl/aprsd>`_ is a Ham radio `APRS <http://aprs.org>`_ message command gateway built on python.
|
||||||
|
|
||||||
APRSD listens on amateur radio aprs-is network for messages and respond to them.
|
|
||||||
It has a plugin architecture for extensibility. Users of APRSD can write their own
|
|
||||||
plugins that can respond to APRS-IS messages.
|
|
||||||
|
|
||||||
You must have an amateur radio callsign to use this software. APRSD gets
|
What is APRSD
|
||||||
messages for the configured HAM callsign, and sends those messages to a
|
=============
|
||||||
list of plugins for processing. There are a set of core plugins that
|
APRSD is a python application for interacting with the APRS network and providing
|
||||||
provide responding to messages to check email, get location, ping,
|
APRS services for HAM radio operators.
|
||||||
time of day, get weather, and fortune telling as well as version information
|
|
||||||
of aprsd itself.
|
APRSD currently has 4 main commands to use.
|
||||||
|
* server - Connect to APRS and listen/respond to APRS messages
|
||||||
|
* webchat - web based chat program over APRS
|
||||||
|
* send-message - Send a message to a callsign via APRS_IS.
|
||||||
|
* listen - Listen to packets on the APRS-IS Network based on FILTER.
|
||||||
|
|
||||||
|
Each of those commands can connect to the APRS-IS network if internet connectivity
|
||||||
|
is available. If internet is not available, then APRS can be configured to talk
|
||||||
|
to a TCP KISS TNC for radio connectivity.
|
||||||
|
|
||||||
Please `read the docs`_ to learn more!
|
Please `read the docs`_ to learn more!
|
||||||
|
|
||||||
|
|
||||||
.. contents:: :local:
|
|
||||||
|
|
||||||
|
|
||||||
APRSD Overview Diagram
|
APRSD Overview Diagram
|
||||||
======================
|
======================
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/craigerl/aprsd/master/docs/_static/aprsd_overview.svg?sanitize=true
|
.. image:: https://raw.githubusercontent.com/craigerl/aprsd/master/docs/_static/aprsd_overview.svg?sanitize=true
|
||||||
|
|
||||||
|
|
||||||
Typical use case
|
Typical use case
|
||||||
================
|
================
|
||||||
|
|
||||||
|
APRSD's typical use case is that of providing an APRS wide service to all HAM
|
||||||
|
radio operators. For example the callsign 'REPEAT' on the APRS network is actually
|
||||||
|
an instance of APRSD that can provide a list of HAM repeaters in the area of the
|
||||||
|
callsign that sent the message.
|
||||||
|
|
||||||
|
|
||||||
Ham radio operator using an APRS enabled HAM radio sends a message to check
|
Ham radio operator using an APRS enabled HAM radio sends a message to check
|
||||||
the weather. An APRS message is sent, and then picked up by APRSD. The
|
the weather. An APRS message is sent, and then picked up by APRSD. The
|
||||||
APRS packet is decoded, and the message is sent through the list of plugins
|
APRS packet is decoded, and the message is sent through the list of plugins
|
||||||
@ -46,55 +52,6 @@ callsigns to look out for. The watch list can notify you when a HAM callsign
|
|||||||
in the list is seen and now available to message on the APRS network.
|
in the list is seen and now available to message on the APRS network.
|
||||||
|
|
||||||
|
|
||||||
Current list of built-in plugins
|
|
||||||
======================================
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
└─> aprsd list-plugins
|
|
||||||
🐍 APRSD Built-in Plugins 🐍
|
|
||||||
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
||||||
┃ Plugin Name ┃ Info ┃ Type ┃ Plugin Path ┃
|
|
||||||
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
||||||
│ AVWXWeatherPlugin │ AVWX weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.AVWXWeatherPlugin │
|
|
||||||
│ EmailPlugin │ Send and Receive email │ RegexCommand │ aprsd.plugins.email.EmailPlugin │
|
|
||||||
│ FortunePlugin │ Give me a fortune │ RegexCommand │ aprsd.plugins.fortune.FortunePlugin │
|
|
||||||
│ LocationPlugin │ Where in the world is a CALLSIGN's last GPS beacon? │ RegexCommand │ aprsd.plugins.location.LocationPlugin │
|
|
||||||
│ NotifySeenPlugin │ Notify me when a CALLSIGN is recently seen on APRS-IS │ WatchList │ aprsd.plugins.notify.NotifySeenPlugin │
|
|
||||||
│ OWMWeatherPlugin │ OpenWeatherMap weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.OWMWeatherPlugin │
|
|
||||||
│ PingPlugin │ reply with a Pong! │ RegexCommand │ aprsd.plugins.ping.PingPlugin │
|
|
||||||
│ QueryPlugin │ APRSD Owner command to query messages in the MsgTrack │ RegexCommand │ aprsd.plugins.query.QueryPlugin │
|
|
||||||
│ TimeOWMPlugin │ Current time of GPS beacon's timezone. Uses OpenWeatherMap │ RegexCommand │ aprsd.plugins.time.TimeOWMPlugin │
|
|
||||||
│ TimePlugin │ What is the current local time. │ RegexCommand │ aprsd.plugins.time.TimePlugin │
|
|
||||||
│ USMetarPlugin │ USA only METAR of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.USMetarPlugin │
|
|
||||||
│ USWeatherPlugin │ Provide USA only weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.USWeatherPlugin │
|
|
||||||
│ VersionPlugin │ What is the APRSD Version │ RegexCommand │ aprsd.plugins.version.VersionPlugin │
|
|
||||||
└───────────────────┴────────────────────────────────────────────────────────────┴──────────────┴─────────────────────────────────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
Pypi.org APRSD Installable Plugin Packages
|
|
||||||
|
|
||||||
Install any of the following plugins with 'pip install <Plugin Package Name>'
|
|
||||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
|
||||||
┃ Plugin Package Name ┃ Description ┃ Version ┃ Released ┃ Installed? ┃
|
|
||||||
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
|
|
||||||
│ 📂 aprsd-stock-plugin │ Ham Radio APRSD Plugin for fetching stock quotes │ 0.1.3 │ Dec 2, 2022 │ No │
|
|
||||||
│ 📂 aprsd-sentry-plugin │ Ham radio APRSD plugin that does.... │ 0.1.2 │ Dec 2, 2022 │ No │
|
|
||||||
│ 📂 aprsd-timeopencage-plugin │ APRSD plugin for fetching time based on GPS location │ 0.1.0 │ Dec 2, 2022 │ No │
|
|
||||||
│ 📂 aprsd-weewx-plugin │ HAM Radio APRSD that reports weather from a weewx weather station. │ 0.1.4 │ Dec 7, 2021 │ Yes │
|
|
||||||
│ 📂 aprsd-repeat-plugins │ APRSD Plugins for the REPEAT service │ 1.0.12 │ Dec 2, 2022 │ No │
|
|
||||||
│ 📂 aprsd-telegram-plugin │ Ham Radio APRS APRSD plugin for Telegram IM service │ 0.1.3 │ Dec 2, 2022 │ No │
|
|
||||||
│ 📂 aprsd-twitter-plugin │ Python APRSD plugin to send tweets │ 0.3.0 │ Dec 7, 2021 │ No │
|
|
||||||
│ 📂 aprsd-slack-plugin │ Amateur radio APRS daemon which listens for messages and responds │ 1.0.5 │ Dec 18, 2022 │ No │
|
|
||||||
└──────────────────────────────┴────────────────────────────────────────────────────────────────────┴─────────┴──────────────┴────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
🐍 APRSD Installed 3rd party Plugins 🐍
|
|
||||||
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
||||||
┃ Package Name ┃ Plugin Name ┃ Version ┃ Type ┃ Plugin Path ┃
|
|
||||||
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
||||||
│ aprsd-weewx-plugin │ WeewxMQTTPlugin │ 1.0 │ RegexCommand │ aprsd_weewx_plugin.weewx.WeewxMQTTPlugin │
|
|
||||||
└────────────────────┴─────────────────┴─────────┴──────────────┴──────────────────────────────────────────┘
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
=============
|
=============
|
||||||
@ -112,6 +69,7 @@ Help
|
|||||||
====
|
====
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
||||||
└─> aprsd -h
|
└─> aprsd -h
|
||||||
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
@ -121,9 +79,11 @@ Help
|
|||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
check-version Check this version against the latest in pypi.org.
|
check-version Check this version against the latest in pypi.org.
|
||||||
completion Click Completion subcommands
|
completion Show the shell completion code
|
||||||
dev Development type subcommands
|
dev Development type subcommands
|
||||||
|
fetch-stats Fetch stats from a APRSD admin web interface.
|
||||||
healthcheck Check the health of the running aprsd server.
|
healthcheck Check the health of the running aprsd server.
|
||||||
|
list-extensions List the built in plugins available to APRSD.
|
||||||
list-plugins List the built in plugins available to APRSD.
|
list-plugins List the built in plugins available to APRSD.
|
||||||
listen Listen to packets on the APRS-IS Network based on FILTER.
|
listen Listen to packets on the APRS-IS Network based on FILTER.
|
||||||
sample-config Generate a sample Config file from aprsd and all...
|
sample-config Generate a sample Config file from aprsd and all...
|
||||||
@ -133,7 +93,6 @@ Help
|
|||||||
webchat Web based HAM Radio chat program!
|
webchat Web based HAM Radio chat program!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Commands
|
Commands
|
||||||
========
|
========
|
||||||
|
|
||||||
@ -187,6 +146,56 @@ look for incomming commands to the callsign configured in the config file
|
|||||||
12/07/2021 03:16:17 PM MainThread INFO aprs.logfile = /tmp/aprsd.log server.py:60
|
12/07/2021 03:16:17 PM MainThread INFO aprs.logfile = /tmp/aprsd.log server.py:60
|
||||||
|
|
||||||
|
|
||||||
|
Current list of built-in plugins
|
||||||
|
======================================
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
└─> aprsd list-plugins
|
||||||
|
🐍 APRSD Built-in Plugins 🐍
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ Plugin Name ┃ Info ┃ Type ┃ Plugin Path ┃
|
||||||
|
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||||
|
│ AVWXWeatherPlugin │ AVWX weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.AVWXWeatherPlugin │
|
||||||
|
│ EmailPlugin │ Send and Receive email │ RegexCommand │ aprsd.plugins.email.EmailPlugin │
|
||||||
|
│ FortunePlugin │ Give me a fortune │ RegexCommand │ aprsd.plugins.fortune.FortunePlugin │
|
||||||
|
│ LocationPlugin │ Where in the world is a CALLSIGN's last GPS beacon? │ RegexCommand │ aprsd.plugins.location.LocationPlugin │
|
||||||
|
│ NotifySeenPlugin │ Notify me when a CALLSIGN is recently seen on APRS-IS │ WatchList │ aprsd.plugins.notify.NotifySeenPlugin │
|
||||||
|
│ OWMWeatherPlugin │ OpenWeatherMap weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.OWMWeatherPlugin │
|
||||||
|
│ PingPlugin │ reply with a Pong! │ RegexCommand │ aprsd.plugins.ping.PingPlugin │
|
||||||
|
│ QueryPlugin │ APRSD Owner command to query messages in the MsgTrack │ RegexCommand │ aprsd.plugins.query.QueryPlugin │
|
||||||
|
│ TimeOWMPlugin │ Current time of GPS beacon's timezone. Uses OpenWeatherMap │ RegexCommand │ aprsd.plugins.time.TimeOWMPlugin │
|
||||||
|
│ TimePlugin │ What is the current local time. │ RegexCommand │ aprsd.plugins.time.TimePlugin │
|
||||||
|
│ USMetarPlugin │ USA only METAR of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.USMetarPlugin │
|
||||||
|
│ USWeatherPlugin │ Provide USA only weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.USWeatherPlugin │
|
||||||
|
│ VersionPlugin │ What is the APRSD Version │ RegexCommand │ aprsd.plugins.version.VersionPlugin │
|
||||||
|
└───────────────────┴────────────────────────────────────────────────────────────┴──────────────┴─────────────────────────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
Pypi.org APRSD Installable Plugin Packages
|
||||||
|
|
||||||
|
Install any of the following plugins with 'pip install <Plugin Package Name>'
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
||||||
|
┃ Plugin Package Name ┃ Description ┃ Version ┃ Released ┃ Installed? ┃
|
||||||
|
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
|
||||||
|
│ 📂 aprsd-stock-plugin │ Ham Radio APRSD Plugin for fetching stock quotes │ 0.1.3 │ Dec 2, 2022 │ No │
|
||||||
|
│ 📂 aprsd-sentry-plugin │ Ham radio APRSD plugin that does.... │ 0.1.2 │ Dec 2, 2022 │ No │
|
||||||
|
│ 📂 aprsd-timeopencage-plugin │ APRSD plugin for fetching time based on GPS location │ 0.1.0 │ Dec 2, 2022 │ No │
|
||||||
|
│ 📂 aprsd-weewx-plugin │ HAM Radio APRSD that reports weather from a weewx weather station. │ 0.1.4 │ Dec 7, 2021 │ Yes │
|
||||||
|
│ 📂 aprsd-repeat-plugins │ APRSD Plugins for the REPEAT service │ 1.0.12 │ Dec 2, 2022 │ No │
|
||||||
|
│ 📂 aprsd-telegram-plugin │ Ham Radio APRS APRSD plugin for Telegram IM service │ 0.1.3 │ Dec 2, 2022 │ No │
|
||||||
|
│ 📂 aprsd-twitter-plugin │ Python APRSD plugin to send tweets │ 0.3.0 │ Dec 7, 2021 │ No │
|
||||||
|
│ 📂 aprsd-slack-plugin │ Amateur radio APRS daemon which listens for messages and responds │ 1.0.5 │ Dec 18, 2022 │ No │
|
||||||
|
└──────────────────────────────┴────────────────────────────────────────────────────────────────────┴─────────┴──────────────┴────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
🐍 APRSD Installed 3rd party Plugins 🐍
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ Package Name ┃ Plugin Name ┃ Version ┃ Type ┃ Plugin Path ┃
|
||||||
|
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||||
|
│ aprsd-weewx-plugin │ WeewxMQTTPlugin │ 1.0 │ RegexCommand │ aprsd_weewx_plugin.weewx.WeewxMQTTPlugin │
|
||||||
|
└────────────────────┴─────────────────┴─────────┴──────────────┴──────────────────────────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
send-message
|
send-message
|
||||||
@ -289,6 +298,20 @@ LOCATION
|
|||||||
AND... ping, fortune, time.....
|
AND... ping, fortune, time.....
|
||||||
|
|
||||||
|
|
||||||
|
Web Admin Interface
|
||||||
|
===================
|
||||||
|
To start the web admin interface, You have to install gunicorn in your virtualenv that already has aprsd installed.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
source <path to APRSD's virtualenv>/bin/activate
|
||||||
|
pip install gunicorn
|
||||||
|
gunicorn --bind 0.0.0.0:8080 "aprsd.wsgi:app"
|
||||||
|
|
||||||
|
The web admin interface will be running on port 8080 on the local machine. http://localhost:8080
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
161
pyproject.toml
161
pyproject.toml
@ -1,5 +1,162 @@
|
|||||||
|
[project]
|
||||||
|
|
||||||
|
# This is the name of your project. The first time you publish this
|
||||||
|
# package, this name will be registered for you. It will determine how
|
||||||
|
# users can install this project, e.g.:
|
||||||
|
#
|
||||||
|
# $ pip install sampleproject
|
||||||
|
#
|
||||||
|
# And where it will live on PyPI: https://pypi.org/project/sampleproject/
|
||||||
|
#
|
||||||
|
# There are some restrictions on what makes a valid project name
|
||||||
|
# specification here:
|
||||||
|
# https://packaging.python.org/specifications/core-metadata/#name
|
||||||
|
name = "aprsd"
|
||||||
|
description = "APRSd is a APRS-IS server that can be used to connect to APRS-IS and send and receive APRS packets."
|
||||||
|
|
||||||
|
# Specify which Python versions you support. In contrast to the
|
||||||
|
# 'Programming Language' classifiers in this file, 'pip install' will check this
|
||||||
|
# and refuse to install the project if the version does not match. See
|
||||||
|
# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
|
dynamic = ["version", "dependencies", "optional-dependencies"]
|
||||||
|
|
||||||
|
# This is an optional longer description of your project that represents
|
||||||
|
# the body of text which users will see when they visit PyPI.
|
||||||
|
#
|
||||||
|
# Often, this is the same as your README, so you can just read it in from
|
||||||
|
# that file directly.
|
||||||
|
#
|
||||||
|
# This field corresponds to the "Description" metadata field:
|
||||||
|
# https://packaging.python.org/specifications/core-metadata/#description-optional
|
||||||
|
readme = {file = "README.rst", content-type = "text/x-rst"}
|
||||||
|
|
||||||
|
|
||||||
|
# This is either text indicating the license for the distribution, or a file
|
||||||
|
# that contains the license.
|
||||||
|
# https://packaging.python.org/en/latest/specifications/core-metadata/#license
|
||||||
|
license = {file = "LICENSE"}
|
||||||
|
|
||||||
|
# This should be your name or the name of the organization who originally
|
||||||
|
# authored the project, and a valid email address corresponding to the name
|
||||||
|
# listed.
|
||||||
|
authors = [
|
||||||
|
{name = "Craig Lamparter", email = "craig@craiger.org"},
|
||||||
|
{name = "Walter A. Boring IV", email = "waboring@hemna.com"},
|
||||||
|
{name = "Emre Saglam", email = "emresaglam@gmail.com"},
|
||||||
|
{name = "Jason Martin", email= "jhmartin@toger.us"},
|
||||||
|
{name = "John", email="johng42@users.noreply.github.com"},
|
||||||
|
{name = "Martiros Shakhzadyan", email="vrzh@vrzh.net"},
|
||||||
|
{name = "Zoe Moore", email="zoenb@mailbox.org"},
|
||||||
|
{name = "ranguli", email="hello@joshmurphy.ca"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# This should be your name or the names of the organization who currently
|
||||||
|
# maintains the project, and a valid email address corresponding to the name
|
||||||
|
# listed.
|
||||||
|
maintainers = [
|
||||||
|
{name = "Craig Lamparter", email = "craig@craiger.org"},
|
||||||
|
{name = "Walter A. Boring IV", email = "waboring@hemna.com"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# This field adds keywords for your project which will appear on the
|
||||||
|
# project page. What does your project relate to?
|
||||||
|
#
|
||||||
|
# Note that this is a list of additional keywords, separated
|
||||||
|
# by commas, to be used to assist searching for the distribution in a
|
||||||
|
# larger catalog.
|
||||||
|
keywords = [
|
||||||
|
"aprs",
|
||||||
|
"aprs-is",
|
||||||
|
"aprsd",
|
||||||
|
"aprsd-server",
|
||||||
|
"aprsd-client",
|
||||||
|
"aprsd-socket",
|
||||||
|
"aprsd-socket-server",
|
||||||
|
"aprsd-socket-client",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Classifiers help users find your project by categorizing it.
|
||||||
|
#
|
||||||
|
# For a list of valid classifiers, see https://pypi.org/classifiers/
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
"Intended Audience :: Information Technology",
|
||||||
|
"Topic :: Communications :: Ham Radio",
|
||||||
|
"Topic :: Internet",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
# This field lists other packages that your project depends on to run.
|
||||||
|
# Any package you put here will be installed by pip when your project is
|
||||||
|
# installed, so they must be valid existing projects.
|
||||||
|
#
|
||||||
|
# For an analysis of this field vs pip's requirements files see:
|
||||||
|
# https://packaging.python.org/discussions/install-requires-vs-requirements/
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
dependencies = {file = ["./requirements.txt"]}
|
||||||
|
optional-dependencies.dev = {file = ["./requirements-dev.txt"]}
|
||||||
|
|
||||||
|
# List additional groups of dependencies here (e.g. development
|
||||||
|
# dependencies). Users will be able to install these using the "extras"
|
||||||
|
# syntax, for example:
|
||||||
|
#
|
||||||
|
# $ pip install sampleproject[dev]
|
||||||
|
#
|
||||||
|
# Optional dependencies the project provides. These are commonly
|
||||||
|
# referred to as "extras". For a more extensive definition see:
|
||||||
|
# https://packaging.python.org/en/latest/specifications/dependency-specifiers/#extras
|
||||||
|
# [project.optional-dependencies]
|
||||||
|
|
||||||
|
# List URLs that are relevant to your project
|
||||||
|
#
|
||||||
|
# This field corresponds to the "Project-URL" and "Home-Page" metadata fields:
|
||||||
|
# https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use
|
||||||
|
# https://packaging.python.org/specifications/core-metadata/#home-page-optional
|
||||||
|
#
|
||||||
|
# Examples listed include a pattern for specifying where the package tracks
|
||||||
|
# issues, where the source is hosted, where to say thanks to the package
|
||||||
|
# maintainers, and where to support the project financially. The key is
|
||||||
|
# what's used to render the link text on PyPI.
|
||||||
|
[project.urls]
|
||||||
|
"Homepage" = "https://github.com/craigerl/aprsd"
|
||||||
|
"Bug Reports" = "https://github.com/craigerl/aprsd/issues"
|
||||||
|
"Source" = "https://github.com/craigerl/aprsd"
|
||||||
|
|
||||||
|
# The following would provide a command line executable called `sample`
|
||||||
|
# which executes the function `main` from this package when invoked.
|
||||||
|
[project.scripts]
|
||||||
|
aprsd = "aprsd.main:main"
|
||||||
|
|
||||||
|
[project.entry-points."oslo.config.opts"]
|
||||||
|
"aprsd.conf" = "aprsd.conf.opts:list_opts"
|
||||||
|
|
||||||
|
[project.entry-points."oslo.config.opts.defaults"]
|
||||||
|
"aprsd.conf" = "aprsd.conf:set_lib_defaults"
|
||||||
|
|
||||||
|
# If you are using a different build backend, you will need to change this.
|
||||||
|
[tool.setuptools]
|
||||||
|
# If there are data files included in your packages that need to be
|
||||||
|
# installed, specify them here.
|
||||||
|
py-modules = ["aprsd"]
|
||||||
|
package-data = {"sample" = ["*.dat"]}
|
||||||
|
packages = ["aprsd"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=46.0", "wheel"]
|
requires = [
|
||||||
|
"setuptools>=69.5.0",
|
||||||
|
"setuptools_scm>=0",
|
||||||
|
"wheel",
|
||||||
|
]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
@ -14,3 +171,5 @@ skip_gitignore = true
|
|||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
branch = true
|
branch = true
|
||||||
|
|
||||||
|
[tool.setuptools_scm]
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
|
build
|
||||||
|
check-manifest
|
||||||
flake8
|
flake8
|
||||||
|
gray
|
||||||
isort
|
isort
|
||||||
mypy
|
mypy
|
||||||
pep8-naming
|
pep8-naming
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
pip
|
||||||
|
pip-tools
|
||||||
|
pre-commit
|
||||||
Sphinx
|
Sphinx
|
||||||
tox
|
tox
|
||||||
|
wheel
|
||||||
|
|
||||||
# Twine is used for uploading packages to pypi
|
# Twine is used for uploading packages to pypi
|
||||||
# but it induces an install of cryptography
|
# but it induces an install of cryptography
|
||||||
# This is sucky for rpi systems.
|
# This is sucky for rpi systems.
|
||||||
# twine
|
# twine
|
||||||
pre-commit
|
|
||||||
pytest
|
# m2r is for converting .md files to .rst for the docs
|
||||||
pytest-cov
|
m2r
|
||||||
gray
|
|
||||||
pip
|
|
||||||
pip-tools
|
|
86
requirements-dev.txt
Normal file
86
requirements-dev.txt
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile --annotation-style=line requirements-dev.in
|
||||||
|
#
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
docutils==0.21.2 # via m2r, sphinx
|
||||||
|
exceptiongroup==1.2.2 # via pytest
|
||||||
|
filelock==3.16.0 # 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
|
||||||
|
identify==2.6.1 # via pre-commit
|
||||||
|
idna==3.10 # via requests
|
||||||
|
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
|
||||||
|
m2r==0.3.1 # via -r requirements-dev.in
|
||||||
|
markupsafe==2.1.5 # 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-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
|
||||||
|
pluggy==1.5.0 # via pytest, tox
|
||||||
|
pre-commit==3.8.0 # 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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
sphinxcontrib-applehelp==2.0.0 # via sphinx
|
||||||
|
sphinxcontrib-devhelp==2.0.0 # via sphinx
|
||||||
|
sphinxcontrib-htmlhelp==2.1.0 # via sphinx
|
||||||
|
sphinxcontrib-jsmath==1.0.1 # via sphinx
|
||||||
|
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
|
||||||
|
trailrunner==1.4.0 # via fixit
|
||||||
|
typing-extensions==4.12.2 # via black, mypy
|
||||||
|
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
|
||||||
|
wheel==0.44.0 # via -r requirements-dev.in, pip-tools
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# pip
|
||||||
|
# setuptools
|
@ -1,38 +1,32 @@
|
|||||||
aprslib>=0.7.0
|
aprslib>=0.7.0
|
||||||
click
|
|
||||||
click-params
|
|
||||||
flask
|
|
||||||
werkzeug
|
|
||||||
flask-httpauth
|
|
||||||
imapclient
|
|
||||||
pluggy
|
|
||||||
pbr
|
|
||||||
pyyaml
|
|
||||||
requests
|
|
||||||
pytz
|
|
||||||
tzlocal
|
|
||||||
six
|
|
||||||
thesmuggler
|
|
||||||
update_checker
|
|
||||||
flask-socketio
|
|
||||||
python-socketio
|
|
||||||
gevent
|
|
||||||
eventlet
|
|
||||||
tabulate
|
|
||||||
# Pinned due to gray needing 12.6.0
|
|
||||||
rich~=12.6.0
|
|
||||||
# For the list-plugins pypi.org search scraping
|
# For the list-plugins pypi.org search scraping
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
wrapt
|
click
|
||||||
# kiss3 uses attrs
|
click-params
|
||||||
kiss3
|
|
||||||
attrs
|
|
||||||
dataclasses
|
dataclasses
|
||||||
oslo.config
|
|
||||||
# Pin this here so it doesn't require a compile on
|
|
||||||
# raspi
|
|
||||||
shellingham
|
|
||||||
geopy
|
|
||||||
rush
|
|
||||||
dataclasses-json
|
dataclasses-json
|
||||||
|
eventlet
|
||||||
|
flask
|
||||||
|
flask-httpauth
|
||||||
|
flask-socketio
|
||||||
|
geopy
|
||||||
|
gevent
|
||||||
|
imapclient
|
||||||
|
kiss3
|
||||||
loguru
|
loguru
|
||||||
|
oslo.config
|
||||||
|
pluggy
|
||||||
|
python-socketio
|
||||||
|
pyyaml
|
||||||
|
pytz
|
||||||
|
requests
|
||||||
|
# Pinned due to gray needing 12.6.0
|
||||||
|
rich~=12.6.0
|
||||||
|
rush
|
||||||
|
shellingham
|
||||||
|
six
|
||||||
|
tabulate
|
||||||
|
thesmuggler
|
||||||
|
tzlocal
|
||||||
|
update_checker
|
||||||
|
wrapt
|
||||||
|
@ -5,78 +5,77 @@
|
|||||||
# pip-compile --annotation-style=line requirements.in
|
# pip-compile --annotation-style=line requirements.in
|
||||||
#
|
#
|
||||||
aprslib==0.7.2 # via -r requirements.in
|
aprslib==0.7.2 # via -r requirements.in
|
||||||
attrs==23.2.0 # via -r requirements.in, ax253, kiss3, rush
|
attrs==24.2.0 # via ax253, kiss3, rush
|
||||||
ax253==0.1.5.post1 # via kiss3
|
ax253==0.1.5.post1 # via kiss3
|
||||||
beautifulsoup4==4.12.3 # via -r requirements.in
|
beautifulsoup4==4.12.3 # via -r requirements.in
|
||||||
bidict==0.23.1 # via python-socketio
|
bidict==0.23.1 # via python-socketio
|
||||||
bitarray==2.9.2 # via ax253, kiss3
|
bitarray==2.9.2 # via ax253, kiss3
|
||||||
blinker==1.7.0 # via flask
|
blinker==1.8.2 # via flask
|
||||||
certifi==2024.2.2 # via requests
|
certifi==2024.8.30 # via requests
|
||||||
charset-normalizer==3.3.2 # via requests
|
charset-normalizer==3.3.2 # via requests
|
||||||
click==8.1.7 # via -r requirements.in, click-completion, click-params, flask
|
click==8.1.7 # via -r requirements.in, click-params, flask
|
||||||
click-completion==0.5.2 # via -r requirements.in
|
|
||||||
click-params==0.5.0 # via -r requirements.in
|
click-params==0.5.0 # via -r requirements.in
|
||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
dataclasses==0.6 # via -r requirements.in
|
dataclasses==0.6 # via -r requirements.in
|
||||||
dataclasses-json==0.6.4 # via -r requirements.in
|
dataclasses-json==0.6.7 # via -r requirements.in
|
||||||
debtcollector==3.0.0 # via oslo-config
|
debtcollector==3.0.0 # via oslo-config
|
||||||
deprecated==1.2.14 # via click-params
|
deprecated==1.2.14 # via click-params
|
||||||
dnspython==2.6.1 # via eventlet
|
dnspython==2.6.1 # via eventlet
|
||||||
eventlet==0.36.1 # via -r requirements.in
|
eventlet==0.37.0 # via -r requirements.in
|
||||||
flask==3.0.3 # via -r requirements.in, flask-httpauth, flask-socketio
|
flask==3.0.3 # via -r requirements.in, flask-httpauth, flask-socketio
|
||||||
flask-httpauth==4.8.0 # via -r requirements.in
|
flask-httpauth==4.8.0 # via -r requirements.in
|
||||||
flask-socketio==5.3.6 # via -r requirements.in
|
flask-socketio==5.3.7 # via -r requirements.in
|
||||||
geographiclib==2.0 # via geopy
|
geographiclib==2.0 # via geopy
|
||||||
geopy==2.4.1 # via -r requirements.in
|
geopy==2.4.1 # via -r requirements.in
|
||||||
gevent==24.2.1 # via -r requirements.in
|
gevent==24.2.1 # via -r requirements.in
|
||||||
greenlet==3.0.3 # via eventlet, gevent
|
greenlet==3.1.0 # via eventlet, gevent
|
||||||
h11==0.14.0 # via wsproto
|
h11==0.14.0 # via wsproto
|
||||||
idna==3.7 # via requests
|
idna==3.10 # via requests
|
||||||
imapclient==3.0.1 # via -r requirements.in
|
imapclient==3.0.1 # via -r requirements.in
|
||||||
importlib-metadata==7.1.0 # via ax253, kiss3
|
importlib-metadata==8.5.0 # via ax253, kiss3
|
||||||
itsdangerous==2.2.0 # via flask
|
itsdangerous==2.2.0 # via flask
|
||||||
jinja2==3.1.3 # via click-completion, flask
|
jinja2==3.1.4 # via flask
|
||||||
kiss3==8.0.0 # via -r requirements.in
|
kiss3==8.0.0 # via -r requirements.in
|
||||||
loguru==0.7.2 # via -r requirements.in
|
loguru==0.7.2 # via -r requirements.in
|
||||||
markupsafe==2.1.5 # via jinja2, werkzeug
|
markupsafe==2.1.5 # via jinja2, werkzeug
|
||||||
marshmallow==3.21.1 # via dataclasses-json
|
marshmallow==3.22.0 # via dataclasses-json
|
||||||
mypy-extensions==1.0.0 # via typing-inspect
|
mypy-extensions==1.0.0 # via typing-inspect
|
||||||
netaddr==1.2.1 # via oslo-config
|
netaddr==1.3.0 # via oslo-config
|
||||||
oslo-config==9.4.0 # via -r requirements.in
|
oslo-config==9.6.0 # via -r requirements.in
|
||||||
oslo-i18n==6.3.0 # via oslo-config
|
oslo-i18n==6.4.0 # via oslo-config
|
||||||
packaging==24.0 # via marshmallow
|
packaging==24.1 # via marshmallow
|
||||||
pbr==6.0.0 # via -r requirements.in, oslo-i18n, stevedore
|
pbr==6.1.0 # via oslo-i18n, stevedore
|
||||||
pluggy==1.5.0 # via -r requirements.in
|
pluggy==1.5.0 # via -r requirements.in
|
||||||
pygments==2.17.2 # via rich
|
pygments==2.18.0 # via rich
|
||||||
pyserial==3.5 # via pyserial-asyncio
|
pyserial==3.5 # via pyserial-asyncio
|
||||||
pyserial-asyncio==0.6 # via kiss3
|
pyserial-asyncio==0.6 # via kiss3
|
||||||
python-engineio==4.9.0 # via python-socketio
|
python-engineio==4.9.1 # via python-socketio
|
||||||
python-socketio==5.11.2 # via -r requirements.in, flask-socketio
|
python-socketio==5.11.4 # via -r requirements.in, flask-socketio
|
||||||
pytz==2024.1 # via -r requirements.in
|
pytz==2024.2 # via -r requirements.in
|
||||||
pyyaml==6.0.1 # via -r requirements.in, oslo-config
|
pyyaml==6.0.2 # via -r requirements.in, oslo-config
|
||||||
requests==2.31.0 # via -r requirements.in, oslo-config, update-checker
|
requests==2.32.3 # via -r requirements.in, oslo-config, update-checker
|
||||||
rfc3986==2.0.0 # via oslo-config
|
rfc3986==2.0.0 # via oslo-config
|
||||||
rich==12.6.0 # via -r requirements.in
|
rich==12.6.0 # via -r requirements.in
|
||||||
rush==2021.4.0 # via -r requirements.in
|
rush==2021.4.0 # via -r requirements.in
|
||||||
shellingham==1.5.4 # via -r requirements.in, click-completion
|
shellingham==1.5.4 # via -r requirements.in
|
||||||
simple-websocket==1.0.0 # via python-engineio
|
simple-websocket==1.0.0 # via python-engineio
|
||||||
six==1.16.0 # via -r requirements.in, click-completion
|
six==1.16.0 # via -r requirements.in
|
||||||
soupsieve==2.5 # via beautifulsoup4
|
soupsieve==2.6 # via beautifulsoup4
|
||||||
stevedore==5.2.0 # via oslo-config
|
stevedore==5.3.0 # via oslo-config
|
||||||
tabulate==0.9.0 # via -r requirements.in
|
tabulate==0.9.0 # via -r requirements.in
|
||||||
thesmuggler==1.0.1 # via -r requirements.in
|
thesmuggler==1.0.1 # via -r requirements.in
|
||||||
typing-extensions==4.11.0 # via typing-inspect
|
typing-extensions==4.12.2 # via typing-inspect
|
||||||
typing-inspect==0.9.0 # via dataclasses-json
|
typing-inspect==0.9.0 # via dataclasses-json
|
||||||
tzlocal==5.2 # via -r requirements.in
|
tzlocal==5.2 # via -r requirements.in
|
||||||
update-checker==0.18.0 # via -r requirements.in
|
update-checker==0.18.0 # via -r requirements.in
|
||||||
urllib3==2.2.1 # via requests
|
urllib3==2.2.3 # via requests
|
||||||
validators==0.22.0 # via click-params
|
validators==0.22.0 # via click-params
|
||||||
werkzeug==3.0.2 # via -r requirements.in, flask
|
werkzeug==3.0.4 # via flask
|
||||||
wrapt==1.16.0 # via -r requirements.in, debtcollector, deprecated
|
wrapt==1.16.0 # via -r requirements.in, debtcollector, deprecated
|
||||||
wsproto==1.2.0 # via simple-websocket
|
wsproto==1.2.0 # via simple-websocket
|
||||||
zipp==3.18.1 # via importlib-metadata
|
zipp==3.20.2 # via importlib-metadata
|
||||||
zope-event==5.0 # via gevent
|
zope-event==5.0 # via gevent
|
||||||
zope-interface==6.3 # via gevent
|
zope-interface==7.0.3 # via gevent
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
# setuptools
|
# setuptools
|
||||||
|
51
setup.cfg
51
setup.cfg
@ -1,51 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = aprsd
|
|
||||||
long_description = file: README.rst
|
|
||||||
long_description_content_type = text/x-rst
|
|
||||||
url = http://aprsd.readthedocs.org
|
|
||||||
author = Craig Lamparter
|
|
||||||
author_email = something@somewhere.com
|
|
||||||
license = Apache
|
|
||||||
license_file = LICENSE
|
|
||||||
classifier =
|
|
||||||
License :: OSI Approved :: Apache Software License
|
|
||||||
Topic :: Communications :: Ham Radio
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Programming Language :: Python :: 3 :: Only
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.7
|
|
||||||
Programming Language :: Python :: 3.8
|
|
||||||
Programming Language :: Python :: 3.9
|
|
||||||
description_file =
|
|
||||||
README.rst
|
|
||||||
project_urls =
|
|
||||||
Source=https://github.com/craigerl/aprsd
|
|
||||||
Tracker=https://github.com/craigerl/aprsd/issues
|
|
||||||
summary = Amateur radio APRS daemon which listens for messages and responds
|
|
||||||
|
|
||||||
[global]
|
|
||||||
setup-hooks =
|
|
||||||
pbr.hooks.setup_hook
|
|
||||||
|
|
||||||
[files]
|
|
||||||
packages =
|
|
||||||
aprsd
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
console_scripts =
|
|
||||||
aprsd = aprsd.main:main
|
|
||||||
oslo.config.opts =
|
|
||||||
aprsd.conf = aprsd.conf.opts:list_opts
|
|
||||||
oslo.config.opts.defaults =
|
|
||||||
aprsd.conf = aprsd.conf:set_lib_defaults
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
source-dir = docs
|
|
||||||
build-dir = docs/_build
|
|
||||||
all_files = 1
|
|
||||||
|
|
||||||
[upload_sphinx]
|
|
||||||
upload-dir = docs/_build
|
|
||||||
|
|
||||||
[bdist_wheel]
|
|
||||||
universal = 1
|
|
12
setup.py
12
setup.py
@ -1,5 +1,3 @@
|
|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
@ -17,12 +15,4 @@
|
|||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
setuptools.setup()
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(setup_requires=["pbr"], pbr=True)
|
|
||||||
|
@ -62,9 +62,9 @@ class TestAPRSDWatchListPluginBase(TestWatchListPlugin):
|
|||||||
expected = packets.NULL_MESSAGE
|
expected = packets.NULL_MESSAGE
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||||
def test_watchlist_not_in_watchlist(self, mock_factory):
|
def test_watchlist_not_in_watchlist(self, mock_factory):
|
||||||
client.factory = mock_factory
|
client.client_factory = mock_factory
|
||||||
self.config_and_init()
|
self.config_and_init()
|
||||||
plugin = fake.FakeWatchListPlugin()
|
plugin = fake.FakeWatchListPlugin()
|
||||||
|
|
||||||
@ -92,9 +92,9 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
|||||||
expected = packets.NULL_MESSAGE
|
expected = packets.NULL_MESSAGE
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||||
def test_callsign_not_in_watchlist(self, mock_factory):
|
def test_callsign_not_in_watchlist(self, mock_factory):
|
||||||
client.factory = mock_factory
|
client.client_factory = mock_factory
|
||||||
self.config_and_init(watchlist_enabled=False)
|
self.config_and_init(watchlist_enabled=False)
|
||||||
plugin = notify_plugin.NotifySeenPlugin()
|
plugin = notify_plugin.NotifySeenPlugin()
|
||||||
|
|
||||||
@ -106,10 +106,10 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
|||||||
expected = packets.NULL_MESSAGE
|
expected = packets.NULL_MESSAGE
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||||
@mock.patch("aprsd.packets.WatchList.is_old")
|
@mock.patch("aprsd.packets.WatchList.is_old")
|
||||||
def test_callsign_in_watchlist_not_old(self, mock_is_old, mock_factory):
|
def test_callsign_in_watchlist_not_old(self, mock_is_old, mock_factory):
|
||||||
client.factory = mock_factory
|
client.client_factory = mock_factory
|
||||||
mock_is_old.return_value = False
|
mock_is_old.return_value = False
|
||||||
self.config_and_init(
|
self.config_and_init(
|
||||||
watchlist_enabled=True,
|
watchlist_enabled=True,
|
||||||
@ -126,10 +126,10 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
|||||||
expected = packets.NULL_MESSAGE
|
expected = packets.NULL_MESSAGE
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||||
@mock.patch("aprsd.packets.WatchList.is_old")
|
@mock.patch("aprsd.packets.WatchList.is_old")
|
||||||
def test_callsign_in_watchlist_old_same_alert_callsign(self, mock_is_old, mock_factory):
|
def test_callsign_in_watchlist_old_same_alert_callsign(self, mock_is_old, mock_factory):
|
||||||
client.factory = mock_factory
|
client.client_factory = mock_factory
|
||||||
mock_is_old.return_value = True
|
mock_is_old.return_value = True
|
||||||
self.config_and_init(
|
self.config_and_init(
|
||||||
watchlist_enabled=True,
|
watchlist_enabled=True,
|
||||||
@ -147,10 +147,10 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
|||||||
expected = packets.NULL_MESSAGE
|
expected = packets.NULL_MESSAGE
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||||
@mock.patch("aprsd.packets.WatchList.is_old")
|
@mock.patch("aprsd.packets.WatchList.is_old")
|
||||||
def test_callsign_in_watchlist_old_send_alert(self, mock_is_old, mock_factory):
|
def test_callsign_in_watchlist_old_send_alert(self, mock_is_old, mock_factory):
|
||||||
client.factory = mock_factory
|
client.client_factory = mock_factory
|
||||||
mock_is_old.return_value = True
|
mock_is_old.return_value = True
|
||||||
notify_callsign = fake.FAKE_TO_CALLSIGN
|
notify_callsign = fake.FAKE_TO_CALLSIGN
|
||||||
fromcall = "WB4BOR"
|
fromcall = "WB4BOR"
|
||||||
|
12
tox.ini
12
tox.ini
@ -2,7 +2,7 @@
|
|||||||
minversion = 2.9.0
|
minversion = 2.9.0
|
||||||
skipdist = True
|
skipdist = True
|
||||||
skip_missing_interpreters = true
|
skip_missing_interpreters = true
|
||||||
envlist = pep8,py{39,310}
|
envlist = pep8,py{310}
|
||||||
#requires = tox-pipenv
|
#requires = tox-pipenv
|
||||||
# pip==22.0.4
|
# pip==22.0.4
|
||||||
# pip-tools==5.4.0
|
# pip-tools==5.4.0
|
||||||
@ -23,7 +23,7 @@ install_command = pip install {opts} {packages}
|
|||||||
extras = tests
|
extras = tests
|
||||||
deps = coverage: coverage
|
deps = coverage: coverage
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/dev-requirements.txt
|
-r{toxinidir}/requirements-dev.txt
|
||||||
pytestmain: git+https://github.com/pytest-dev/pytest.git@main
|
pytestmain: git+https://github.com/pytest-dev/pytest.git@main
|
||||||
commands =
|
commands =
|
||||||
pytest -v --cov-report term-missing --cov=aprsd {posargs}
|
pytest -v --cov-report term-missing --cov=aprsd {posargs}
|
||||||
@ -34,7 +34,7 @@ commands =
|
|||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/dev-requirements.txt
|
-r{toxinidir}/requirements-dev.txt
|
||||||
{toxinidir}/.
|
{toxinidir}/.
|
||||||
changedir = {toxinidir}/docs
|
changedir = {toxinidir}/docs
|
||||||
commands =
|
commands =
|
||||||
@ -57,7 +57,7 @@ passenv = FAST8_NUM_COMMITS
|
|||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/dev-requirements.txt
|
-r{toxinidir}/requirements-dev.txt
|
||||||
commands =
|
commands =
|
||||||
flake8 aprsd tests
|
flake8 aprsd tests
|
||||||
|
|
||||||
@ -85,14 +85,14 @@ python =
|
|||||||
# and standard formatting
|
# and standard formatting
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/dev-requirements.txt
|
-r{toxinidir}/requirements-dev.txt
|
||||||
commands =
|
commands =
|
||||||
gray aprsd tests
|
gray aprsd tests
|
||||||
|
|
||||||
[testenv:type-check]
|
[testenv:type-check]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/dev-requirements.txt
|
-r{toxinidir}/requirements-dev.txt
|
||||||
commands =
|
commands =
|
||||||
mypy --ignore-missing-imports --install-types aprsd
|
mypy --ignore-missing-imports --install-types aprsd
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user