mirror of
https://github.com/craigerl/aprsd.git
synced 2025-06-21 11:16:27 -04:00
Compare commits
30 Commits
72d068c0b8
...
375a5e5b34
Author | SHA1 | Date | |
---|---|---|---|
375a5e5b34 | |||
cf4a29f0cb | |||
447451c6c9 | |||
0ed648f8f8 | |||
ba8acdc584 | |||
dabb48c6f6 | |||
1054999568 | |||
044ea4cc9a | |||
24db814c82 | |||
1cba31f0ac | |||
02e29405ce | |||
cdd297c5bb | |||
e332d7c9d0 | |||
e4f82d6054 | |||
8d98546055 | |||
3ee422b5c9 | |||
7702d68cf7 | |||
c8735c257a | |||
3cd9bfa7bb | |||
007386505a | |||
7f2c1d7124 | |||
fbec7168eb | |||
e3a7e7fb8a | |||
a21432fb24 | |||
275e33538d | |||
5274c5dc56 | |||
df14eb8f28 | |||
fd74405b5f | |||
ec1adf4182 | |||
b1a830d54e |
15
.github/workflows/authors.yml
vendored
Normal file
15
.github/workflows/authors.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
name: Update Authors
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: wow-actions/update-authors@v1
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
template: '{{email}} : {{commits}}'
|
||||||
|
path: 'AUTHORS'
|
9
.github/workflows/manual_build.yml
vendored
9
.github/workflows/manual_build.yml
vendored
@ -18,7 +18,6 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Get Branch Name
|
- name: Get Branch Name
|
||||||
id: branch-name
|
id: branch-name
|
||||||
uses: tj-actions/branch-names@v8
|
uses: tj-actions/branch-names@v8
|
||||||
@ -30,16 +29,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "Selected Branch '${{ steps.extract_branch.outputs.branch }}'"
|
echo "Selected Branch '${{ steps.extract_branch.outputs.branch }}'"
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to Docker HUB
|
- name: Login to Docker HUB
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Build the Docker image
|
- name: Build the Docker image
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: "{{defaultContext}}:docker"
|
context: "{{defaultContext}}:docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
13
.github/workflows/master-build.yml
vendored
13
.github/workflows/master-build.yml
vendored
@ -7,7 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
tags:
|
tags:
|
||||||
- "v*.*.*"
|
- "*.*.*"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "master"
|
- "master"
|
||||||
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10", "3.11"]
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
@ -35,21 +35,20 @@ jobs:
|
|||||||
needs: tox
|
needs: tox
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Get Branch Name
|
- name: Get Branch Name
|
||||||
id: branch-name
|
id: branch-name
|
||||||
uses: tj-actions/branch-names@v8
|
uses: tj-actions/branch-names@v8
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to Docker HUB
|
- name: Login to Docker HUB
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Build the Docker image
|
- name: Build the Docker image
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: "{{defaultContext}}:docker"
|
context: "{{defaultContext}}:docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
4
.github/workflows/python.yml
vendored
4
.github/workflows/python.yml
vendored
@ -9,9 +9,9 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10", "3.11"]
|
python-version: ["3.10", "3.11"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
4
.mailmap
Normal file
4
.mailmap
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Craig Lamparter <craig@craiger.org> <craiger@hpe.com>
|
||||||
|
Craig Lamparter <craig@craiger.org> craigerl <craig@craiger.org>
|
||||||
|
Craig Lamparter <craig@craiger.org> craigerl <craiger@hpe.com>
|
||||||
|
Walter A. Boring IV <waboring@hemna.com> Hemna <waboring@hemna.com>
|
@ -13,12 +13,12 @@ repos:
|
|||||||
- id: check-illegal-windows-names
|
- id: check-illegal-windows-names
|
||||||
|
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||||
rev: v2.5.0
|
rev: v2.7.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.8.4
|
rev: v0.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
###### Relevant part below ######
|
###### Relevant part below ######
|
||||||
@ -26,3 +26,16 @@ repos:
|
|||||||
args: ["check", "--select", "I", "--fix"]
|
args: ["check", "--select", "I", "--fix"]
|
||||||
###### Relevant part above ######
|
###### Relevant part above ######
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
|
# uv version.
|
||||||
|
rev: 0.5.16
|
||||||
|
hooks:
|
||||||
|
# Compile requirements
|
||||||
|
- id: pip-compile
|
||||||
|
name: pip-compile requirements.in
|
||||||
|
args: [--resolver, backtracking, --annotation-style=line, requirements.in, -o, requirements.txt]
|
||||||
|
- id: pip-compile
|
||||||
|
name: pip-compile requirements-dev.in
|
||||||
|
args: [--resolver, backtracking, --annotation-style=line, requirements-dev.in, -o, requirements-dev.txt]
|
||||||
|
files: ^requirements-dev\.(in|txt)$
|
||||||
|
31
CONTRIBUTING.md
Normal file
31
CONTRIBUTING.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# CONTRIBUTING
|
||||||
|
|
||||||
|
Code contributions are welcomed and appreciated. Just submit a PR!
|
||||||
|
|
||||||
|
The current build environment uses `pre-commit`, and `uv`.
|
||||||
|
|
||||||
|
### Environment setup:
|
||||||
|
|
||||||
|
```console
|
||||||
|
pip install uv
|
||||||
|
uv venv
|
||||||
|
uv pip install pip-tools
|
||||||
|
git clone git@github.com:craigerl/aprsd.git
|
||||||
|
cd aprsd
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
|
# Optionally run the pre-commit scripts at any time
|
||||||
|
pre-commit run --all-files
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running and testing:
|
||||||
|
|
||||||
|
From the aprstastic directory:
|
||||||
|
|
||||||
|
```console
|
||||||
|
cd aprsd
|
||||||
|
uv pip install -e .
|
||||||
|
|
||||||
|
# Running
|
||||||
|
uv run aprsd
|
||||||
|
```
|
454
README.md
Normal file
454
README.md
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
# APRSD - Ham radio APRS-IS Message platform software
|
||||||
|
|
||||||
|
## KM6LYW and WB4BOR
|
||||||
|
|
||||||
|
[](https://badge.fury.io/py/aprsd)
|
||||||
|
[](https://pypi.org/pypi/aprsd)
|
||||||
|
[](https://hemna.slack.com/app_redirect?channel=C01KQSCP5RP)
|
||||||
|

|
||||||
|

|
||||||
|
[](https://timothycrosley.github.io/isort/)
|
||||||
|
[](https://pepy.tech/project/aprsd)
|
||||||
|
|
||||||
|
[APRSD](http://github.com/craigerl/aprsd) is a Ham radio
|
||||||
|
[APRS](http://aprs.org) message platform built with python.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
1. [APRSD - Ham radio APRS-IS Message platform software](#aprsd---ham-radio-aprs-is-message-platform-software)
|
||||||
|
2. [What is APRSD](#what-is-aprsd)
|
||||||
|
3. [APRSD Plugins/Extensions](#aprsd-pluginsextensions)
|
||||||
|
4. [List of existing plugins - APRS Message processing/responders](#list-of-existing-plugins---aprs-message-processingresponders)
|
||||||
|
5. [List of existing extensions - Add new capabilities to APRSD](#list-of-existing-extensions---add-new-capabilities-to-aprsd)
|
||||||
|
6. [APRSD Overview Diagram](#aprsd-overview-diagram)
|
||||||
|
7. [Typical use case](#typical-use-case)
|
||||||
|
8. [Installation](#installation)
|
||||||
|
9. [Example usage](#example-usage)
|
||||||
|
10. [Help](#help)
|
||||||
|
11. [Commands](#commands)
|
||||||
|
12. [Configuration](#configuration)
|
||||||
|
13. [server](#server)
|
||||||
|
14. [Current list plugins](#current-list-plugins)
|
||||||
|
15. [Current list extensions](#current-list-extensions)
|
||||||
|
16. [send-message](#send-message)
|
||||||
|
17. [Development](#development)
|
||||||
|
18. [Release](#release)
|
||||||
|
19. [Building your own APRSD plugins](#building-your-own-aprsd-plugins)
|
||||||
|
20. [Overview](#overview)
|
||||||
|
21. [Docker Container](#docker-container)
|
||||||
|
22. [Building](#building)
|
||||||
|
23. [Official Build](#official-build)
|
||||||
|
24. [Development Build](#development-build)
|
||||||
|
25. [Running the container](#running-the-container)
|
||||||
|
26. [Activity](#activity)
|
||||||
|
27. [Star History](#star-history)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Legal operation of this software requires an amateur radio license and a valid call sign.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Star this repo to follow our progress! This code is under active development, and contributions are both welcomed and appreciated. See [CONTRIBUTING.md](<https://github.com/craigerl/aprsd/blob/master/CONTRIBUTING.md>) for details.
|
||||||
|
|
||||||
|
### What is APRSD
|
||||||
|
|
||||||
|
APRSD is a python application for interacting with the APRS network and Ham radios with KISS interfaces and
|
||||||
|
providing APRS services for HAM radio operators.
|
||||||
|
|
||||||
|
APRSD currently has 4 main commands to use.
|
||||||
|
|
||||||
|
- server - Connect to APRS and listen/respond to APRS messages
|
||||||
|
- send-message - Send a message to a callsign via APRS_IS.
|
||||||
|
- listen - Listen to packets on the APRS-IS Network based on FILTER.
|
||||||
|
- check-version - check the version of aprsd
|
||||||
|
- sample-config - generate a sample config file
|
||||||
|
- dev - helpful for testing new aprsd plugins under development
|
||||||
|
- dump-stats - output the stats of a running aprsd server command
|
||||||
|
- list-plugins - list the built in plugins, available plugins on pypi.org and installed plugins
|
||||||
|
- list-extensions - list the available extensions on pypi.org and installed extensions
|
||||||
|
|
||||||
|
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 directly.
|
||||||
|
|
||||||
|
Please [read the docs](https://aprsd.readthedocs.io) to learn more!
|
||||||
|
|
||||||
|
|
||||||
|
### APRSD Plugins/Extensions
|
||||||
|
|
||||||
|
APRSD Has the ability to add plugins and extensions. Plugins add new message filters that can look for specific messages and respond. For example, the aprsd-email-plugin adds the ability to send/recieve email to/from an APRS callsign. Extensions add new unique capabilities to APRSD itself. For example the aprsd-admin-extension adds a web interface command that shows the running status of the aprsd server command. aprsd-webchat-extension is a new web based APRS 'chat' command.
|
||||||
|
|
||||||
|
You can see the [available plugins/extensions on pypi here:](https://pypi.org/search/?q=aprsd) [https://pypi.org/search/?q=aprsd](https://pypi.org/search/?q=aprsd)
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> aprsd admin and webchat commands have been extracted into separate extensions.
|
||||||
|
* [See admin extension here](https://github.com/hemna/aprsd-admin-extension) <div id="admin logo" align="left"><img src="https://raw.githubusercontent.com/hemna/aprsd-admin-extension/refs/heads/master/screenshot.png" alt="Web Admin" width="340"/></div>
|
||||||
|
|
||||||
|
* [See webchat extension here](https://github.com/hemna/aprsd-webchat-extension) <div id="webchat logo" align="left"><img src="https://raw.githubusercontent.com/hemna/aprsd-webchat-extension/master/screenshot.png" alt="Webchat" width="340"/></div>
|
||||||
|
|
||||||
|
|
||||||
|
### List of existing plugins - APRS Message processing/responders
|
||||||
|
|
||||||
|
- [aprsd-email-plugin](https://github.com/hemna/aprsd-email-plugin) - send/receive email!
|
||||||
|
- [aprsd-location-plugin](https://github.com/hemna/aprsd-location-plugin) - get latest GPS location.
|
||||||
|
- [aprsd-locationdata-plugin](https://github.com/hemna/aprsd-locationdata-plugin) - get latest GPS location
|
||||||
|
- [aprsd-digipi-plugin](https://github.com/hemna/aprsd-digipi-plugin) - Look for digipi beacon packets
|
||||||
|
- [aprsd-w3w-plugin](https://github.com/hemna/aprsd-w3w-plugin) - get your w3w coordinates
|
||||||
|
- [aprsd-mqtt-plugin](https://github.com/hemna/aprsd-mqtt-plugin) - send aprs packets to an MQTT topic
|
||||||
|
- [aprsd-telegram-plugin](https://github.com/hemna/aprsd-telegram-plugin) - send/receive messages to telegram
|
||||||
|
- [aprsd-borat-plugin](https://github.com/hemna/aprsd-borat-plugin) - get Borat quotes
|
||||||
|
- [aprsd-wxnow-plugin](https://github.com/hemna/aprsd-wxnow-plugin) - get closest N weather station reports
|
||||||
|
- [aprsd-weewx-plugin](https://github.com/hemna/aprsd-weewx-plugin) - get weather from your weewx weather station
|
||||||
|
- [aprsd-slack-plugin](https://github.com/hemna/aprsd-slack-plugin) - send/receive messages to a slack channel
|
||||||
|
- [aprsd-sentry-plugin](https://github.com/hemna/aprsd-sentry-plugin) -
|
||||||
|
- [aprsd-repeat-plugins](https://github.com/hemna/aprsd-repeat-plugins) - plugins for the REPEAT service. Get nearest Ham radio repeaters!
|
||||||
|
- [aprsd-twitter-plugin](https://github.com/hemna/aprsd-twitter-plugin) - make tweets from your Ham Radio!
|
||||||
|
- [aprsd-timeopencage-plugin](https://github.com/hemna/aprsd-timeopencage-plugin) - Get local time for a callsign
|
||||||
|
- [aprsd-stock-plugin](https://github.com/hemna/aprsd-stock-plugin) - get stock quotes from your Ham radio
|
||||||
|
|
||||||
|
### List of existing extensions - Add new capabilities to APRSD
|
||||||
|
|
||||||
|
- [aprsd-admin-extension](https://github.com/hemna/aprsd-admin-extension) - Web Administration page for APRSD
|
||||||
|
- [aprsd-webchat-extension](https://github.com/hemna/aprsd-webchat-extension) - Web page for APRS Messaging
|
||||||
|
- [aprsd-irc-extension](https://github.com/hemna/aprsd-irc-extension) - an IRC like server command for APRS
|
||||||
|
|
||||||
|
### APRSD Overview Diagram
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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 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 for processing. For example, the WeatherPlugin picks up the
|
||||||
|
message, fetches the weather for the area around the user who sent the
|
||||||
|
request, and then responds with the weather conditions in that area.
|
||||||
|
Also includes a watch list of HAM 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.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
To install `aprsd`, use Pip:
|
||||||
|
|
||||||
|
`pip install aprsd`
|
||||||
|
|
||||||
|
### Example usage
|
||||||
|
|
||||||
|
`aprsd -h`
|
||||||
|
|
||||||
|
### Help
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
|
└─> aprsd -h
|
||||||
|
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version Show the version and exit.
|
||||||
|
-h, --help Show this message and exit.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
check-version Check this version against the latest in pypi.org.
|
||||||
|
completion Show the shell completion code
|
||||||
|
dev Development type subcommands
|
||||||
|
fetch-stats Fetch stats from a APRSD admin web interface.
|
||||||
|
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.
|
||||||
|
listen Listen to packets on the APRS-IS Network based on FILTER.
|
||||||
|
sample-config Generate a sample Config file from aprsd and all...
|
||||||
|
send-message Send a message to a callsign via APRS_IS.
|
||||||
|
server Start the aprsd server gateway process.
|
||||||
|
version Show the APRSD version.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
This command outputs a sample config yml formatted block that you can
|
||||||
|
edit and use to pass in to `aprsd` with `-c`. By default aprsd looks in
|
||||||
|
`~/.config/aprsd/aprsd.yml`
|
||||||
|
|
||||||
|
`aprsd sample-config`
|
||||||
|
|
||||||
|
└─> aprsd sample-config
|
||||||
|
...
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
This is the main server command that will listen to APRS-IS servers and
|
||||||
|
look for incomming commands to the callsign configured in the config
|
||||||
|
file
|
||||||
|
|
||||||
|
└─[$] > aprsd server --help
|
||||||
|
Usage: aprsd server [OPTIONS]
|
||||||
|
|
||||||
|
Start the aprsd server gateway process.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
||||||
|
The log level to use for aprsd.log
|
||||||
|
[default: INFO]
|
||||||
|
-c, --config TEXT The aprsd config file to use for options.
|
||||||
|
[default:
|
||||||
|
/Users/i530566/.config/aprsd/aprsd.yml]
|
||||||
|
--quiet Don't log to stdout
|
||||||
|
-f, --flush Flush out all old aged messages on disk.
|
||||||
|
[default: False]
|
||||||
|
-h, --help Show this message and exit.
|
||||||
|
|
||||||
|
└─> aprsd server
|
||||||
|
Registering LogMonitorThread
|
||||||
|
2025-01-06 16:27:12.398 | MainThread | INFO | APRSD is up to date | aprsd.cmds.server:server:82
|
||||||
|
2025-01-06 16:27:12.398 | MainThread | INFO | APRSD Started version: 3.5.1.dev0+g72d068c.d20250102 | aprsd.cmds.server:server:83
|
||||||
|
2025-01-06 16:27:12.398 | MainThread | INFO | Creating client connection | aprsd.cmds.server:server:101
|
||||||
|
2025-01-06 16:27:12.398 | MainThread | INFO | Creating aprslib client(noam.aprs2.net:14580) and logging in WB4BOR-1. | aprsd.client.aprsis:setup_connection:136
|
||||||
|
2025-01-06 16:27:12.398 | MainThread | INFO | Attempting connection to noam.aprs2.net:14580 | aprslib.inet:_connect:226
|
||||||
|
2025-01-06 16:27:12.473 | MainThread | INFO | Connected to ('44.135.208.225', 14580) | aprslib.inet:_connect:233
|
||||||
|
2025-01-06 16:27:12.617 | MainThread | INFO | Login successful | aprsd.client.drivers.aprsis:_send_login:154
|
||||||
|
2025-01-06 16:27:12.618 | MainThread | INFO | Connected to T2BC | aprsd.client.drivers.aprsis:_send_login:156
|
||||||
|
2025-01-06 16:27:12.618 | MainThread | INFO | <aprsd.client.aprsis.APRSISClient object at 0x103a36480> | aprsd.cmds.server:server:103
|
||||||
|
2025-01-06 16:27:12.618 | MainThread | INFO | Loading Plugin Manager and registering plugins | aprsd.cmds.server:server:117
|
||||||
|
2025-01-06 16:27:12.619 | MainThread | INFO | Loading APRSD Plugins | aprsd.plugin:setup_plugins:492
|
||||||
|
|
||||||
|
|
||||||
|
#### Current list 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 │
|
||||||
|
│ FortunePlugin │ Give me a fortune │ RegexCommand │ aprsd.plugins.fortune.FortunePlugin │
|
||||||
|
│ 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 │
|
||||||
|
│ 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-assistant-plugin │ APRSd plugin for hosting the APRS Assistant chatbot │ 0.0.3 │ 2024-10-20T02:59:39 │ No │
|
||||||
|
│ │ (aprs-assistant) │ │ │ │
|
||||||
|
│ 📂 aprsd-borat-plugin │ Borat quotes for aprsd plugin │ 0.1.1.dev1 │ 2024-01-19T16:04:38 │ No │
|
||||||
|
│ 📂 aprsd-locationdata-plugin │ Fetch location information from a callsign │ 0.3.0 │ 2024-02-06T17:20:43 │ No │
|
||||||
|
│ 📂 aprsd-mqtt-plugin │ APRSD MQTT Plugin sends APRS packets to mqtt queue │ 0.2.0 │ 2023-04-17T16:01:50 │ No │
|
||||||
|
│ 📂 aprsd-repeat-plugins │ APRSD Plugins for the REPEAT service │ 1.2.0 │ 2023-01-10T17:15:36 │ No │
|
||||||
|
│ 📂 aprsd-sentry-plugin │ Ham radio APRSD plugin that does.... │ 0.1.2 │ 2022-12-02T19:07:33 │ No │
|
||||||
|
│ 📂 aprsd-slack-plugin │ Amateur radio APRS daemon which listens for messages and │ 1.2.0 │ 2023-01-10T19:21:33 │ No │
|
||||||
|
│ │ responds │ │ │ │
|
||||||
|
│ 📂 aprsd-stock-plugin │ Ham Radio APRSD Plugin for fetching stock quotes │ 0.1.3 │ 2022-12-02T18:56:19 │ Yes │
|
||||||
|
│ 📂 aprsd-telegram-plugin │ Ham Radio APRS APRSD plugin for Telegram IM service │ 0.1.3 │ 2022-12-02T19:07:15 │ No │
|
||||||
|
│ 📂 aprsd-timeopencage-plugin │ APRSD plugin for fetching time based on GPS location │ 0.2.0 │ 2023-01-10T17:07:11 │ No │
|
||||||
|
│ 📂 aprsd-twitter-plugin │ Python APRSD plugin to send tweets │ 0.5.0 │ 2023-01-10T16:51:47 │ No │
|
||||||
|
│ 📂 aprsd-weewx-plugin │ HAM Radio APRSD that reports weather from a weewx weather │ 0.3.2 │ 2023-04-20T20:16:19 │ No │
|
||||||
|
│ │ station. │ │ │ │
|
||||||
|
│ 📂 aprsd-wxnow-plugin │ APRSD Plugin for getting the closest wx reports to last │ 0.2.0 │ 2023-10-08T01:27:29 │ Yes │
|
||||||
|
│ │ beacon │ │ │ │
|
||||||
|
└──────────────────────────────┴──────────────────────────────────────────────────────────────┴────────────┴─────────────────────┴────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
🐍 APRSD Installed 3rd party Plugins 🐍
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ Package Name ┃ Plugin Name ┃ Version ┃ Type ┃ Plugin Path ┃
|
||||||
|
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||||
|
│ aprsd-stock-plugin │ YahooStockQuote │ 0.1.3 │ RegexCommand │ aprsd_stock_plugin.stock.YahooStockQuote │
|
||||||
|
│ aprsd-wxnow-plugin │ WXNowPlugin │ 0.2.0 │ RegexCommand │ aprsd_wxnow_plugin.conf.opts.WXNowPlugin │
|
||||||
|
└────────────────────┴─────────────────┴─────────┴──────────────┴──────────────────────────────────────────┘
|
||||||
|
|
||||||
|
#### Current list extensions
|
||||||
|
└─> aprsd list-extensions
|
||||||
|
|
||||||
|
|
||||||
|
Pypi.org APRSD Installable Extension Packages
|
||||||
|
|
||||||
|
Install any of the following extensions by running
|
||||||
|
'pip install <Plugin Package Name>'
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
||||||
|
┃ Extension Package Name ┃ Description ┃ Version ┃ Released ┃ Installed? ┃
|
||||||
|
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
|
||||||
|
│ 📂 aprsd-admin-extension │ Administration extension for the Ham radio APRSD Server │ 1.0.1 │ 2025-01-06T21:57:24 │ Yes │
|
||||||
|
│ 📂 aprsd-irc-extension │ An Extension to Ham radio APRSD Daemon to act like an irc server │ 0.0.5 │ 2024-04-09T11:28:47 │ No │
|
||||||
|
│ │ for APRS │ │ │ │
|
||||||
|
└──────────────────────────┴─────────────────────────────────────────────────────────────────────┴─────────┴─────────────────────┴────────────┘
|
||||||
|
|
||||||
|
### send-message
|
||||||
|
|
||||||
|
This command is typically used for development to send another aprsd
|
||||||
|
instance test messages
|
||||||
|
|
||||||
|
└─[$] > aprsd send-message -h
|
||||||
|
Usage: aprsd send-message [OPTIONS] TOCALLSIGN COMMAND...
|
||||||
|
|
||||||
|
Send a message to a callsign via APRS_IS.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
||||||
|
The log level to use for aprsd.log
|
||||||
|
[default: INFO]
|
||||||
|
-c, --config TEXT The aprsd config file to use for options.
|
||||||
|
[default:
|
||||||
|
/Users/i530566/.config/aprsd/aprsd.yml]
|
||||||
|
--quiet Don't log to stdout
|
||||||
|
--aprs-login TEXT What callsign to send the message from.
|
||||||
|
[env var: APRS_LOGIN]
|
||||||
|
--aprs-password TEXT the APRS-IS password for APRS_LOGIN [env
|
||||||
|
var: APRS_PASSWORD]
|
||||||
|
-n, --no-ack Don't wait for an ack, just sent it to APRS-
|
||||||
|
IS and bail. [default: False]
|
||||||
|
-w, --wait-response Wait for a response to the message?
|
||||||
|
[default: False]
|
||||||
|
--raw TEXT Send a raw message. Implies --no-ack
|
||||||
|
-h, --help Show this message and exit.
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
- `git clone git@github.com:craigerl/aprsd.git`
|
||||||
|
- `cd aprsd`
|
||||||
|
- `make`
|
||||||
|
|
||||||
|
#### Workflow
|
||||||
|
|
||||||
|
While working aprsd, The workflow is as follows:
|
||||||
|
|
||||||
|
- Checkout a new branch to work on by running
|
||||||
|
|
||||||
|
`git checkout -b mybranch`
|
||||||
|
|
||||||
|
- Make your changes to the code
|
||||||
|
|
||||||
|
- Run Tox with the following options:
|
||||||
|
|
||||||
|
- `tox -epep8`
|
||||||
|
- `tox -efmt`
|
||||||
|
- `tox -p`
|
||||||
|
|
||||||
|
- Commit your changes. This will run the pre-commit hooks which does
|
||||||
|
checks too
|
||||||
|
|
||||||
|
`git commit`
|
||||||
|
|
||||||
|
- Once you are done with all of your commits, then push up the branch
|
||||||
|
to github with:
|
||||||
|
|
||||||
|
`git push -u origin mybranch`
|
||||||
|
|
||||||
|
- Create a pull request from your branch so github tests can run and
|
||||||
|
we can do a code review.
|
||||||
|
|
||||||
|
#### Release
|
||||||
|
|
||||||
|
To do release to pypi:
|
||||||
|
|
||||||
|
- Tag release with:
|
||||||
|
|
||||||
|
`git tag -v1.XX -m "New release"`
|
||||||
|
|
||||||
|
- Push release tag:
|
||||||
|
|
||||||
|
`git push origin master --tags`
|
||||||
|
|
||||||
|
- Do a test build and verify build is valid by running:
|
||||||
|
|
||||||
|
`make build`
|
||||||
|
|
||||||
|
- Once twine is happy, upload release to pypi:
|
||||||
|
|
||||||
|
`make upload`
|
||||||
|
|
||||||
|
#### Building your own APRSD plugins
|
||||||
|
|
||||||
|
APRSD plugins are the mechanism by which APRSD can respond to APRS
|
||||||
|
Messages. The plugins are loaded at server startup and can also be
|
||||||
|
loaded at listen startup. When a packet is received by APRSD, it is
|
||||||
|
passed to each of the plugins in the order they were registered in the
|
||||||
|
config file. The plugins can then decide what to do with the packet.
|
||||||
|
When a plugin is called, it is passed a APRSD Packet object. The plugin
|
||||||
|
can then do something with the packet and return a reply message if
|
||||||
|
desired. If a plugin does not want to reply to the packet, it can just
|
||||||
|
return None. When a plugin does return a reply message, APRSD will send
|
||||||
|
the reply message to the appropriate destination.
|
||||||
|
|
||||||
|
For example, when a \'ping\' message is received, the PingPlugin will
|
||||||
|
return a reply message of \'pong\'. When APRSD receives the \'pong\'
|
||||||
|
message, it will be sent back to the original caller of the ping
|
||||||
|
message.
|
||||||
|
|
||||||
|
APRSD plugins are simply python packages that can be installed from
|
||||||
|
pypi.org. They are installed into the aprsd virtualenv and can be
|
||||||
|
imported by APRSD at runtime. The plugins are registered in the config
|
||||||
|
file and loaded at startup of the aprsd server command or the aprsd
|
||||||
|
listen command.
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
|
||||||
|
You can build your own plugins by following the instructions in the
|
||||||
|
[Building your own APRSD plugins](#building-your-own-aprsd-plugins)
|
||||||
|
section.
|
||||||
|
|
||||||
|
Plugins are called by APRSD when packe
|
||||||
|
|
||||||
|
### Docker Container
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
There are 2 versions of the container Dockerfile that can be used. The
|
||||||
|
main Dockerfile, which is for building the official release container
|
||||||
|
based off of the pip install version of aprsd and the Dockerfile-dev,
|
||||||
|
which is used for building a container based off of a git branch of the
|
||||||
|
repo.
|
||||||
|
|
||||||
|
### Official Build
|
||||||
|
|
||||||
|
`docker build -t hemna6969/aprsd:latest .`
|
||||||
|
|
||||||
|
### Development Build
|
||||||
|
|
||||||
|
`docker build -t hemna6969/aprsd:latest -f Dockerfile-dev .`
|
||||||
|
|
||||||
|
### Running the container
|
||||||
|
|
||||||
|
There is a `docker-compose.yml` file in the `docker/` directory that can
|
||||||
|
be used to run your container. To provide the container an `aprsd.conf`
|
||||||
|
configuration file, change your `docker-compose.yml` as shown below:
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- $HOME/.config/aprsd:/config
|
||||||
|
|
||||||
|
To install plugins at container start time, pass in a list of
|
||||||
|
comma-separated list of plugins on PyPI using the `APRSD_PLUGINS`
|
||||||
|
environment variable in the `docker-compose.yml` file. Note that version
|
||||||
|
constraints may also be provided. For example:
|
||||||
|
|
||||||
|
environment:
|
||||||
|
- APRSD_PLUGINS=aprsd-slack-plugin>=1.0.2,aprsd-twitter-plugin
|
||||||
|
|
||||||
|
|
||||||
|
### Activity
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#craigerl/aprsd&Date)
|
502
README.rst
502
README.rst
@ -1,502 +0,0 @@
|
|||||||
===============================================
|
|
||||||
APRSD - Ham radio APRS-IS Message plugin server
|
|
||||||
===============================================
|
|
||||||
|
|
||||||
KM6LYW and WB4BOR
|
|
||||||
____________________
|
|
||||||
|
|
||||||
|pypi| |pytest| |versions| |slack| |issues| |commit| |imports| |down|
|
|
||||||
|
|
||||||
|
|
||||||
`APRSD <http://github.com/craigerl/aprsd>`_ is a Ham radio `APRS <http://aprs.org>`_ message command gateway built on python.
|
|
||||||
|
|
||||||
|
|
||||||
Table of Contents
|
|
||||||
=================
|
|
||||||
|
|
||||||
1. `What is APRSD <#what-is-aprsd>`_
|
|
||||||
2. `APRSD Overview Diagram <#aprsd-overview-diagram>`_
|
|
||||||
3. `Typical Use Case <#typical-use-case>`_
|
|
||||||
4. `Installation <#installation>`_
|
|
||||||
5. `Example Usage <#example-usage>`_
|
|
||||||
6. `Help <#help>`_
|
|
||||||
7. `Commands <#commands>`_
|
|
||||||
- `Configuration <#configuration>`_
|
|
||||||
- `Server <#server>`_
|
|
||||||
- `Current List of Built-in Plugins <#current-list-of-built-in-plugins>`_
|
|
||||||
- `Pypi.org APRSD Installable Plugin Packages <#pypiorg-aprsd-installable-plugin-packages>`_
|
|
||||||
- `🐍 APRSD Installed 3rd Party Plugins <#aprsd-installed-3rd-party-plugins>`_
|
|
||||||
- `Send Message <#send-message>`_
|
|
||||||
- `Send Email (Radio to SMTP Server) <#send-email-radio-to-smtp-server>`_
|
|
||||||
- `Receive Email (IMAP Server to Radio) <#receive-email-imap-server-to-radio>`_
|
|
||||||
- `Location <#location>`_
|
|
||||||
- `Web Admin Interface <#web-admin-interface>`_
|
|
||||||
8. `Development <#development>`_
|
|
||||||
- `Building Your Own APRSD Plugins <#building-your-own-aprsd-plugins>`_
|
|
||||||
9. `Workflow <#workflow>`_
|
|
||||||
10. `Release <#release>`_
|
|
||||||
11. `Docker Container <#docker-container>`_
|
|
||||||
- `Building <#building-1>`_
|
|
||||||
- `Official Build <#official-build>`_
|
|
||||||
- `Development Build <#development-build>`_
|
|
||||||
- `Running the Container <#running-the-container>`_
|
|
||||||
|
|
||||||
|
|
||||||
What is APRSD
|
|
||||||
=============
|
|
||||||
APRSD is a python application for interacting with the APRS network and providing
|
|
||||||
APRS services for HAM radio operators.
|
|
||||||
|
|
||||||
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!
|
|
||||||
|
|
||||||
APRSD Overview Diagram
|
|
||||||
======================
|
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/craigerl/aprsd/master/docs/_static/aprsd_overview.svg?sanitize=true
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
for processing. For example, the WeatherPlugin picks up the message, fetches the weather
|
|
||||||
for the area around the user who sent the request, and then responds with
|
|
||||||
the weather conditions in that area. Also includes a watch list of HAM
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Installation
|
|
||||||
=============
|
|
||||||
|
|
||||||
To install ``aprsd``, use Pip:
|
|
||||||
|
|
||||||
``pip install aprsd``
|
|
||||||
|
|
||||||
Example usage
|
|
||||||
==============
|
|
||||||
|
|
||||||
``aprsd -h``
|
|
||||||
|
|
||||||
Help
|
|
||||||
====
|
|
||||||
::
|
|
||||||
|
|
||||||
|
|
||||||
└─> aprsd -h
|
|
||||||
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--version Show the version and exit.
|
|
||||||
-h, --help Show this message and exit.
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
check-version Check this version against the latest in pypi.org.
|
|
||||||
completion Show the shell completion code
|
|
||||||
dev Development type subcommands
|
|
||||||
fetch-stats Fetch stats from a APRSD admin web interface.
|
|
||||||
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.
|
|
||||||
listen Listen to packets on the APRS-IS Network based on FILTER.
|
|
||||||
sample-config Generate a sample Config file from aprsd and all...
|
|
||||||
send-message Send a message to a callsign via APRS_IS.
|
|
||||||
server Start the aprsd server gateway process.
|
|
||||||
version Show the APRSD version.
|
|
||||||
webchat Web based HAM Radio chat program!
|
|
||||||
|
|
||||||
|
|
||||||
Commands
|
|
||||||
========
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
This command outputs a sample config yml formatted block that you can edit
|
|
||||||
and use to pass in to ``aprsd`` with ``-c``. By default aprsd looks in ``~/.config/aprsd/aprsd.yml``
|
|
||||||
|
|
||||||
``aprsd sample-config``
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
└─> aprsd sample-config
|
|
||||||
...
|
|
||||||
|
|
||||||
server
|
|
||||||
======
|
|
||||||
|
|
||||||
This is the main server command that will listen to APRS-IS servers and
|
|
||||||
look for incomming commands to the callsign configured in the config file
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
└─[$] > aprsd server --help
|
|
||||||
Usage: aprsd server [OPTIONS]
|
|
||||||
|
|
||||||
Start the aprsd server gateway process.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
|
||||||
The log level to use for aprsd.log
|
|
||||||
[default: INFO]
|
|
||||||
-c, --config TEXT The aprsd config file to use for options.
|
|
||||||
[default:
|
|
||||||
/Users/i530566/.config/aprsd/aprsd.yml]
|
|
||||||
--quiet Don't log to stdout
|
|
||||||
-f, --flush Flush out all old aged messages on disk.
|
|
||||||
[default: False]
|
|
||||||
-h, --help Show this message and exit.
|
|
||||||
|
|
||||||
└─> aprsd server
|
|
||||||
Load config
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO APRSD is up to date server.py:51
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO APRSD Started version: 2.5.6 server.py:52
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO Using CONFIG values: server.py:55
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO ham.callsign = WB4BOR server.py:60
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO aprs.login = WB4BOR-12 server.py:60
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO aprs.password = XXXXXXXXXXXXXXXXXXX server.py:58
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO aprs.host = noam.aprs2.net server.py:60
|
|
||||||
12/07/2021 03:16:17 PM MainThread INFO aprs.port = 14580 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
|
|
||||||
============
|
|
||||||
|
|
||||||
This command is typically used for development to send another aprsd instance
|
|
||||||
test messages
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
└─[$] > aprsd send-message -h
|
|
||||||
Usage: aprsd send-message [OPTIONS] TOCALLSIGN COMMAND...
|
|
||||||
|
|
||||||
Send a message to a callsign via APRS_IS.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
|
||||||
The log level to use for aprsd.log
|
|
||||||
[default: INFO]
|
|
||||||
-c, --config TEXT The aprsd config file to use for options.
|
|
||||||
[default:
|
|
||||||
/Users/i530566/.config/aprsd/aprsd.yml]
|
|
||||||
--quiet Don't log to stdout
|
|
||||||
--aprs-login TEXT What callsign to send the message from.
|
|
||||||
[env var: APRS_LOGIN]
|
|
||||||
--aprs-password TEXT the APRS-IS password for APRS_LOGIN [env
|
|
||||||
var: APRS_PASSWORD]
|
|
||||||
-n, --no-ack Don't wait for an ack, just sent it to APRS-
|
|
||||||
IS and bail. [default: False]
|
|
||||||
-w, --wait-response Wait for a response to the message?
|
|
||||||
[default: False]
|
|
||||||
--raw TEXT Send a raw message. Implies --no-ack
|
|
||||||
-h, --help Show this message and exit.
|
|
||||||
|
|
||||||
|
|
||||||
SEND EMAIL (radio to smtp server)
|
|
||||||
=================================
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Received message______________
|
|
||||||
Raw : KM6XXX>APY400,WIDE1-1,qAO,KM6XXX-1::KM6XXX-9 :-user@host.com test new shortcuts global, radio to pc{29
|
|
||||||
From : KM6XXX
|
|
||||||
Message : -user@host.com test new shortcuts global, radio to pc
|
|
||||||
Msg number : 29
|
|
||||||
|
|
||||||
Sending Email_________________
|
|
||||||
To : user@host.com
|
|
||||||
Subject : KM6XXX
|
|
||||||
Body : test new shortcuts global, radio to pc
|
|
||||||
|
|
||||||
Sending ack __________________ Tx(3)
|
|
||||||
Raw : KM6XXX-9>APRS::KM6XXX :ack29
|
|
||||||
To : KM6XXX
|
|
||||||
Ack number : 29
|
|
||||||
|
|
||||||
|
|
||||||
RECEIVE EMAIL (imap server to radio)
|
|
||||||
====================================
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Sending message_______________ 6(Tx3)
|
|
||||||
Raw : KM6XXX-9>APRS::KM6XXX :-somebody@gmail.com email from internet to radio{6
|
|
||||||
To : KM6XXX
|
|
||||||
Message : -somebody@gmail.com email from internet to radio
|
|
||||||
|
|
||||||
Received message______________
|
|
||||||
Raw : KM6XXX>APY400,WIDE1-1,qAO,KM6XXX-1::KM6XXX-9 :ack6
|
|
||||||
From : KM6XXX
|
|
||||||
Message : ack6
|
|
||||||
Msg number : 0
|
|
||||||
|
|
||||||
|
|
||||||
LOCATION
|
|
||||||
========
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Received Message _______________
|
|
||||||
Raw : KM6XXX-6>APRS,TCPIP*,qAC,T2CAEAST::KM6XXX-14:location{2
|
|
||||||
From : KM6XXX-6
|
|
||||||
Message : location
|
|
||||||
Msg number : 2
|
|
||||||
Received Message _______________ Complete
|
|
||||||
|
|
||||||
Sending Message _______________
|
|
||||||
Raw : KM6XXX-14>APRS::KM6XXX-6 :KM6XXX-6: 8 Miles E Auburn CA 0' 0,-120.93584 1873.7h ago{2
|
|
||||||
To : KM6XXX-6
|
|
||||||
Message : KM6XXX-6: 8 Miles E Auburn CA 0' 0,-120.93584 1873.7h ago
|
|
||||||
Msg number : 2
|
|
||||||
Sending Message _______________ Complete
|
|
||||||
|
|
||||||
Sending ack _______________
|
|
||||||
Raw : KM6XXX-14>APRS::KM6XXX-6 :ack2
|
|
||||||
To : KM6XXX-6
|
|
||||||
Ack : 2
|
|
||||||
Sending ack _______________ Complete
|
|
||||||
|
|
||||||
AND... ping, fortune, time.....
|
|
||||||
|
|
||||||
|
|
||||||
Web Admin Interface
|
|
||||||
===================
|
|
||||||
APRSD has a web admin interface that allows you to view the status of the running APRSD server instance.
|
|
||||||
The web admin interface shows graphs of packet counts, packet types, number of threads running, the latest
|
|
||||||
packets sent and received, and the status of each of the plugins that are loaded. You can also view the logfile
|
|
||||||
and view the raw APRSD configuration file.
|
|
||||||
|
|
||||||
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
|
|
||||||
aprsd admin --loglevel INFO
|
|
||||||
|
|
||||||
The web admin interface will be running on port 8080 on the local machine. http://localhost:8080
|
|
||||||
|
|
||||||
|
|
||||||
Development
|
|
||||||
===========
|
|
||||||
|
|
||||||
* ``git clone git@github.com:craigerl/aprsd.git``
|
|
||||||
* ``cd aprsd``
|
|
||||||
* ``make``
|
|
||||||
|
|
||||||
Workflow
|
|
||||||
--------
|
|
||||||
|
|
||||||
While working aprsd, The workflow is as follows:
|
|
||||||
|
|
||||||
* Checkout a new branch to work on by running
|
|
||||||
|
|
||||||
``git checkout -b mybranch``
|
|
||||||
|
|
||||||
* Make your changes to the code
|
|
||||||
* Run Tox with the following options:
|
|
||||||
|
|
||||||
- ``tox -epep8``
|
|
||||||
- ``tox -efmt``
|
|
||||||
- ``tox -p``
|
|
||||||
|
|
||||||
* Commit your changes. This will run the pre-commit hooks which does checks too
|
|
||||||
|
|
||||||
``git commit``
|
|
||||||
|
|
||||||
* Once you are done with all of your commits, then push up the branch to
|
|
||||||
github with:
|
|
||||||
|
|
||||||
``git push -u origin mybranch``
|
|
||||||
|
|
||||||
* Create a pull request from your branch so github tests can run and we can do
|
|
||||||
a code review.
|
|
||||||
|
|
||||||
|
|
||||||
Release
|
|
||||||
-------
|
|
||||||
|
|
||||||
To do release to pypi:
|
|
||||||
|
|
||||||
* Tag release with:
|
|
||||||
|
|
||||||
``git tag -v1.XX -m "New release"``
|
|
||||||
|
|
||||||
* Push release tag:
|
|
||||||
|
|
||||||
``git push origin master --tags``
|
|
||||||
|
|
||||||
* Do a test build and verify build is valid by running:
|
|
||||||
|
|
||||||
``make build``
|
|
||||||
|
|
||||||
* Once twine is happy, upload release to pypi:
|
|
||||||
|
|
||||||
``make upload``
|
|
||||||
|
|
||||||
|
|
||||||
Building your own APRSD plugins
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
APRSD plugins are the mechanism by which APRSD can respond to APRS Messages. The plugins are loaded at server startup
|
|
||||||
and can also be loaded at listen startup. When a packet is received by APRSD, it is passed to each of the plugins
|
|
||||||
in the order they were registered in the config file. The plugins can then decide what to do with the packet.
|
|
||||||
When a plugin is called, it is passed a APRSD Packet object. The plugin can then do something with the packet and
|
|
||||||
return a reply message if desired. If a plugin does not want to reply to the packet, it can just return None.
|
|
||||||
When a plugin does return a reply message, APRSD will send the reply message to the appropriate destination.
|
|
||||||
|
|
||||||
For example, when a 'ping' message is received, the PingPlugin will return a reply message of 'pong'. When APRSD
|
|
||||||
receives the 'pong' message, it will be sent back to the original caller of the ping message.
|
|
||||||
|
|
||||||
APRSD plugins are simply python packages that can be installed from pypi.org. They are installed into the
|
|
||||||
aprsd virtualenv and can be imported by APRSD at runtime. The plugins are registered in the config file and loaded
|
|
||||||
at startup of the aprsd server command or the aprsd listen command.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
--------
|
|
||||||
You can build your own plugins by following the instructions in the `Building your own APRSD plugins`_ section.
|
|
||||||
|
|
||||||
Plugins are called by APRSD when packe
|
|
||||||
|
|
||||||
Docker Container
|
|
||||||
================
|
|
||||||
|
|
||||||
Building
|
|
||||||
========
|
|
||||||
|
|
||||||
There are 2 versions of the container Dockerfile that can be used.
|
|
||||||
The main Dockerfile, which is for building the official release container
|
|
||||||
based off of the pip install version of aprsd and the Dockerfile-dev,
|
|
||||||
which is used for building a container based off of a git branch of
|
|
||||||
the repo.
|
|
||||||
|
|
||||||
Official Build
|
|
||||||
==============
|
|
||||||
|
|
||||||
``docker build -t hemna6969/aprsd:latest .``
|
|
||||||
|
|
||||||
Development Build
|
|
||||||
=================
|
|
||||||
|
|
||||||
``docker build -t hemna6969/aprsd:latest -f Dockerfile-dev .``
|
|
||||||
|
|
||||||
|
|
||||||
Running the container
|
|
||||||
=====================
|
|
||||||
|
|
||||||
There is a ``docker-compose.yml`` file in the ``docker/`` directory
|
|
||||||
that can be used to run your container. To provide the container
|
|
||||||
an ``aprsd.conf`` configuration file, change your
|
|
||||||
``docker-compose.yml`` as shown below:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- $HOME/.config/aprsd:/config
|
|
||||||
|
|
||||||
To install plugins at container start time, pass in a list of
|
|
||||||
comma-separated list of plugins on PyPI using the ``APRSD_PLUGINS``
|
|
||||||
environment variable in the ``docker-compose.yml`` file. Note that
|
|
||||||
version constraints may also be provided. For example:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
environment:
|
|
||||||
- APRSD_PLUGINS=aprsd-slack-plugin>=1.0.2,aprsd-twitter-plugin
|
|
||||||
|
|
||||||
|
|
||||||
.. badges
|
|
||||||
|
|
||||||
.. |pypi| image:: https://badge.fury.io/py/aprsd.svg
|
|
||||||
:target: https://badge.fury.io/py/aprsd
|
|
||||||
|
|
||||||
.. |pytest| image:: https://github.com/craigerl/aprsd/workflows/python/badge.svg
|
|
||||||
:target: https://github.com/craigerl/aprsd/actions
|
|
||||||
|
|
||||||
.. |versions| image:: https://img.shields.io/pypi/pyversions/aprsd.svg
|
|
||||||
:target: https://pypi.org/pypi/aprsd
|
|
||||||
|
|
||||||
.. |slack| image:: https://img.shields.io/badge/slack-@hemna/aprsd-blue.svg?logo=slack
|
|
||||||
:target: https://hemna.slack.com/app_redirect?channel=C01KQSCP5RP
|
|
||||||
|
|
||||||
.. |imports| image:: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336
|
|
||||||
:target: https://timothycrosley.github.io/isort/
|
|
||||||
|
|
||||||
.. |issues| image:: https://img.shields.io/github/issues/craigerl/aprsd
|
|
||||||
|
|
||||||
.. |commit| image:: https://img.shields.io/github/last-commit/craigerl/aprsd
|
|
||||||
|
|
||||||
.. |down| image:: https://static.pepy.tech/personalized-badge/aprsd?period=month&units=international_system&left_color=black&right_color=orange&left_text=Downloads
|
|
||||||
:target: https://pepy.tech/project/aprsd
|
|
||||||
|
|
||||||
.. links
|
|
||||||
.. _read the docs:
|
|
||||||
https://aprsd.readthedocs.io
|
|
@ -11,7 +11,7 @@ from aprsd.packets import core
|
|||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger('APRSD')
|
||||||
|
|
||||||
|
|
||||||
class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
||||||
@ -24,12 +24,12 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
|||||||
path = []
|
path = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
LOG.info("Starting APRSDFakeClient client.")
|
LOG.info('Starting APRSDFakeClient client.')
|
||||||
self.path = ["WIDE1-1", "WIDE2-1"]
|
self.path = ['WIDE1-1', 'WIDE2-1']
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.thread_stop = True
|
self.thread_stop = True
|
||||||
LOG.info("Shutdown APRSDFakeClient client.")
|
LOG.info('Shutdown APRSDFakeClient client.')
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self):
|
||||||
"""If the connection is alive or not."""
|
"""If the connection is alive or not."""
|
||||||
@ -38,35 +38,31 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
|||||||
@wrapt.synchronized(lock)
|
@wrapt.synchronized(lock)
|
||||||
def send(self, packet: core.Packet):
|
def send(self, packet: core.Packet):
|
||||||
"""Send an APRS Message object."""
|
"""Send an APRS Message object."""
|
||||||
LOG.info(f"Sending packet: {packet}")
|
LOG.info(f'Sending packet: {packet}')
|
||||||
payload = None
|
payload = None
|
||||||
if isinstance(packet, core.Packet):
|
if isinstance(packet, core.Packet):
|
||||||
packet.prepare()
|
packet.prepare()
|
||||||
payload = packet.payload.encode("US-ASCII")
|
payload = packet.payload.encode('US-ASCII')
|
||||||
if packet.path:
|
|
||||||
packet.path
|
|
||||||
else:
|
else:
|
||||||
self.path
|
msg_payload = f'{packet.raw}{{{str(packet.msgNo)}'
|
||||||
else:
|
|
||||||
msg_payload = f"{packet.raw}{{{str(packet.msgNo)}"
|
|
||||||
payload = (
|
payload = (
|
||||||
":{:<9}:{}".format(
|
':{:<9}:{}'.format(
|
||||||
packet.to_call,
|
packet.to_call,
|
||||||
msg_payload,
|
msg_payload,
|
||||||
)
|
)
|
||||||
).encode("US-ASCII")
|
).encode('US-ASCII')
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
f"FAKE::Send '{payload}' TO '{packet.to_call}' From "
|
f"FAKE::Send '{payload}' TO '{packet.to_call}' From "
|
||||||
f"'{packet.from_call}' with PATH \"{self.path}\"",
|
f'\'{packet.from_call}\' with PATH "{self.path}"',
|
||||||
)
|
)
|
||||||
|
|
||||||
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
def consumer(self, callback, blocking=False, immortal=False, raw=False):
|
||||||
LOG.debug("Start non blocking FAKE consumer")
|
LOG.debug('Start non blocking FAKE consumer')
|
||||||
# Generate packets here?
|
# Generate packets here?
|
||||||
raw = "GTOWN>APDW16,WIDE1-1,WIDE2-1:}KM6LYW-9>APZ100,TCPIP,GTOWN*::KM6LYW :KM6LYW: 19 Miles SW"
|
raw = 'GTOWN>APDW16,WIDE1-1,WIDE2-1:}KM6LYW-9>APZ100,TCPIP,GTOWN*::KM6LYW :KM6LYW: 19 Miles SW'
|
||||||
pkt_raw = aprslib.parse(raw)
|
pkt_raw = aprslib.parse(raw)
|
||||||
pkt = core.factory(pkt_raw)
|
pkt = core.factory(pkt_raw)
|
||||||
callback(packet=pkt)
|
callback(packet=pkt)
|
||||||
LOG.debug(f"END blocking FAKE consumer {self}")
|
LOG.debug(f'END blocking FAKE consumer {self}')
|
||||||
time.sleep(8)
|
time.sleep(8)
|
||||||
|
@ -16,11 +16,11 @@ from aprsd.main import cli
|
|||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger('APRSD')
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||||
|
|
||||||
|
|
||||||
@cli.group(help="Development type subcommands", context_settings=CONTEXT_SETTINGS)
|
@cli.group(help='Development type subcommands', context_settings=CONTEXT_SETTINGS)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def dev(ctx):
|
def dev(ctx):
|
||||||
pass
|
pass
|
||||||
@ -29,37 +29,37 @@ def dev(ctx):
|
|||||||
@dev.command()
|
@dev.command()
|
||||||
@cli_helper.add_options(cli_helper.common_options)
|
@cli_helper.add_options(cli_helper.common_options)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--aprs-login",
|
'--aprs-login',
|
||||||
envvar="APRS_LOGIN",
|
envvar='APRS_LOGIN',
|
||||||
show_envvar=True,
|
show_envvar=True,
|
||||||
help="What callsign to send the message from.",
|
help='What callsign to send the message from.',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-p",
|
'-p',
|
||||||
"--plugin",
|
'--plugin',
|
||||||
"plugin_path",
|
'plugin_path',
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=None,
|
default=None,
|
||||||
help="The plugin to run. Ex: aprsd.plugins.ping.PingPlugin",
|
help='The plugin to run. Ex: aprsd.plugins.ping.PingPlugin',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-a",
|
'-a',
|
||||||
"--all",
|
'--all',
|
||||||
"load_all",
|
'load_all',
|
||||||
show_default=True,
|
show_default=True,
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Load all the plugins in config?",
|
help='Load all the plugins in config?',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-n",
|
'-n',
|
||||||
"--num",
|
'--num',
|
||||||
"number",
|
'number',
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=1,
|
default=1,
|
||||||
help="Number of times to call the plugin",
|
help='Number of times to call the plugin',
|
||||||
)
|
)
|
||||||
@click.argument("message", nargs=-1, required=True)
|
@click.argument('message', nargs=-1, required=True)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@cli_helper.process_standard_options
|
@cli_helper.process_standard_options
|
||||||
def test_plugin(
|
def test_plugin(
|
||||||
@ -76,7 +76,7 @@ def test_plugin(
|
|||||||
|
|
||||||
if not aprs_login:
|
if not aprs_login:
|
||||||
if CONF.aprs_network.login == conf.client.DEFAULT_LOGIN:
|
if CONF.aprs_network.login == conf.client.DEFAULT_LOGIN:
|
||||||
click.echo("Must set --aprs_login or APRS_LOGIN")
|
click.echo('Must set --aprs_login or APRS_LOGIN')
|
||||||
ctx.exit(-1)
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -86,16 +86,16 @@ def test_plugin(
|
|||||||
|
|
||||||
if not plugin_path:
|
if not plugin_path:
|
||||||
click.echo(ctx.get_help())
|
click.echo(ctx.get_help())
|
||||||
click.echo("")
|
click.echo('')
|
||||||
click.echo("Failed to provide -p option to test a plugin")
|
click.echo('Failed to provide -p option to test a plugin')
|
||||||
ctx.exit(-1)
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
|
|
||||||
if type(message) is tuple:
|
if type(message) is tuple:
|
||||||
message = " ".join(message)
|
message = ' '.join(message)
|
||||||
|
|
||||||
if CONF.trace_enabled:
|
if CONF.trace_enabled:
|
||||||
trace.setup_tracing(["method", "api"])
|
trace.setup_tracing(['method', 'api'])
|
||||||
|
|
||||||
base.APRSClient()
|
base.APRSClient()
|
||||||
|
|
||||||
@ -105,13 +105,13 @@ def test_plugin(
|
|||||||
obj = pm._create_class(plugin_path, plugin.APRSDPluginBase)
|
obj = pm._create_class(plugin_path, plugin.APRSDPluginBase)
|
||||||
if not obj:
|
if not obj:
|
||||||
click.echo(ctx.get_help())
|
click.echo(ctx.get_help())
|
||||||
click.echo("")
|
click.echo('')
|
||||||
ctx.fail(f"Failed to create object from plugin path '{plugin_path}'")
|
ctx.fail(f"Failed to create object from plugin path '{plugin_path}'")
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
||||||
# Register the plugin they wanted tested.
|
# Register the plugin they wanted tested.
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Testing plugin {} Version {}".format(
|
'Testing plugin {} Version {}'.format(
|
||||||
obj.__class__,
|
obj.__class__,
|
||||||
obj.version,
|
obj.version,
|
||||||
),
|
),
|
||||||
@ -126,7 +126,7 @@ def test_plugin(
|
|||||||
)
|
)
|
||||||
LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'")
|
LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'")
|
||||||
|
|
||||||
for x in range(number):
|
for _ in range(number):
|
||||||
replies = pm.run(packet)
|
replies = pm.run(packet)
|
||||||
# Plugin might have threads, so lets stop them so we can exit.
|
# Plugin might have threads, so lets stop them so we can exit.
|
||||||
# obj.stop_threads()
|
# obj.stop_threads()
|
||||||
@ -147,12 +147,7 @@ def test_plugin(
|
|||||||
elif isinstance(reply, packets.Packet):
|
elif isinstance(reply, packets.Packet):
|
||||||
# We have a message based object.
|
# We have a message based object.
|
||||||
LOG.info(reply)
|
LOG.info(reply)
|
||||||
else:
|
elif reply is not packets.NULL_MESSAGE:
|
||||||
# A plugin can return a null message flag which signals
|
|
||||||
# us that they processed the message correctly, but have
|
|
||||||
# nothing to reply with, so we avoid replying with a
|
|
||||||
# usage string
|
|
||||||
if reply is not packets.NULL_MESSAGE:
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=CONF.callsign,
|
from_call=CONF.callsign,
|
||||||
|
@ -15,30 +15,30 @@ from aprsd.threads.stats import StatsStore
|
|||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
# log.basicConfig(level=log.DEBUG) # level=10
|
# log.basicConfig(level=log.DEBUG) # level=10
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger('APRSD')
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@cli_helper.add_options(cli_helper.common_options)
|
@cli_helper.add_options(cli_helper.common_options)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--host",
|
'--host',
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="IP address of the remote aprsd admin web ui fetch stats from.",
|
help='IP address of the remote aprsd admin web ui fetch stats from.',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--port",
|
'--port',
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help="Port of the remote aprsd web admin interface to fetch stats from.",
|
help='Port of the remote aprsd web admin interface to fetch stats from.',
|
||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@cli_helper.process_standard_options
|
@cli_helper.process_standard_options
|
||||||
def fetch_stats(ctx, host, port):
|
def fetch_stats(ctx, host, port):
|
||||||
"""Fetch stats from a APRSD admin web interface."""
|
"""Fetch stats from a APRSD admin web interface."""
|
||||||
console = Console()
|
console = Console()
|
||||||
console.print(f"APRSD Fetch-Stats started version: {aprsd.__version__}")
|
console.print(f'APRSD Fetch-Stats started version: {aprsd.__version__}')
|
||||||
|
|
||||||
CONF.log_opt_values(LOG, logging.DEBUG)
|
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||||
if not host:
|
if not host:
|
||||||
@ -46,114 +46,110 @@ def fetch_stats(ctx, host, port):
|
|||||||
if not port:
|
if not port:
|
||||||
port = CONF.admin.web_port
|
port = CONF.admin.web_port
|
||||||
|
|
||||||
msg = f"Fetching stats from {host}:{port}"
|
msg = f'Fetching stats from {host}:{port}'
|
||||||
console.print(msg)
|
console.print(msg)
|
||||||
with console.status(msg):
|
with console.status(msg):
|
||||||
response = requests.get(f"http://{host}:{port}/stats", timeout=120)
|
response = requests.get(f'http://{host}:{port}/stats', timeout=120)
|
||||||
if not response:
|
if not response:
|
||||||
console.print(
|
console.print(
|
||||||
f"Failed to fetch stats from {host}:{port}?",
|
f'Failed to fetch stats from {host}:{port}?',
|
||||||
style="bold red",
|
style='bold red',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
stats = response.json()
|
stats = response.json()
|
||||||
if not stats:
|
if not stats:
|
||||||
console.print(
|
console.print(
|
||||||
f"Failed to fetch stats from aprsd admin ui at {host}:{port}",
|
f'Failed to fetch stats from aprsd admin ui at {host}:{port}',
|
||||||
style="bold red",
|
style='bold red',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
aprsd_title = (
|
aprsd_title = (
|
||||||
"APRSD "
|
'APRSD '
|
||||||
f"[bold cyan]v{stats['APRSDStats']['version']}[/] "
|
f'[bold cyan]v{stats["APRSDStats"]["version"]}[/] '
|
||||||
f"Callsign [bold green]{stats['APRSDStats']['callsign']}[/] "
|
f'Callsign [bold green]{stats["APRSDStats"]["callsign"]}[/] '
|
||||||
f"Uptime [bold yellow]{stats['APRSDStats']['uptime']}[/]"
|
f'Uptime [bold yellow]{stats["APRSDStats"]["uptime"]}[/]'
|
||||||
)
|
)
|
||||||
|
|
||||||
console.rule(f"Stats from {host}:{port}")
|
console.rule(f'Stats from {host}:{port}')
|
||||||
console.print("\n\n")
|
console.print('\n\n')
|
||||||
console.rule(aprsd_title)
|
console.rule(aprsd_title)
|
||||||
|
|
||||||
# Show the connection to APRS
|
# Show the connection to APRS
|
||||||
# It can be a connection to an APRS-IS server or a local TNC via KISS or KISSTCP
|
# It can be a connection to an APRS-IS server or a local TNC via KISS or KISSTCP
|
||||||
if "aprs-is" in stats:
|
if 'aprs-is' in stats:
|
||||||
title = f"APRS-IS Connection {stats['APRSClientStats']['server_string']}"
|
title = f'APRS-IS Connection {stats["APRSClientStats"]["server_string"]}'
|
||||||
table = Table(title=title)
|
table = Table(title=title)
|
||||||
table.add_column("Key")
|
table.add_column('Key')
|
||||||
table.add_column("Value")
|
table.add_column('Value')
|
||||||
for key, value in stats["APRSClientStats"].items():
|
for key, value in stats['APRSClientStats'].items():
|
||||||
table.add_row(key, value)
|
table.add_row(key, value)
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
threads_table = Table(title="Threads")
|
threads_table = Table(title='Threads')
|
||||||
threads_table.add_column("Name")
|
threads_table.add_column('Name')
|
||||||
threads_table.add_column("Alive?")
|
threads_table.add_column('Alive?')
|
||||||
for name, alive in stats["APRSDThreadList"].items():
|
for name, alive in stats['APRSDThreadList'].items():
|
||||||
threads_table.add_row(name, str(alive))
|
threads_table.add_row(name, str(alive))
|
||||||
|
|
||||||
console.print(threads_table)
|
console.print(threads_table)
|
||||||
|
|
||||||
packet_totals = Table(title="Packet Totals")
|
packet_totals = Table(title='Packet Totals')
|
||||||
packet_totals.add_column("Key")
|
packet_totals.add_column('Key')
|
||||||
packet_totals.add_column("Value")
|
packet_totals.add_column('Value')
|
||||||
packet_totals.add_row("Total Received", str(stats["PacketList"]["rx"]))
|
packet_totals.add_row('Total Received', str(stats['PacketList']['rx']))
|
||||||
packet_totals.add_row("Total Sent", str(stats["PacketList"]["tx"]))
|
packet_totals.add_row('Total Sent', str(stats['PacketList']['tx']))
|
||||||
console.print(packet_totals)
|
console.print(packet_totals)
|
||||||
|
|
||||||
# Show each of the packet types
|
# Show each of the packet types
|
||||||
packets_table = Table(title="Packets By Type")
|
packets_table = Table(title='Packets By Type')
|
||||||
packets_table.add_column("Packet Type")
|
packets_table.add_column('Packet Type')
|
||||||
packets_table.add_column("TX")
|
packets_table.add_column('TX')
|
||||||
packets_table.add_column("RX")
|
packets_table.add_column('RX')
|
||||||
for key, value in stats["PacketList"]["packets"].items():
|
for key, value in stats['PacketList']['packets'].items():
|
||||||
packets_table.add_row(key, str(value["tx"]), str(value["rx"]))
|
packets_table.add_row(key, str(value['tx']), str(value['rx']))
|
||||||
|
|
||||||
console.print(packets_table)
|
console.print(packets_table)
|
||||||
|
|
||||||
if "plugins" in stats:
|
if 'plugins' in stats:
|
||||||
count = len(stats["PluginManager"])
|
count = len(stats['PluginManager'])
|
||||||
plugins_table = Table(title=f"Plugins ({count})")
|
plugins_table = Table(title=f'Plugins ({count})')
|
||||||
plugins_table.add_column("Plugin")
|
plugins_table.add_column('Plugin')
|
||||||
plugins_table.add_column("Enabled")
|
plugins_table.add_column('Enabled')
|
||||||
plugins_table.add_column("Version")
|
plugins_table.add_column('Version')
|
||||||
plugins_table.add_column("TX")
|
plugins_table.add_column('TX')
|
||||||
plugins_table.add_column("RX")
|
plugins_table.add_column('RX')
|
||||||
plugins = stats["PluginManager"]
|
plugins = stats['PluginManager']
|
||||||
for key, value in plugins.items():
|
for key, _ in plugins.items():
|
||||||
plugins_table.add_row(
|
plugins_table.add_row(
|
||||||
key,
|
key,
|
||||||
str(plugins[key]["enabled"]),
|
str(plugins[key]['enabled']),
|
||||||
plugins[key]["version"],
|
plugins[key]['version'],
|
||||||
str(plugins[key]["tx"]),
|
str(plugins[key]['tx']),
|
||||||
str(plugins[key]["rx"]),
|
str(plugins[key]['rx']),
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(plugins_table)
|
console.print(plugins_table)
|
||||||
|
|
||||||
seen_list = stats.get("SeenList")
|
if seen_list := stats.get('SeenList'):
|
||||||
|
|
||||||
if seen_list:
|
|
||||||
count = len(seen_list)
|
count = len(seen_list)
|
||||||
seen_table = Table(title=f"Seen List ({count})")
|
seen_table = Table(title=f'Seen List ({count})')
|
||||||
seen_table.add_column("Callsign")
|
seen_table.add_column('Callsign')
|
||||||
seen_table.add_column("Message Count")
|
seen_table.add_column('Message Count')
|
||||||
seen_table.add_column("Last Heard")
|
seen_table.add_column('Last Heard')
|
||||||
for key, value in seen_list.items():
|
for key, value in seen_list.items():
|
||||||
seen_table.add_row(key, str(value["count"]), value["last"])
|
seen_table.add_row(key, str(value['count']), value['last'])
|
||||||
|
|
||||||
console.print(seen_table)
|
console.print(seen_table)
|
||||||
|
|
||||||
watch_list = stats.get("WatchList")
|
if watch_list := stats.get('WatchList'):
|
||||||
|
|
||||||
if watch_list:
|
|
||||||
count = len(watch_list)
|
count = len(watch_list)
|
||||||
watch_table = Table(title=f"Watch List ({count})")
|
watch_table = Table(title=f'Watch List ({count})')
|
||||||
watch_table.add_column("Callsign")
|
watch_table.add_column('Callsign')
|
||||||
watch_table.add_column("Last Heard")
|
watch_table.add_column('Last Heard')
|
||||||
for key, value in watch_list.items():
|
for key, value in watch_list.items():
|
||||||
watch_table.add_row(key, value["last"])
|
watch_table.add_row(key, value['last'])
|
||||||
|
|
||||||
console.print(watch_table)
|
console.print(watch_table)
|
||||||
|
|
||||||
@ -161,27 +157,27 @@ def fetch_stats(ctx, host, port):
|
|||||||
@cli.command()
|
@cli.command()
|
||||||
@cli_helper.add_options(cli_helper.common_options)
|
@cli_helper.add_options(cli_helper.common_options)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--raw",
|
'--raw',
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Dump raw stats instead of formatted output.",
|
help='Dump raw stats instead of formatted output.',
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--show-section",
|
'--show-section',
|
||||||
default=["All"],
|
default=['All'],
|
||||||
help="Show specific sections of the stats. "
|
help='Show specific sections of the stats. '
|
||||||
" Choices: All, APRSDStats, APRSDThreadList, APRSClientStats,"
|
' Choices: All, APRSDStats, APRSDThreadList, APRSClientStats,'
|
||||||
" PacketList, SeenList, WatchList",
|
' PacketList, SeenList, WatchList',
|
||||||
multiple=True,
|
multiple=True,
|
||||||
type=click.Choice(
|
type=click.Choice(
|
||||||
[
|
[
|
||||||
"All",
|
'All',
|
||||||
"APRSDStats",
|
'APRSDStats',
|
||||||
"APRSDThreadList",
|
'APRSDThreadList',
|
||||||
"APRSClientStats",
|
'APRSClientStats',
|
||||||
"PacketList",
|
'PacketList',
|
||||||
"SeenList",
|
'SeenList',
|
||||||
"WatchList",
|
'WatchList',
|
||||||
],
|
],
|
||||||
case_sensitive=False,
|
case_sensitive=False,
|
||||||
),
|
),
|
||||||
@ -191,122 +187,122 @@ def fetch_stats(ctx, host, port):
|
|||||||
def dump_stats(ctx, raw, show_section):
|
def dump_stats(ctx, raw, show_section):
|
||||||
"""Dump the current stats from the running APRSD instance."""
|
"""Dump the current stats from the running APRSD instance."""
|
||||||
console = Console()
|
console = Console()
|
||||||
console.print(f"APRSD Dump-Stats started version: {aprsd.__version__}")
|
console.print(f'APRSD Dump-Stats started version: {aprsd.__version__}')
|
||||||
|
|
||||||
with console.status("Dumping stats"):
|
with console.status('Dumping stats'):
|
||||||
ss = StatsStore()
|
ss = StatsStore()
|
||||||
ss.load()
|
ss.load()
|
||||||
stats = ss.data
|
stats = ss.data
|
||||||
if raw:
|
if raw:
|
||||||
if "All" in show_section:
|
if 'All' in show_section:
|
||||||
console.print(stats)
|
console.print(stats)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
for section in show_section:
|
for section in show_section:
|
||||||
console.print(f"Dumping {section} section:")
|
console.print(f'Dumping {section} section:')
|
||||||
console.print(stats[section])
|
console.print(stats[section])
|
||||||
return
|
return
|
||||||
|
|
||||||
t = Table(title="APRSD Stats")
|
t = Table(title='APRSD Stats')
|
||||||
t.add_column("Key")
|
t.add_column('Key')
|
||||||
t.add_column("Value")
|
t.add_column('Value')
|
||||||
for key, value in stats["APRSDStats"].items():
|
for key, value in stats['APRSDStats'].items():
|
||||||
t.add_row(key, str(value))
|
t.add_row(key, str(value))
|
||||||
|
|
||||||
if "All" in show_section or "APRSDStats" in show_section:
|
if 'All' in show_section or 'APRSDStats' in show_section:
|
||||||
console.print(t)
|
console.print(t)
|
||||||
|
|
||||||
# Show the thread list
|
# Show the thread list
|
||||||
t = Table(title="Thread List")
|
t = Table(title='Thread List')
|
||||||
t.add_column("Name")
|
t.add_column('Name')
|
||||||
t.add_column("Class")
|
t.add_column('Class')
|
||||||
t.add_column("Alive?")
|
t.add_column('Alive?')
|
||||||
t.add_column("Loop Count")
|
t.add_column('Loop Count')
|
||||||
t.add_column("Age")
|
t.add_column('Age')
|
||||||
for name, value in stats["APRSDThreadList"].items():
|
for name, value in stats['APRSDThreadList'].items():
|
||||||
t.add_row(
|
t.add_row(
|
||||||
name,
|
name,
|
||||||
value["class"],
|
value['class'],
|
||||||
str(value["alive"]),
|
str(value['alive']),
|
||||||
str(value["loop_count"]),
|
str(value['loop_count']),
|
||||||
str(value["age"]),
|
str(value['age']),
|
||||||
)
|
)
|
||||||
|
|
||||||
if "All" in show_section or "APRSDThreadList" in show_section:
|
if 'All' in show_section or 'APRSDThreadList' in show_section:
|
||||||
console.print(t)
|
console.print(t)
|
||||||
|
|
||||||
# Show the plugins
|
# Show the plugins
|
||||||
t = Table(title="Plugin List")
|
t = Table(title='Plugin List')
|
||||||
t.add_column("Name")
|
t.add_column('Name')
|
||||||
t.add_column("Enabled")
|
t.add_column('Enabled')
|
||||||
t.add_column("Version")
|
t.add_column('Version')
|
||||||
t.add_column("TX")
|
t.add_column('TX')
|
||||||
t.add_column("RX")
|
t.add_column('RX')
|
||||||
for name, value in stats["PluginManager"].items():
|
for name, value in stats['PluginManager'].items():
|
||||||
t.add_row(
|
t.add_row(
|
||||||
name,
|
name,
|
||||||
str(value["enabled"]),
|
str(value['enabled']),
|
||||||
value["version"],
|
value['version'],
|
||||||
str(value["tx"]),
|
str(value['tx']),
|
||||||
str(value["rx"]),
|
str(value['rx']),
|
||||||
)
|
)
|
||||||
|
|
||||||
if "All" in show_section or "PluginManager" in show_section:
|
if 'All' in show_section or 'PluginManager' in show_section:
|
||||||
console.print(t)
|
console.print(t)
|
||||||
|
|
||||||
# Now show the client stats
|
# Now show the client stats
|
||||||
t = Table(title="Client Stats")
|
t = Table(title='Client Stats')
|
||||||
t.add_column("Key")
|
t.add_column('Key')
|
||||||
t.add_column("Value")
|
t.add_column('Value')
|
||||||
for key, value in stats["APRSClientStats"].items():
|
for key, value in stats['APRSClientStats'].items():
|
||||||
t.add_row(key, str(value))
|
t.add_row(key, str(value))
|
||||||
|
|
||||||
if "All" in show_section or "APRSClientStats" in show_section:
|
if 'All' in show_section or 'APRSClientStats' in show_section:
|
||||||
console.print(t)
|
console.print(t)
|
||||||
|
|
||||||
# now show the packet list
|
# now show the packet list
|
||||||
packet_list = stats.get("PacketList")
|
packet_list = stats.get('PacketList')
|
||||||
t = Table(title="Packet List")
|
t = Table(title='Packet List')
|
||||||
t.add_column("Key")
|
t.add_column('Key')
|
||||||
t.add_column("Value")
|
t.add_column('Value')
|
||||||
t.add_row("Total Received", str(packet_list["rx"]))
|
t.add_row('Total Received', str(packet_list['rx']))
|
||||||
t.add_row("Total Sent", str(packet_list["tx"]))
|
t.add_row('Total Sent', str(packet_list['tx']))
|
||||||
|
|
||||||
if "All" in show_section or "PacketList" in show_section:
|
if 'All' in show_section or 'PacketList' in show_section:
|
||||||
console.print(t)
|
console.print(t)
|
||||||
|
|
||||||
# now show the seen list
|
# now show the seen list
|
||||||
seen_list = stats.get("SeenList")
|
seen_list = stats.get('SeenList')
|
||||||
sorted_seen_list = sorted(
|
sorted_seen_list = sorted(
|
||||||
seen_list.items(),
|
seen_list.items(),
|
||||||
)
|
)
|
||||||
t = Table(title="Seen List")
|
t = Table(title='Seen List')
|
||||||
t.add_column("Callsign")
|
t.add_column('Callsign')
|
||||||
t.add_column("Message Count")
|
t.add_column('Message Count')
|
||||||
t.add_column("Last Heard")
|
t.add_column('Last Heard')
|
||||||
for key, value in sorted_seen_list:
|
for key, value in sorted_seen_list:
|
||||||
t.add_row(
|
t.add_row(
|
||||||
key,
|
key,
|
||||||
str(value["count"]),
|
str(value['count']),
|
||||||
str(value["last"]),
|
str(value['last']),
|
||||||
)
|
)
|
||||||
|
|
||||||
if "All" in show_section or "SeenList" in show_section:
|
if 'All' in show_section or 'SeenList' in show_section:
|
||||||
console.print(t)
|
console.print(t)
|
||||||
|
|
||||||
# now show the watch list
|
# now show the watch list
|
||||||
watch_list = stats.get("WatchList")
|
watch_list = stats.get('WatchList')
|
||||||
sorted_watch_list = sorted(
|
sorted_watch_list = sorted(
|
||||||
watch_list.items(),
|
watch_list.items(),
|
||||||
)
|
)
|
||||||
t = Table(title="Watch List")
|
t = Table(title='Watch List')
|
||||||
t.add_column("Callsign")
|
t.add_column('Callsign')
|
||||||
t.add_column("Last Heard")
|
t.add_column('Last Heard')
|
||||||
for key, value in sorted_watch_list:
|
for key, value in sorted_watch_list:
|
||||||
t.add_row(
|
t.add_row(
|
||||||
key,
|
key,
|
||||||
str(value["last"]),
|
str(value['last']),
|
||||||
)
|
)
|
||||||
|
|
||||||
if "All" in show_section or "WatchList" in show_section:
|
if 'All' in show_section or 'WatchList' in show_section:
|
||||||
console.print(t)
|
console.print(t)
|
||||||
|
@ -13,9 +13,9 @@ from oslo_config import cfg
|
|||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import (
|
from aprsd import ( # noqa: F401
|
||||||
cli_helper,
|
cli_helper,
|
||||||
conf, # noqa
|
conf,
|
||||||
)
|
)
|
||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
|
@ -4,14 +4,11 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
from traceback import print_tb
|
from traceback import print_tb
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
@ -22,12 +19,12 @@ from aprsd import plugin as aprsd_plugin
|
|||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger('APRSD')
|
||||||
PYPI_URL = "https://pypi.org/search/"
|
PYPI_URL = 'https://pypi.org/search/'
|
||||||
|
|
||||||
|
|
||||||
def onerror(name):
|
def onerror(name):
|
||||||
print(f"Error importing module {name}")
|
print(f'Error importing module {name}')
|
||||||
type, value, traceback = sys.exc_info()
|
type, value, traceback = sys.exc_info()
|
||||||
print_tb(traceback)
|
print_tb(traceback)
|
||||||
|
|
||||||
@ -43,19 +40,19 @@ def is_plugin(obj):
|
|||||||
def plugin_type(obj):
|
def plugin_type(obj):
|
||||||
for c in inspect.getmro(obj):
|
for c in inspect.getmro(obj):
|
||||||
if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
|
if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
|
||||||
return "RegexCommand"
|
return 'RegexCommand'
|
||||||
if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
|
if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
|
||||||
return "WatchList"
|
return 'WatchList'
|
||||||
if issubclass(c, aprsd_plugin.APRSDPluginBase):
|
if issubclass(c, aprsd_plugin.APRSDPluginBase):
|
||||||
return "APRSDPluginBase"
|
return 'APRSDPluginBase'
|
||||||
|
|
||||||
return "Unknown"
|
return 'Unknown'
|
||||||
|
|
||||||
|
|
||||||
def walk_package(package):
|
def walk_package(package):
|
||||||
return pkgutil.walk_packages(
|
return pkgutil.walk_packages(
|
||||||
package.__path__,
|
package.__path__,
|
||||||
package.__name__ + ".",
|
package.__name__ + '.',
|
||||||
onerror=onerror,
|
onerror=onerror,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,23 +62,23 @@ def get_module_info(package_name, module_name, module_path):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
dir_path = os.path.realpath(module_path)
|
dir_path = os.path.realpath(module_path)
|
||||||
pattern = "*.py"
|
pattern = '*.py'
|
||||||
|
|
||||||
obj_list = []
|
obj_list = []
|
||||||
|
|
||||||
for path, _subdirs, files in os.walk(dir_path):
|
for path, _subdirs, files in os.walk(dir_path):
|
||||||
for name in files:
|
for name in files:
|
||||||
if fnmatch.fnmatch(name, pattern):
|
if fnmatch.fnmatch(name, pattern):
|
||||||
module = smuggle(f"{path}/{name}")
|
module = smuggle(f'{path}/{name}')
|
||||||
for mem_name, obj in inspect.getmembers(module):
|
for mem_name, obj in inspect.getmembers(module):
|
||||||
if inspect.isclass(obj) and is_plugin(obj):
|
if inspect.isclass(obj) and is_plugin(obj):
|
||||||
obj_list.append(
|
obj_list.append(
|
||||||
{
|
{
|
||||||
"package": package_name,
|
'package': package_name,
|
||||||
"name": mem_name,
|
'name': mem_name,
|
||||||
"obj": obj,
|
'obj': obj,
|
||||||
"version": obj.version,
|
'version': obj.version,
|
||||||
"path": f"{'.'.join([module_name, obj.__name__])}",
|
'path': f'{".".join([module_name, obj.__name__])}',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -92,19 +89,17 @@ def _get_installed_aprsd_items():
|
|||||||
# installed plugins
|
# installed plugins
|
||||||
plugins = {}
|
plugins = {}
|
||||||
extensions = {}
|
extensions = {}
|
||||||
for finder, name, ispkg in pkgutil.iter_modules():
|
for _finder, name, ispkg in pkgutil.iter_modules():
|
||||||
if name.startswith("aprsd_"):
|
if ispkg and name.startswith('aprsd_'):
|
||||||
print(f"Found aprsd_ module: {name}")
|
|
||||||
if ispkg:
|
|
||||||
module = importlib.import_module(name)
|
module = importlib.import_module(name)
|
||||||
pkgs = walk_package(module)
|
pkgs = walk_package(module)
|
||||||
for pkg in pkgs:
|
for pkg in pkgs:
|
||||||
pkg_info = get_module_info(
|
pkg_info = get_module_info(
|
||||||
module.__name__, pkg.name, module.__path__[0]
|
module.__name__, pkg.name, module.__path__[0]
|
||||||
)
|
)
|
||||||
if "plugin" in name:
|
if 'plugin' in name:
|
||||||
plugins[name] = pkg_info
|
plugins[name] = pkg_info
|
||||||
elif "extension" in name:
|
elif 'extension' in name:
|
||||||
extensions[name] = pkg_info
|
extensions[name] = pkg_info
|
||||||
return plugins, extensions
|
return plugins, extensions
|
||||||
|
|
||||||
@ -131,154 +126,141 @@ def show_built_in_plugins(console):
|
|||||||
cls = entry[1]
|
cls = entry[1]
|
||||||
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
|
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
|
||||||
info = {
|
info = {
|
||||||
"name": cls.__qualname__,
|
'name': cls.__qualname__,
|
||||||
"path": f"{cls.__module__}.{cls.__qualname__}",
|
'path': f'{cls.__module__}.{cls.__qualname__}',
|
||||||
"version": cls.version,
|
'version': cls.version,
|
||||||
"docstring": cls.__doc__,
|
'docstring': cls.__doc__,
|
||||||
"short_desc": cls.short_description,
|
'short_desc': cls.short_description,
|
||||||
}
|
}
|
||||||
|
|
||||||
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
|
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
|
||||||
info["command_regex"] = cls.command_regex
|
info['command_regex'] = cls.command_regex
|
||||||
info["type"] = "RegexCommand"
|
info['type'] = 'RegexCommand'
|
||||||
|
|
||||||
if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
|
if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
|
||||||
info["type"] = "WatchList"
|
info['type'] = 'WatchList'
|
||||||
|
|
||||||
plugins.append(info)
|
plugins.append(info)
|
||||||
|
|
||||||
plugins = sorted(plugins, key=lambda i: i["name"])
|
plugins = sorted(plugins, key=lambda i: i['name'])
|
||||||
|
|
||||||
table = Table(
|
table = Table(
|
||||||
title="[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]",
|
title='[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]',
|
||||||
)
|
)
|
||||||
table.add_column("Plugin Name", style="cyan", no_wrap=True)
|
table.add_column('Plugin Name', style='cyan', no_wrap=True)
|
||||||
table.add_column("Info", style="bold yellow")
|
table.add_column('Info', style='bold yellow')
|
||||||
table.add_column("Type", style="bold green")
|
table.add_column('Type', style='bold green')
|
||||||
table.add_column("Plugin Path", style="bold blue")
|
table.add_column('Plugin Path', style='bold blue')
|
||||||
for entry in plugins:
|
for entry in plugins:
|
||||||
table.add_row(entry["name"], entry["short_desc"], entry["type"], entry["path"])
|
table.add_row(entry['name'], entry['short_desc'], entry['type'], entry['path'])
|
||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
def _get_pypi_packages():
|
def _get_pypi_packages():
|
||||||
query = "aprsd"
|
if simple_r := requests.get(
|
||||||
snippets = []
|
'https://pypi.org/simple',
|
||||||
s = requests.Session()
|
headers={'Accept': 'application/vnd.pypi.simple.v1+json'},
|
||||||
for page in range(1, 3):
|
):
|
||||||
params = {"q": query, "page": page}
|
simple_response = simple_r.json()
|
||||||
r = s.get(PYPI_URL, params=params)
|
else:
|
||||||
soup = BeautifulSoup(r.text, "html.parser")
|
simple_response = {}
|
||||||
snippets += soup.select('a[class*="snippet"]')
|
|
||||||
if not hasattr(s, "start_url"):
|
|
||||||
s.start_url = r.url.rsplit("&page", maxsplit=1).pop(0)
|
|
||||||
|
|
||||||
return snippets
|
key = 'aprsd'
|
||||||
|
matches = [
|
||||||
|
p['name'] for p in simple_response['projects'] if p['name'].startswith(key)
|
||||||
|
]
|
||||||
|
|
||||||
|
packages = []
|
||||||
|
for pkg in matches:
|
||||||
|
# Get info for first match
|
||||||
|
if r := requests.get(
|
||||||
|
f'https://pypi.org/pypi/{pkg}/json',
|
||||||
|
headers={'Accept': 'application/json'},
|
||||||
|
):
|
||||||
|
packages.append(r.json())
|
||||||
|
|
||||||
|
return packages
|
||||||
|
|
||||||
|
|
||||||
def show_pypi_plugins(installed_plugins, console):
|
def show_pypi_plugins(installed_plugins, console):
|
||||||
snippets = _get_pypi_packages()
|
packages = _get_pypi_packages()
|
||||||
|
|
||||||
title = Text.assemble(
|
title = Text.assemble(
|
||||||
("Pypi.org APRSD Installable Plugin Packages\n\n", "bold magenta"),
|
('Pypi.org APRSD Installable Plugin Packages\n\n', 'bold magenta'),
|
||||||
("Install any of the following plugins with\n", "bold yellow"),
|
('Install any of the following plugins with\n', 'bold yellow'),
|
||||||
("'pip install ", "bold white"),
|
("'pip install ", 'bold white'),
|
||||||
("<Plugin Package Name>'", "cyan"),
|
("<Plugin Package Name>'", 'cyan'),
|
||||||
)
|
)
|
||||||
|
|
||||||
table = Table(title=title)
|
table = Table(title=title)
|
||||||
table.add_column("Plugin Package Name", style="cyan", no_wrap=True)
|
table.add_column('Plugin Package Name', style='cyan', no_wrap=True)
|
||||||
table.add_column("Description", style="yellow")
|
table.add_column('Description', style='yellow')
|
||||||
table.add_column("Version", style="yellow", justify="center")
|
table.add_column('Version', style='yellow', justify='center')
|
||||||
table.add_column("Released", style="bold green", justify="center")
|
table.add_column('Released', style='bold green', justify='center')
|
||||||
table.add_column("Installed?", style="red", justify="center")
|
table.add_column('Installed?', style='red', justify='center')
|
||||||
for snippet in snippets:
|
emoji = ':open_file_folder:'
|
||||||
link = urljoin(PYPI_URL, snippet.get("href"))
|
for package in packages:
|
||||||
package = re.sub(
|
link = package['info']['package_url']
|
||||||
r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip()
|
version = package['info']['version']
|
||||||
)
|
package_name = package['info']['name']
|
||||||
version = re.sub(
|
description = package['info']['summary']
|
||||||
r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip()
|
created = package['releases'][version][0]['upload_time']
|
||||||
)
|
|
||||||
created = re.sub(
|
|
||||||
r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip()
|
|
||||||
)
|
|
||||||
description = re.sub(
|
|
||||||
r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip()
|
|
||||||
)
|
|
||||||
emoji = ":open_file_folder:"
|
|
||||||
|
|
||||||
if "aprsd-" not in package or "-plugin" not in package:
|
if 'aprsd-' not in package_name or '-plugin' not in package_name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
under = package.replace("-", "_")
|
under = package_name.replace('-', '_')
|
||||||
if under in installed_plugins:
|
installed = 'Yes' if under in installed_plugins else 'No'
|
||||||
installed = "Yes"
|
|
||||||
else:
|
|
||||||
installed = "No"
|
|
||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
f"[link={link}]{emoji}[/link] {package}",
|
f'[link={link}]{emoji}[/link] {package_name}',
|
||||||
description,
|
description,
|
||||||
version,
|
version,
|
||||||
created,
|
created,
|
||||||
installed,
|
installed,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print("\n")
|
console.print('\n')
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
def show_pypi_extensions(installed_extensions, console):
|
def show_pypi_extensions(installed_extensions, console):
|
||||||
snippets = _get_pypi_packages()
|
packages = _get_pypi_packages()
|
||||||
|
|
||||||
title = Text.assemble(
|
title = Text.assemble(
|
||||||
("Pypi.org APRSD Installable Extension Packages\n\n", "bold magenta"),
|
('Pypi.org APRSD Installable Extension Packages\n\n', 'bold magenta'),
|
||||||
("Install any of the following extensions by running\n", "bold yellow"),
|
('Install any of the following extensions by running\n', 'bold yellow'),
|
||||||
("'pip install ", "bold white"),
|
("'pip install ", 'bold white'),
|
||||||
("<Plugin Package Name>'", "cyan"),
|
("<Plugin Package Name>'", 'cyan'),
|
||||||
)
|
)
|
||||||
table = Table(title=title)
|
table = Table(title=title)
|
||||||
table.add_column("Extension Package Name", style="cyan", no_wrap=True)
|
table.add_column('Extension Package Name', style='cyan', no_wrap=True)
|
||||||
table.add_column("Description", style="yellow")
|
table.add_column('Description', style='yellow')
|
||||||
table.add_column("Version", style="yellow", justify="center")
|
table.add_column('Version', style='yellow', justify='center')
|
||||||
table.add_column("Released", style="bold green", justify="center")
|
table.add_column('Released', style='bold green', justify='center')
|
||||||
table.add_column("Installed?", style="red", justify="center")
|
table.add_column('Installed?', style='red', justify='center')
|
||||||
for snippet in snippets:
|
emoji = ':open_file_folder:'
|
||||||
link = urljoin(PYPI_URL, snippet.get("href"))
|
|
||||||
package = re.sub(
|
|
||||||
r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip()
|
|
||||||
)
|
|
||||||
version = re.sub(
|
|
||||||
r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip()
|
|
||||||
)
|
|
||||||
created = re.sub(
|
|
||||||
r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip()
|
|
||||||
)
|
|
||||||
description = re.sub(
|
|
||||||
r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip()
|
|
||||||
)
|
|
||||||
emoji = ":open_file_folder:"
|
|
||||||
|
|
||||||
if "aprsd-" not in package or "-extension" not in package:
|
for package in packages:
|
||||||
|
link = package['info']['package_url']
|
||||||
|
version = package['info']['version']
|
||||||
|
package_name = package['info']['name']
|
||||||
|
description = package['info']['summary']
|
||||||
|
created = package['releases'][version][0]['upload_time']
|
||||||
|
if 'aprsd-' not in package_name or '-extension' not in package_name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
under = package.replace("-", "_")
|
under = package_name.replace('-', '_')
|
||||||
if under in installed_extensions:
|
installed = 'Yes' if under in installed_extensions else 'No'
|
||||||
installed = "Yes"
|
|
||||||
else:
|
|
||||||
installed = "No"
|
|
||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
f"[link={link}]{emoji}[/link] {package}",
|
f'[link={link}]{emoji}[/link] {package_name}',
|
||||||
description,
|
description,
|
||||||
version,
|
version,
|
||||||
created,
|
created,
|
||||||
installed,
|
installed,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print("\n")
|
console.print('\n')
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
@ -287,24 +269,24 @@ def show_installed_plugins(installed_plugins, console):
|
|||||||
return
|
return
|
||||||
|
|
||||||
table = Table(
|
table = Table(
|
||||||
title="[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]",
|
title='[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]',
|
||||||
)
|
)
|
||||||
table.add_column("Package Name", style=" bold white", no_wrap=True)
|
table.add_column('Package Name', style=' bold white', no_wrap=True)
|
||||||
table.add_column("Plugin Name", style="cyan", no_wrap=True)
|
table.add_column('Plugin Name', style='cyan', no_wrap=True)
|
||||||
table.add_column("Version", style="yellow", justify="center")
|
table.add_column('Version', style='yellow', justify='center')
|
||||||
table.add_column("Type", style="bold green")
|
table.add_column('Type', style='bold green')
|
||||||
table.add_column("Plugin Path", style="bold blue")
|
table.add_column('Plugin Path', style='bold blue')
|
||||||
for name in installed_plugins:
|
for name in installed_plugins:
|
||||||
for plugin in installed_plugins[name]:
|
for plugin in installed_plugins[name]:
|
||||||
table.add_row(
|
table.add_row(
|
||||||
name.replace("_", "-"),
|
name.replace('_', '-'),
|
||||||
plugin["name"],
|
plugin['name'],
|
||||||
plugin["version"],
|
plugin['version'],
|
||||||
plugin_type(plugin["obj"]),
|
plugin_type(plugin['obj']),
|
||||||
plugin["path"],
|
plugin['path'],
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print("\n")
|
console.print('\n')
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
@ -316,14 +298,14 @@ def list_plugins(ctx):
|
|||||||
"""List the built in plugins available to APRSD."""
|
"""List the built in plugins available to APRSD."""
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
with console.status("Show Built-in Plugins") as status:
|
with console.status('Show Built-in Plugins') as status:
|
||||||
show_built_in_plugins(console)
|
show_built_in_plugins(console)
|
||||||
|
|
||||||
status.update("Fetching pypi.org plugins")
|
status.update('Fetching pypi.org plugins')
|
||||||
installed_plugins = get_installed_plugins()
|
installed_plugins = get_installed_plugins()
|
||||||
show_pypi_plugins(installed_plugins, console)
|
show_pypi_plugins(installed_plugins, console)
|
||||||
|
|
||||||
status.update("Looking for installed APRSD plugins")
|
status.update('Looking for installed APRSD plugins')
|
||||||
show_installed_plugins(installed_plugins, console)
|
show_installed_plugins(installed_plugins, console)
|
||||||
|
|
||||||
|
|
||||||
@ -335,7 +317,9 @@ def list_extensions(ctx):
|
|||||||
"""List the built in plugins available to APRSD."""
|
"""List the built in plugins available to APRSD."""
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
with console.status("Show APRSD Extensions") as status:
|
with console.status('Show APRSD Extensions') as status:
|
||||||
status.update("Fetching pypi.org APRSD Extensions")
|
status.update('Fetching pypi.org APRSD Extensions')
|
||||||
|
|
||||||
|
status.update('Looking for installed APRSD Extensions')
|
||||||
installed_extensions = get_installed_extensions()
|
installed_extensions = get_installed_extensions()
|
||||||
show_pypi_extensions(installed_extensions, console)
|
show_pypi_extensions(installed_extensions, console)
|
||||||
|
@ -113,6 +113,7 @@ class ListenStatsThread(APRSDThread):
|
|||||||
stats_json = collector.Collector().collect()
|
stats_json = collector.Collector().collect()
|
||||||
stats = stats_json["PacketList"]
|
stats = stats_json["PacketList"]
|
||||||
total_rx = stats["rx"]
|
total_rx = stats["rx"]
|
||||||
|
packet_count = len(stats["packets"])
|
||||||
rx_delta = total_rx - self._last_total_rx
|
rx_delta = total_rx - self._last_total_rx
|
||||||
rate = rx_delta / 10
|
rate = rx_delta / 10
|
||||||
|
|
||||||
@ -120,7 +121,8 @@ class ListenStatsThread(APRSDThread):
|
|||||||
LOGU.opt(colors=True).info(
|
LOGU.opt(colors=True).info(
|
||||||
f"<green>RX Rate: {rate} pps</green> "
|
f"<green>RX Rate: {rate} pps</green> "
|
||||||
f"<yellow>Total RX: {total_rx}</yellow> "
|
f"<yellow>Total RX: {total_rx}</yellow> "
|
||||||
f"<red>RX Last 10 secs: {rx_delta}</red>",
|
f"<red>RX Last 10 secs: {rx_delta}</red> "
|
||||||
|
f"<white>Packets in PacketList: {packet_count}</white>",
|
||||||
)
|
)
|
||||||
self._last_total_rx = total_rx
|
self._last_total_rx = total_rx
|
||||||
|
|
||||||
|
BIN
aprsd_logo.png
Normal file
BIN
aprsd_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
@ -1,6 +1,6 @@
|
|||||||
FROM python:3.11-slim AS build
|
FROM ghcr.io/astral-sh/uv:python3.11-alpine AS build
|
||||||
|
|
||||||
ARG VERSION=3.4.0
|
ARG VERSION=3.5.0
|
||||||
# pass this in as 'dev' if you want to install from github repo vs pypi
|
# pass this in as 'dev' if you want to install from github repo vs pypi
|
||||||
ARG INSTALL_TYPE=pypi
|
ARG INSTALL_TYPE=pypi
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ ARG BRANCH=master
|
|||||||
ARG BUILDX_QEMU_ENV
|
ARG BUILDX_QEMU_ENV
|
||||||
|
|
||||||
ENV APRSD_BRANCH=${BRANCH:-master}
|
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}
|
||||||
@ -23,49 +23,59 @@ ENV PIP_DEFAULT_TIMEOUT=100 \
|
|||||||
PIP_NO_CACHE_DIR=1
|
PIP_NO_CACHE_DIR=1
|
||||||
|
|
||||||
|
|
||||||
RUN set -ex \
|
#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 \
|
# && usermod -aG sudo appuser
|
||||||
|
|
||||||
|
RUN apk add git
|
||||||
|
|
||||||
|
#RUN set -ex \
|
||||||
# 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 \
|
||||||
&& apt-get install -y git build-essential curl libffi-dev \
|
# && apt-get install -y git build-essential curl libffi-dev \
|
||||||
python3-dev libssl-dev libxml2-dev libxslt-dev telnet sudo fortune \
|
# python3-dev libssl-dev libxml2-dev libxslt-dev telnet sudo fortune \
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
# Clean up
|
# Clean up
|
||||||
&& apt-get autoremove -y \
|
# && apt-get autoremove -y \
|
||||||
&& apt-get clean -y
|
# && apt-get clean -y
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Final stage
|
### Final stage
|
||||||
FROM build AS install
|
FROM build AS install
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN pip3 install -U pip
|
|
||||||
RUN mkdir /config
|
RUN mkdir /config
|
||||||
RUN chown -R appuser:appgroup /app
|
RUN uv venv
|
||||||
RUN chown -R appuser:appgroup /config
|
RUN uv pip install -U pip
|
||||||
USER appuser
|
|
||||||
RUN if [ "$INSTALL_TYPE" = "pypi" ]; then \
|
RUN if [ "$INSTALL_TYPE" = "pypi" ]; then \
|
||||||
pip3 install aprsd==$APRSD_PIP_VERSION; \
|
uv pip install aprsd==$APRSD_PIP_VERSION; \
|
||||||
elif [ "$INSTALL_TYPE" = "github" ]; then \
|
elif [ "$INSTALL_TYPE" = "github" ]; then \
|
||||||
git clone -b $APRSD_BRANCH https://github.com/craigerl/aprsd; \
|
git clone -b $APRSD_BRANCH https://github.com/craigerl/aprsd; \
|
||||||
cd /app/aprsd && pip install .; \
|
cd /app/aprsd && uv pip install .; \
|
||||||
ls -al /app/.local/lib/python3.11/site-packages/aprsd*; \
|
ls -al /app/.venv/lib/python3.11/site-packages/aprsd*; \
|
||||||
fi
|
fi
|
||||||
RUN pip install gevent uwsgi
|
# RUN uv pip install gevent uwsgi
|
||||||
RUN echo "PATH=\$PATH:/usr/games:/app/.local/bin" >> /app/.bashrc
|
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 uv run aprsd sample-config > /config/aprsd.conf
|
||||||
RUN aprsd --version
|
RUN uv run aprsd --version
|
||||||
|
|
||||||
|
|
||||||
|
FROM ghcr.io/astral-sh/uv:python3.11-alpine
|
||||||
|
RUN apk add fortune bash git
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||||
|
COPY --from=install /app /app
|
||||||
|
COPY --from=install /config config
|
||||||
ADD bin/setup.sh /app
|
ADD bin/setup.sh /app
|
||||||
ADD bin/admin.sh /app
|
ADD bin/admin.sh /app
|
||||||
|
ADD bin/listen.sh /app
|
||||||
|
ADD bin/healthcheck.sh /app
|
||||||
|
|
||||||
|
RUN ls -al /app
|
||||||
FROM install AS final
|
|
||||||
# For the web admin interface
|
# For the web admin interface
|
||||||
EXPOSE 8001
|
EXPOSE 8001
|
||||||
|
|
||||||
@ -74,7 +84,7 @@ ENTRYPOINT ["/app/setup.sh"]
|
|||||||
CMD ["server"]
|
CMD ["server"]
|
||||||
|
|
||||||
# Set the user to run the application
|
# Set the user to run the application
|
||||||
USER appuser
|
# USER appuser
|
||||||
|
|
||||||
HEALTHCHECK --interval=1m --timeout=12s --start-period=30s \
|
HEALTHCHECK --interval=1m --timeout=12s --start-period=30s \
|
||||||
CMD aprsd healthcheck --config /config/aprsd.conf --loglevel DEBUG
|
CMD /app/healthcheck.sh
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
source /app/.venv/bin/activate
|
||||||
|
|
||||||
if [ ! -z "${APRSD_PLUGINS}" ]; then
|
if [ ! -z "${APRSD_PLUGINS}" ]; then
|
||||||
OLDIFS=$IFS
|
OLDIFS=$IFS
|
||||||
IFS=','
|
IFS=','
|
||||||
@ -9,7 +11,7 @@ if [ ! -z "${APRSD_PLUGINS}" ]; then
|
|||||||
IFS=$OLDIFS
|
IFS=$OLDIFS
|
||||||
# call your procedure/other scripts here below
|
# call your procedure/other scripts here below
|
||||||
echo "Installing '$plugin'"
|
echo "Installing '$plugin'"
|
||||||
pip3 install --user $plugin
|
uv pip install $plugin
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
||||||
@ -20,7 +22,7 @@ if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
|||||||
IFS=$OLDIFS
|
IFS=$OLDIFS
|
||||||
# call your procedure/other scripts here below
|
# call your procedure/other scripts here below
|
||||||
echo "Installing '$extension'"
|
echo "Installing '$extension'"
|
||||||
pip3 install --user $extension
|
uv pip install $extension
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -40,5 +42,7 @@ fi
|
|||||||
export COLUMNS=200
|
export COLUMNS=200
|
||||||
#exec gunicorn -b :8000 --workers 4 "aprsd.admin_web:create_app(config_file='$APRSD_CONFIG', log_level='$LOG_LEVEL')"
|
#exec gunicorn -b :8000 --workers 4 "aprsd.admin_web:create_app(config_file='$APRSD_CONFIG', log_level='$LOG_LEVEL')"
|
||||||
# exec gunicorn -b :8000 --workers 4 "aprsd.wsgi:app"
|
# exec gunicorn -b :8000 --workers 4 "aprsd.wsgi:app"
|
||||||
exec uwsgi --http :8000 --gevent 1000 --http-websockets --master -w aprsd.wsgi --callable app
|
#exec uwsgi --http :8000 --gevent 1000 --http-websockets --master -w aprsd.wsgi --callable app
|
||||||
#exec aprsd listen -c $APRSD_CONFIG --loglevel ${LOG_LEVEL} ${APRSD_LOAD_PLUGINS} ${APRSD_LISTEN_FILTER}
|
#exec aprsd listen -c $APRSD_CONFIG --loglevel ${LOG_LEVEL} ${APRSD_LOAD_PLUGINS} ${APRSD_LISTEN_FILTER}
|
||||||
|
#
|
||||||
|
uv run aprsd admin web -c $APRSD_CONFIG --loglevel ${LOG_LEVEL}
|
||||||
|
22
docker/bin/healthcheck.sh
Executable file
22
docker/bin/healthcheck.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# This is the docker container healthcheck script
|
||||||
|
# It's assumed to be running in a working aprsd container.
|
||||||
|
set -x
|
||||||
|
|
||||||
|
source /app/.venv/bin/activate
|
||||||
|
|
||||||
|
if [ -z "${LOG_LEVEL}" ] || [[ ! "${LOG_LEVEL}" =~ ^(CRITICAL|ERROR|WARNING|INFO)$ ]]; then
|
||||||
|
LOG_LEVEL="DEBUG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Log level is set to ${LOG_LEVEL}";
|
||||||
|
|
||||||
|
# check to see if there is a config file
|
||||||
|
APRSD_CONFIG="/config/aprsd.conf"
|
||||||
|
if [ ! -e "$APRSD_CONFIG" ]; then
|
||||||
|
echo "'$APRSD_CONFIG' File does not exist. Creating."
|
||||||
|
aprsd sample-config > $APRSD_CONFIG
|
||||||
|
fi
|
||||||
|
|
||||||
|
uv run aprsd healthcheck --config $APRSD_CONFIG --loglevel ${LOG_LEVEL}
|
@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
source /app/.venv/bin/activate
|
||||||
|
|
||||||
if [ ! -z "${APRSD_PLUGINS}" ]; then
|
if [ ! -z "${APRSD_PLUGINS}" ]; then
|
||||||
OLDIFS=$IFS
|
OLDIFS=$IFS
|
||||||
IFS=','
|
IFS=','
|
||||||
@ -9,7 +11,7 @@ if [ ! -z "${APRSD_PLUGINS}" ]; then
|
|||||||
IFS=$OLDIFS
|
IFS=$OLDIFS
|
||||||
# call your procedure/other scripts here below
|
# call your procedure/other scripts here below
|
||||||
echo "Installing '$plugin'"
|
echo "Installing '$plugin'"
|
||||||
pip3 install --user $plugin
|
uv pip install --user $plugin
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
|||||||
IFS=$OLDIFS
|
IFS=$OLDIFS
|
||||||
# call your procedure/other scripts here below
|
# call your procedure/other scripts here below
|
||||||
echo "Installing '$extension'"
|
echo "Installing '$extension'"
|
||||||
pip3 install --user $extension
|
uv pip install --user $extension
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -35,7 +37,7 @@ echo "Log level is set to ${LOG_LEVEL}";
|
|||||||
APRSD_CONFIG="/config/aprsd.conf"
|
APRSD_CONFIG="/config/aprsd.conf"
|
||||||
if [ ! -e "$APRSD_CONFIG" ]; then
|
if [ ! -e "$APRSD_CONFIG" ]; then
|
||||||
echo "'$APRSD_CONFIG' File does not exist. Creating."
|
echo "'$APRSD_CONFIG' File does not exist. Creating."
|
||||||
aprsd sample-config > $APRSD_CONFIG
|
uv run aprsd sample-config > $APRSD_CONFIG
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export COLUMNS=200
|
export COLUMNS=200
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
source /app/.venv/bin/activate
|
||||||
|
|
||||||
# The default command
|
# The default command
|
||||||
# Override the command in docker-compose.yml to change
|
# Override the command in docker-compose.yml to change
|
||||||
# what command you want to run in the container
|
# what command you want to run in the container
|
||||||
@ -18,7 +20,7 @@ if [ ! -z "${APRSD_PLUGINS}" ]; then
|
|||||||
IFS=$OLDIFS
|
IFS=$OLDIFS
|
||||||
# call your procedure/other scripts here below
|
# call your procedure/other scripts here below
|
||||||
echo "Installing '$plugin'"
|
echo "Installing '$plugin'"
|
||||||
pip3 install --user $plugin
|
uv pip install $plugin
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -30,7 +32,7 @@ if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
|||||||
IFS=$OLDIFS
|
IFS=$OLDIFS
|
||||||
# call your procedure/other scripts here below
|
# call your procedure/other scripts here below
|
||||||
echo "Installing '$extension'"
|
echo "Installing '$extension'"
|
||||||
pip3 install --user $extension
|
uv pip install $extension
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -68,14 +68,6 @@ aprsd.cmds.server module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.cmds.webchat module
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.cmds.webchat
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -44,14 +44,6 @@ aprsd.conf.plugin\_common module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.conf.plugin\_email module
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.conf.plugin_email
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -4,14 +4,6 @@ aprsd.plugins package
|
|||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
aprsd.plugins.email module
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.plugins.email
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.plugins.fortune module
|
aprsd.plugins.fortune module
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
@ -20,14 +12,6 @@ aprsd.plugins.fortune module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.plugins.location module
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.plugins.location
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.plugins.notify module
|
aprsd.plugins.notify module
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ Subpackages
|
|||||||
aprsd.stats
|
aprsd.stats
|
||||||
aprsd.threads
|
aprsd.threads
|
||||||
aprsd.utils
|
aprsd.utils
|
||||||
aprsd.web
|
|
||||||
|
|
||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
@ -45,14 +44,6 @@ aprsd.main module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.messaging module
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.messaging
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.plugin module
|
aprsd.plugin module
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@ -69,14 +60,6 @@ aprsd.plugin\_utils module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.wsgi module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.wsgi
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -12,18 +12,10 @@ aprsd.threads.aprsd module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.threads.keep\_alive module
|
aprsd.threads.keepalive module
|
||||||
--------------------------------
|
------------------------------
|
||||||
|
|
||||||
.. automodule:: aprsd.threads.keep_alive
|
.. automodule:: aprsd.threads.keepalive
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.threads.log\_monitor module
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.threads.log_monitor
|
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
@ -28,6 +28,14 @@ aprsd.utils.json module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.utils.keepalive\_collector module
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.utils.keepalive_collector
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.utils.objectstore module
|
aprsd.utils.objectstore module
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
aprsd.web.admin package
|
|
||||||
=======================
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.web.admin
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,18 +0,0 @@
|
|||||||
aprsd.web package
|
|
||||||
=================
|
|
||||||
|
|
||||||
Subpackages
|
|
||||||
-----------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 4
|
|
||||||
|
|
||||||
aprsd.web.admin
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.web
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,7 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aprsd import plugin
|
from aprsd import packets, plugin
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
@ -14,7 +13,7 @@ class HelloPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
command_regex = "^[hH]"
|
command_regex = "^[hH]"
|
||||||
command_name = "hello"
|
command_name = "hello"
|
||||||
|
|
||||||
def process(self, packet):
|
def process(self, packet: packets.MessagePacket):
|
||||||
LOG.info("HelloPlugin")
|
LOG.info("HelloPlugin")
|
||||||
reply = f"Hello '{packet.from_call}'"
|
reply = f"Hello '{packet.from_call}'"
|
||||||
return reply
|
return reply
|
||||||
|
@ -18,7 +18,7 @@ description = "APRSd is a APRS-IS server that can be used to connect to APRS-IS
|
|||||||
# 'Programming Language' classifiers in this file, 'pip install' will check this
|
# 'Programming Language' classifiers in this file, 'pip install' will check this
|
||||||
# and refuse to install the project if the version does not match. See
|
# and refuse to install the project if the version does not match. See
|
||||||
# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
|
# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
dynamic = ["version", "dependencies", "optional-dependencies"]
|
dynamic = ["version", "dependencies", "optional-dependencies"]
|
||||||
|
|
||||||
@ -173,3 +173,20 @@ skip_gitignore = true
|
|||||||
branch = true
|
branch = true
|
||||||
|
|
||||||
[tool.setuptools_scm]
|
[tool.setuptools_scm]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88
|
||||||
|
select = [
|
||||||
|
"F", # pyflakes rules
|
||||||
|
"E", # pycodestyle error rules
|
||||||
|
"W", # pycodestyle warning rules
|
||||||
|
"B", # flake8-bugbear rules
|
||||||
|
# "I", # isort rules
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"E501", # line-too-long
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
indent-style = "space"
|
||||||
|
quote-style = "single"
|
||||||
|
@ -1,40 +1,38 @@
|
|||||||
#
|
# This file was autogenerated by uv via the following command:
|
||||||
# This file is autogenerated by pip-compile with Python 3.10
|
# uv pip compile --resolver backtracking --annotation-style=line requirements-dev.in -o requirements-dev.txt
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile --annotation-style=line requirements-dev.in
|
|
||||||
#
|
|
||||||
alabaster==1.0.0 # via sphinx
|
alabaster==1.0.0 # via sphinx
|
||||||
babel==2.16.0 # via sphinx
|
babel==2.16.0 # via sphinx
|
||||||
build==1.2.2.post1 # via -r requirements-dev.in, pip-tools
|
build==1.2.2.post1 # via pip-tools, -r requirements-dev.in
|
||||||
cachetools==5.5.0 # via tox
|
cachetools==5.5.0 # via tox
|
||||||
certifi==2024.12.14 # via requests
|
certifi==2024.12.14 # via requests
|
||||||
cfgv==3.4.0 # via pre-commit
|
cfgv==3.4.0 # via pre-commit
|
||||||
chardet==5.2.0 # via tox
|
chardet==5.2.0 # via tox
|
||||||
charset-normalizer==3.4.0 # via requests
|
charset-normalizer==3.4.1 # via requests
|
||||||
click==8.1.7 # via pip-tools
|
click==8.1.8 # via pip-tools
|
||||||
colorama==0.4.6 # via tox
|
colorama==0.4.6 # via tox
|
||||||
distlib==0.3.9 # via virtualenv
|
distlib==0.3.9 # via virtualenv
|
||||||
docutils==0.21.2 # via m2r, sphinx
|
docutils==0.21.2 # via m2r, sphinx
|
||||||
filelock==3.16.1 # via tox, virtualenv
|
filelock==3.16.1 # via tox, virtualenv
|
||||||
identify==2.6.3 # via pre-commit
|
identify==2.6.5 # via pre-commit
|
||||||
idna==3.10 # via requests
|
idna==3.10 # via requests
|
||||||
imagesize==1.4.1 # via sphinx
|
imagesize==1.4.1 # via sphinx
|
||||||
jinja2==3.1.4 # via sphinx
|
jinja2==3.1.5 # via sphinx
|
||||||
m2r==0.3.1 # via -r requirements-dev.in
|
m2r==0.3.1 # via -r requirements-dev.in
|
||||||
markupsafe==3.0.2 # via jinja2
|
markupsafe==3.0.2 # via jinja2
|
||||||
mistune==0.8.4 # via m2r
|
mistune==0.8.4 # via m2r
|
||||||
nodeenv==1.9.1 # via pre-commit
|
nodeenv==1.9.1 # via pre-commit
|
||||||
packaging==24.2 # via build, pyproject-api, sphinx, tox
|
packaging==24.2 # via build, pyproject-api, sphinx, tox
|
||||||
|
pip==24.3.1 # via pip-tools, -r requirements-dev.in
|
||||||
pip-tools==7.4.1 # via -r requirements-dev.in
|
pip-tools==7.4.1 # via -r requirements-dev.in
|
||||||
platformdirs==4.3.6 # via tox, virtualenv
|
platformdirs==4.3.6 # via tox, virtualenv
|
||||||
pluggy==1.5.0 # via tox
|
pluggy==1.5.0 # via tox
|
||||||
pre-commit==4.0.1 # via -r requirements-dev.in
|
pre-commit==4.0.1 # via -r requirements-dev.in
|
||||||
pygments==2.18.0 # via sphinx
|
pygments==2.19.1 # via sphinx
|
||||||
pyproject-api==1.8.0 # via tox
|
pyproject-api==1.8.0 # via tox
|
||||||
pyproject-hooks==1.2.0 # via build, pip-tools
|
pyproject-hooks==1.2.0 # via build, pip-tools
|
||||||
pyyaml==6.0.2 # via pre-commit
|
pyyaml==6.0.2 # via pre-commit
|
||||||
requests==2.32.3 # via sphinx
|
requests==2.32.3 # via sphinx
|
||||||
|
setuptools==75.7.0 # via pip-tools
|
||||||
snowballstemmer==2.2.0 # via sphinx
|
snowballstemmer==2.2.0 # via sphinx
|
||||||
sphinx==8.1.3 # via -r requirements-dev.in
|
sphinx==8.1.3 # via -r requirements-dev.in
|
||||||
sphinxcontrib-applehelp==2.0.0 # via sphinx
|
sphinxcontrib-applehelp==2.0.0 # via sphinx
|
||||||
@ -46,10 +44,6 @@ sphinxcontrib-serializinghtml==2.0.0 # via sphinx
|
|||||||
tomli==2.2.1 # via build, pip-tools, pyproject-api, sphinx, tox
|
tomli==2.2.1 # via build, pip-tools, pyproject-api, sphinx, tox
|
||||||
tox==4.23.2 # via -r requirements-dev.in
|
tox==4.23.2 # via -r requirements-dev.in
|
||||||
typing-extensions==4.12.2 # via tox
|
typing-extensions==4.12.2 # via tox
|
||||||
urllib3==2.2.3 # via requests
|
urllib3==2.3.0 # via requests
|
||||||
virtualenv==20.28.0 # via pre-commit, tox
|
virtualenv==20.28.1 # via pre-commit, tox
|
||||||
wheel==0.45.1 # via -r requirements-dev.in, pip-tools
|
wheel==0.45.1 # via pip-tools, -r requirements-dev.in
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
|
||||||
# pip
|
|
||||||
# setuptools
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
aprslib>=0.7.0
|
aprslib>=0.7.0
|
||||||
# For the list-plugins pypi.org search scraping
|
|
||||||
beautifulsoup4
|
|
||||||
click
|
click
|
||||||
dataclasses-json
|
dataclasses-json
|
||||||
haversine
|
haversine
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
#
|
# This file was autogenerated by uv via the following command:
|
||||||
# This file is autogenerated by pip-compile with Python 3.10
|
# uv pip compile --resolver backtracking --annotation-style=line requirements.in -o requirements.txt
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile --annotation-style=line requirements.in
|
|
||||||
#
|
|
||||||
aprslib==0.7.2 # via -r requirements.in
|
aprslib==0.7.2 # via -r requirements.in
|
||||||
attrs==24.3.0 # via ax253, kiss3, rush
|
attrs==24.3.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
|
|
||||||
bitarray==3.0.0 # via ax253, kiss3
|
bitarray==3.0.0 # via ax253, kiss3
|
||||||
certifi==2024.12.14 # via requests
|
certifi==2024.12.14 # via requests
|
||||||
charset-normalizer==3.4.0 # via requests
|
charset-normalizer==3.4.1 # via requests
|
||||||
click==8.1.7 # via -r requirements.in
|
click==8.1.8 # via -r requirements.in
|
||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
dataclasses-json==0.6.7 # 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
|
||||||
@ -20,7 +15,7 @@ idna==3.10 # via requests
|
|||||||
importlib-metadata==8.5.0 # via ax253, kiss3
|
importlib-metadata==8.5.0 # via ax253, kiss3
|
||||||
kiss3==8.0.0 # via -r requirements.in
|
kiss3==8.0.0 # via -r requirements.in
|
||||||
loguru==0.7.3 # via -r requirements.in
|
loguru==0.7.3 # via -r requirements.in
|
||||||
marshmallow==3.23.2 # via dataclasses-json
|
marshmallow==3.24.1 # via dataclasses-json
|
||||||
mypy-extensions==1.0.0 # via typing-inspect
|
mypy-extensions==1.0.0 # via typing-inspect
|
||||||
netaddr==1.3.0 # via oslo-config
|
netaddr==1.3.0 # via oslo-config
|
||||||
oslo-config==9.7.0 # via -r requirements.in
|
oslo-config==9.7.0 # via -r requirements.in
|
||||||
@ -28,16 +23,15 @@ oslo-i18n==6.5.0 # via oslo-config
|
|||||||
packaging==24.2 # via marshmallow
|
packaging==24.2 # via marshmallow
|
||||||
pbr==6.1.0 # via 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.18.0 # via rich
|
pygments==2.19.1 # 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
|
||||||
pytz==2024.2 # via -r requirements.in
|
pytz==2024.2 # via -r requirements.in
|
||||||
pyyaml==6.0.2 # via oslo-config
|
pyyaml==6.0.2 # via oslo-config
|
||||||
requests==2.32.3 # via -r requirements.in, oslo-config, update-checker
|
requests==2.32.3 # via oslo-config, update-checker, -r requirements.in
|
||||||
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
|
||||||
soupsieve==2.6 # via beautifulsoup4
|
|
||||||
stevedore==5.4.0 # via oslo-config
|
stevedore==5.4.0 # via oslo-config
|
||||||
thesmuggler==1.0.1 # via -r requirements.in
|
thesmuggler==1.0.1 # via -r requirements.in
|
||||||
timeago==1.0.16 # via -r requirements.in
|
timeago==1.0.16 # via -r requirements.in
|
||||||
@ -45,6 +39,6 @@ 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.3 # via requests
|
urllib3==2.3.0 # via requests
|
||||||
wrapt==1.17.0 # via -r requirements.in, debtcollector
|
wrapt==1.17.0 # via debtcollector, -r requirements.in
|
||||||
zipp==3.21.0 # via importlib-metadata
|
zipp==3.21.0 # via importlib-metadata
|
||||||
|
@ -2,9 +2,9 @@ from unittest import mock
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import (
|
from aprsd import ( # noqa: F401
|
||||||
client,
|
client,
|
||||||
conf, # noqa: F401
|
conf,
|
||||||
packets,
|
packets,
|
||||||
)
|
)
|
||||||
from aprsd.plugins import notify as notify_plugin
|
from aprsd.plugins import notify as notify_plugin
|
||||||
|
@ -3,8 +3,8 @@ from unittest import mock
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import (
|
from aprsd import ( # noqa: F401
|
||||||
conf, # noqa: F401
|
conf,
|
||||||
packets,
|
packets,
|
||||||
plugins,
|
plugins,
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user