mirror of
https://github.com/craigerl/aprsd.git
synced 2025-06-14 12:22:25 -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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get Branch Name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v8
|
||||
@ -30,16 +29,16 @@ jobs:
|
||||
run: |
|
||||
echo "Selected Branch '${{ steps.extract_branch.outputs.branch }}'"
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker HUB
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build the Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: "{{defaultContext}}:docker"
|
||||
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:
|
||||
- "**"
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
- "*.*.*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11"]
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
@ -35,21 +35,20 @@ jobs:
|
||||
needs: tox
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get Branch Name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v8
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker HUB
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build the Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: "{{defaultContext}}:docker"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
4
.github/workflows/python.yml
vendored
4
.github/workflows/python.yml
vendored
@ -9,9 +9,9 @@ jobs:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- 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
|
||||
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.5.0
|
||||
rev: v2.7.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.4
|
||||
rev: v0.9.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
###### Relevant part below ######
|
||||
@ -26,3 +26,16 @@ repos:
|
||||
args: ["check", "--select", "I", "--fix"]
|
||||
###### Relevant part above ######
|
||||
- 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
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger("APRSD")
|
||||
LOG = logging.getLogger('APRSD')
|
||||
|
||||
|
||||
class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
||||
@ -24,12 +24,12 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
||||
path = []
|
||||
|
||||
def __init__(self):
|
||||
LOG.info("Starting APRSDFakeClient client.")
|
||||
self.path = ["WIDE1-1", "WIDE2-1"]
|
||||
LOG.info('Starting APRSDFakeClient client.')
|
||||
self.path = ['WIDE1-1', 'WIDE2-1']
|
||||
|
||||
def stop(self):
|
||||
self.thread_stop = True
|
||||
LOG.info("Shutdown APRSDFakeClient client.")
|
||||
LOG.info('Shutdown APRSDFakeClient client.')
|
||||
|
||||
def is_alive(self):
|
||||
"""If the connection is alive or not."""
|
||||
@ -38,35 +38,31 @@ class APRSDFakeClient(metaclass=trace.TraceWrapperMetaclass):
|
||||
@wrapt.synchronized(lock)
|
||||
def send(self, packet: core.Packet):
|
||||
"""Send an APRS Message object."""
|
||||
LOG.info(f"Sending packet: {packet}")
|
||||
LOG.info(f'Sending packet: {packet}')
|
||||
payload = None
|
||||
if isinstance(packet, core.Packet):
|
||||
packet.prepare()
|
||||
payload = packet.payload.encode("US-ASCII")
|
||||
if packet.path:
|
||||
packet.path
|
||||
else:
|
||||
self.path
|
||||
payload = packet.payload.encode('US-ASCII')
|
||||
else:
|
||||
msg_payload = f"{packet.raw}{{{str(packet.msgNo)}"
|
||||
msg_payload = f'{packet.raw}{{{str(packet.msgNo)}'
|
||||
payload = (
|
||||
":{:<9}:{}".format(
|
||||
':{:<9}:{}'.format(
|
||||
packet.to_call,
|
||||
msg_payload,
|
||||
)
|
||||
).encode("US-ASCII")
|
||||
).encode('US-ASCII')
|
||||
|
||||
LOG.debug(
|
||||
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):
|
||||
LOG.debug("Start non blocking FAKE consumer")
|
||||
LOG.debug('Start non blocking FAKE consumer')
|
||||
# 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 = core.factory(pkt_raw)
|
||||
callback(packet=pkt)
|
||||
LOG.debug(f"END blocking FAKE consumer {self}")
|
||||
LOG.debug(f'END blocking FAKE consumer {self}')
|
||||
time.sleep(8)
|
||||
|
@ -16,11 +16,11 @@ from aprsd.main import cli
|
||||
from aprsd.utils import trace
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger("APRSD")
|
||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||
LOG = logging.getLogger('APRSD')
|
||||
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
|
||||
def dev(ctx):
|
||||
pass
|
||||
@ -29,37 +29,37 @@ def dev(ctx):
|
||||
@dev.command()
|
||||
@cli_helper.add_options(cli_helper.common_options)
|
||||
@click.option(
|
||||
"--aprs-login",
|
||||
envvar="APRS_LOGIN",
|
||||
'--aprs-login',
|
||||
envvar='APRS_LOGIN',
|
||||
show_envvar=True,
|
||||
help="What callsign to send the message from.",
|
||||
help='What callsign to send the message from.',
|
||||
)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--plugin",
|
||||
"plugin_path",
|
||||
'-p',
|
||||
'--plugin',
|
||||
'plugin_path',
|
||||
show_default=True,
|
||||
default=None,
|
||||
help="The plugin to run. Ex: aprsd.plugins.ping.PingPlugin",
|
||||
help='The plugin to run. Ex: aprsd.plugins.ping.PingPlugin',
|
||||
)
|
||||
@click.option(
|
||||
"-a",
|
||||
"--all",
|
||||
"load_all",
|
||||
'-a',
|
||||
'--all',
|
||||
'load_all',
|
||||
show_default=True,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Load all the plugins in config?",
|
||||
help='Load all the plugins in config?',
|
||||
)
|
||||
@click.option(
|
||||
"-n",
|
||||
"--num",
|
||||
"number",
|
||||
'-n',
|
||||
'--num',
|
||||
'number',
|
||||
show_default=True,
|
||||
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
|
||||
@cli_helper.process_standard_options
|
||||
def test_plugin(
|
||||
@ -76,7 +76,7 @@ def test_plugin(
|
||||
|
||||
if not aprs_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)
|
||||
return
|
||||
else:
|
||||
@ -86,16 +86,16 @@ def test_plugin(
|
||||
|
||||
if not plugin_path:
|
||||
click.echo(ctx.get_help())
|
||||
click.echo("")
|
||||
click.echo("Failed to provide -p option to test a plugin")
|
||||
click.echo('')
|
||||
click.echo('Failed to provide -p option to test a plugin')
|
||||
ctx.exit(-1)
|
||||
return
|
||||
|
||||
if type(message) is tuple:
|
||||
message = " ".join(message)
|
||||
message = ' '.join(message)
|
||||
|
||||
if CONF.trace_enabled:
|
||||
trace.setup_tracing(["method", "api"])
|
||||
trace.setup_tracing(['method', 'api'])
|
||||
|
||||
base.APRSClient()
|
||||
|
||||
@ -105,13 +105,13 @@ def test_plugin(
|
||||
obj = pm._create_class(plugin_path, plugin.APRSDPluginBase)
|
||||
if not obj:
|
||||
click.echo(ctx.get_help())
|
||||
click.echo("")
|
||||
click.echo('')
|
||||
ctx.fail(f"Failed to create object from plugin path '{plugin_path}'")
|
||||
ctx.exit()
|
||||
|
||||
# Register the plugin they wanted tested.
|
||||
LOG.info(
|
||||
"Testing plugin {} Version {}".format(
|
||||
'Testing plugin {} Version {}'.format(
|
||||
obj.__class__,
|
||||
obj.version,
|
||||
),
|
||||
@ -126,7 +126,7 @@ def test_plugin(
|
||||
)
|
||||
LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'")
|
||||
|
||||
for x in range(number):
|
||||
for _ in range(number):
|
||||
replies = pm.run(packet)
|
||||
# Plugin might have threads, so lets stop them so we can exit.
|
||||
# obj.stop_threads()
|
||||
@ -147,17 +147,12 @@ def test_plugin(
|
||||
elif isinstance(reply, packets.Packet):
|
||||
# We have a message based object.
|
||||
LOG.info(reply)
|
||||
else:
|
||||
# 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(
|
||||
packets.MessagePacket(
|
||||
from_call=CONF.callsign,
|
||||
to_call=fromcall,
|
||||
message_text=reply,
|
||||
),
|
||||
)
|
||||
elif reply is not packets.NULL_MESSAGE:
|
||||
LOG.info(
|
||||
packets.MessagePacket(
|
||||
from_call=CONF.callsign,
|
||||
to_call=fromcall,
|
||||
message_text=reply,
|
||||
),
|
||||
)
|
||||
pm.stop()
|
||||
|
@ -15,30 +15,30 @@ from aprsd.threads.stats import StatsStore
|
||||
|
||||
# setup the global logger
|
||||
# log.basicConfig(level=log.DEBUG) # level=10
|
||||
LOG = logging.getLogger("APRSD")
|
||||
LOG = logging.getLogger('APRSD')
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@cli.command()
|
||||
@cli_helper.add_options(cli_helper.common_options)
|
||||
@click.option(
|
||||
"--host",
|
||||
'--host',
|
||||
type=str,
|
||||
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(
|
||||
"--port",
|
||||
'--port',
|
||||
type=int,
|
||||
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
|
||||
@cli_helper.process_standard_options
|
||||
def fetch_stats(ctx, host, port):
|
||||
"""Fetch stats from a APRSD admin web interface."""
|
||||
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)
|
||||
if not host:
|
||||
@ -46,114 +46,110 @@ def fetch_stats(ctx, host, port):
|
||||
if not port:
|
||||
port = CONF.admin.web_port
|
||||
|
||||
msg = f"Fetching stats from {host}:{port}"
|
||||
msg = f'Fetching stats from {host}:{port}'
|
||||
console.print(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:
|
||||
console.print(
|
||||
f"Failed to fetch stats from {host}:{port}?",
|
||||
style="bold red",
|
||||
f'Failed to fetch stats from {host}:{port}?',
|
||||
style='bold red',
|
||||
)
|
||||
return
|
||||
|
||||
stats = response.json()
|
||||
if not stats:
|
||||
console.print(
|
||||
f"Failed to fetch stats from aprsd admin ui at {host}:{port}",
|
||||
style="bold red",
|
||||
f'Failed to fetch stats from aprsd admin ui at {host}:{port}',
|
||||
style='bold red',
|
||||
)
|
||||
return
|
||||
|
||||
aprsd_title = (
|
||||
"APRSD "
|
||||
f"[bold cyan]v{stats['APRSDStats']['version']}[/] "
|
||||
f"Callsign [bold green]{stats['APRSDStats']['callsign']}[/] "
|
||||
f"Uptime [bold yellow]{stats['APRSDStats']['uptime']}[/]"
|
||||
'APRSD '
|
||||
f'[bold cyan]v{stats["APRSDStats"]["version"]}[/] '
|
||||
f'Callsign [bold green]{stats["APRSDStats"]["callsign"]}[/] '
|
||||
f'Uptime [bold yellow]{stats["APRSDStats"]["uptime"]}[/]'
|
||||
)
|
||||
|
||||
console.rule(f"Stats from {host}:{port}")
|
||||
console.print("\n\n")
|
||||
console.rule(f'Stats from {host}:{port}')
|
||||
console.print('\n\n')
|
||||
console.rule(aprsd_title)
|
||||
|
||||
# Show the connection to APRS
|
||||
# It can be a connection to an APRS-IS server or a local TNC via KISS or KISSTCP
|
||||
if "aprs-is" in stats:
|
||||
title = f"APRS-IS Connection {stats['APRSClientStats']['server_string']}"
|
||||
if 'aprs-is' in stats:
|
||||
title = f'APRS-IS Connection {stats["APRSClientStats"]["server_string"]}'
|
||||
table = Table(title=title)
|
||||
table.add_column("Key")
|
||||
table.add_column("Value")
|
||||
for key, value in stats["APRSClientStats"].items():
|
||||
table.add_column('Key')
|
||||
table.add_column('Value')
|
||||
for key, value in stats['APRSClientStats'].items():
|
||||
table.add_row(key, value)
|
||||
console.print(table)
|
||||
|
||||
threads_table = Table(title="Threads")
|
||||
threads_table.add_column("Name")
|
||||
threads_table.add_column("Alive?")
|
||||
for name, alive in stats["APRSDThreadList"].items():
|
||||
threads_table = Table(title='Threads')
|
||||
threads_table.add_column('Name')
|
||||
threads_table.add_column('Alive?')
|
||||
for name, alive in stats['APRSDThreadList'].items():
|
||||
threads_table.add_row(name, str(alive))
|
||||
|
||||
console.print(threads_table)
|
||||
|
||||
packet_totals = Table(title="Packet Totals")
|
||||
packet_totals.add_column("Key")
|
||||
packet_totals.add_column("Value")
|
||||
packet_totals.add_row("Total Received", str(stats["PacketList"]["rx"]))
|
||||
packet_totals.add_row("Total Sent", str(stats["PacketList"]["tx"]))
|
||||
packet_totals = Table(title='Packet Totals')
|
||||
packet_totals.add_column('Key')
|
||||
packet_totals.add_column('Value')
|
||||
packet_totals.add_row('Total Received', str(stats['PacketList']['rx']))
|
||||
packet_totals.add_row('Total Sent', str(stats['PacketList']['tx']))
|
||||
console.print(packet_totals)
|
||||
|
||||
# Show each of the packet types
|
||||
packets_table = Table(title="Packets By Type")
|
||||
packets_table.add_column("Packet Type")
|
||||
packets_table.add_column("TX")
|
||||
packets_table.add_column("RX")
|
||||
for key, value in stats["PacketList"]["packets"].items():
|
||||
packets_table.add_row(key, str(value["tx"]), str(value["rx"]))
|
||||
packets_table = Table(title='Packets By Type')
|
||||
packets_table.add_column('Packet Type')
|
||||
packets_table.add_column('TX')
|
||||
packets_table.add_column('RX')
|
||||
for key, value in stats['PacketList']['packets'].items():
|
||||
packets_table.add_row(key, str(value['tx']), str(value['rx']))
|
||||
|
||||
console.print(packets_table)
|
||||
|
||||
if "plugins" in stats:
|
||||
count = len(stats["PluginManager"])
|
||||
plugins_table = Table(title=f"Plugins ({count})")
|
||||
plugins_table.add_column("Plugin")
|
||||
plugins_table.add_column("Enabled")
|
||||
plugins_table.add_column("Version")
|
||||
plugins_table.add_column("TX")
|
||||
plugins_table.add_column("RX")
|
||||
plugins = stats["PluginManager"]
|
||||
for key, value in plugins.items():
|
||||
if 'plugins' in stats:
|
||||
count = len(stats['PluginManager'])
|
||||
plugins_table = Table(title=f'Plugins ({count})')
|
||||
plugins_table.add_column('Plugin')
|
||||
plugins_table.add_column('Enabled')
|
||||
plugins_table.add_column('Version')
|
||||
plugins_table.add_column('TX')
|
||||
plugins_table.add_column('RX')
|
||||
plugins = stats['PluginManager']
|
||||
for key, _ in plugins.items():
|
||||
plugins_table.add_row(
|
||||
key,
|
||||
str(plugins[key]["enabled"]),
|
||||
plugins[key]["version"],
|
||||
str(plugins[key]["tx"]),
|
||||
str(plugins[key]["rx"]),
|
||||
str(plugins[key]['enabled']),
|
||||
plugins[key]['version'],
|
||||
str(plugins[key]['tx']),
|
||||
str(plugins[key]['rx']),
|
||||
)
|
||||
|
||||
console.print(plugins_table)
|
||||
|
||||
seen_list = stats.get("SeenList")
|
||||
|
||||
if seen_list:
|
||||
if seen_list := stats.get('SeenList'):
|
||||
count = len(seen_list)
|
||||
seen_table = Table(title=f"Seen List ({count})")
|
||||
seen_table.add_column("Callsign")
|
||||
seen_table.add_column("Message Count")
|
||||
seen_table.add_column("Last Heard")
|
||||
seen_table = Table(title=f'Seen List ({count})')
|
||||
seen_table.add_column('Callsign')
|
||||
seen_table.add_column('Message Count')
|
||||
seen_table.add_column('Last Heard')
|
||||
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)
|
||||
|
||||
watch_list = stats.get("WatchList")
|
||||
|
||||
if watch_list:
|
||||
if watch_list := stats.get('WatchList'):
|
||||
count = len(watch_list)
|
||||
watch_table = Table(title=f"Watch List ({count})")
|
||||
watch_table.add_column("Callsign")
|
||||
watch_table.add_column("Last Heard")
|
||||
watch_table = Table(title=f'Watch List ({count})')
|
||||
watch_table.add_column('Callsign')
|
||||
watch_table.add_column('Last Heard')
|
||||
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)
|
||||
|
||||
@ -161,27 +157,27 @@ def fetch_stats(ctx, host, port):
|
||||
@cli.command()
|
||||
@cli_helper.add_options(cli_helper.common_options)
|
||||
@click.option(
|
||||
"--raw",
|
||||
'--raw',
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Dump raw stats instead of formatted output.",
|
||||
help='Dump raw stats instead of formatted output.',
|
||||
)
|
||||
@click.option(
|
||||
"--show-section",
|
||||
default=["All"],
|
||||
help="Show specific sections of the stats. "
|
||||
" Choices: All, APRSDStats, APRSDThreadList, APRSClientStats,"
|
||||
" PacketList, SeenList, WatchList",
|
||||
'--show-section',
|
||||
default=['All'],
|
||||
help='Show specific sections of the stats. '
|
||||
' Choices: All, APRSDStats, APRSDThreadList, APRSClientStats,'
|
||||
' PacketList, SeenList, WatchList',
|
||||
multiple=True,
|
||||
type=click.Choice(
|
||||
[
|
||||
"All",
|
||||
"APRSDStats",
|
||||
"APRSDThreadList",
|
||||
"APRSClientStats",
|
||||
"PacketList",
|
||||
"SeenList",
|
||||
"WatchList",
|
||||
'All',
|
||||
'APRSDStats',
|
||||
'APRSDThreadList',
|
||||
'APRSClientStats',
|
||||
'PacketList',
|
||||
'SeenList',
|
||||
'WatchList',
|
||||
],
|
||||
case_sensitive=False,
|
||||
),
|
||||
@ -191,122 +187,122 @@ def fetch_stats(ctx, host, port):
|
||||
def dump_stats(ctx, raw, show_section):
|
||||
"""Dump the current stats from the running APRSD instance."""
|
||||
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.load()
|
||||
stats = ss.data
|
||||
if raw:
|
||||
if "All" in show_section:
|
||||
if 'All' in show_section:
|
||||
console.print(stats)
|
||||
return
|
||||
else:
|
||||
for section in show_section:
|
||||
console.print(f"Dumping {section} section:")
|
||||
console.print(f'Dumping {section} section:')
|
||||
console.print(stats[section])
|
||||
return
|
||||
|
||||
t = Table(title="APRSD Stats")
|
||||
t.add_column("Key")
|
||||
t.add_column("Value")
|
||||
for key, value in stats["APRSDStats"].items():
|
||||
t = Table(title='APRSD Stats')
|
||||
t.add_column('Key')
|
||||
t.add_column('Value')
|
||||
for key, value in stats['APRSDStats'].items():
|
||||
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)
|
||||
|
||||
# Show the thread list
|
||||
t = Table(title="Thread List")
|
||||
t.add_column("Name")
|
||||
t.add_column("Class")
|
||||
t.add_column("Alive?")
|
||||
t.add_column("Loop Count")
|
||||
t.add_column("Age")
|
||||
for name, value in stats["APRSDThreadList"].items():
|
||||
t = Table(title='Thread List')
|
||||
t.add_column('Name')
|
||||
t.add_column('Class')
|
||||
t.add_column('Alive?')
|
||||
t.add_column('Loop Count')
|
||||
t.add_column('Age')
|
||||
for name, value in stats['APRSDThreadList'].items():
|
||||
t.add_row(
|
||||
name,
|
||||
value["class"],
|
||||
str(value["alive"]),
|
||||
str(value["loop_count"]),
|
||||
str(value["age"]),
|
||||
value['class'],
|
||||
str(value['alive']),
|
||||
str(value['loop_count']),
|
||||
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)
|
||||
|
||||
# Show the plugins
|
||||
t = Table(title="Plugin List")
|
||||
t.add_column("Name")
|
||||
t.add_column("Enabled")
|
||||
t.add_column("Version")
|
||||
t.add_column("TX")
|
||||
t.add_column("RX")
|
||||
for name, value in stats["PluginManager"].items():
|
||||
t = Table(title='Plugin List')
|
||||
t.add_column('Name')
|
||||
t.add_column('Enabled')
|
||||
t.add_column('Version')
|
||||
t.add_column('TX')
|
||||
t.add_column('RX')
|
||||
for name, value in stats['PluginManager'].items():
|
||||
t.add_row(
|
||||
name,
|
||||
str(value["enabled"]),
|
||||
value["version"],
|
||||
str(value["tx"]),
|
||||
str(value["rx"]),
|
||||
str(value['enabled']),
|
||||
value['version'],
|
||||
str(value['tx']),
|
||||
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)
|
||||
|
||||
# Now show the client stats
|
||||
t = Table(title="Client Stats")
|
||||
t.add_column("Key")
|
||||
t.add_column("Value")
|
||||
for key, value in stats["APRSClientStats"].items():
|
||||
t = Table(title='Client Stats')
|
||||
t.add_column('Key')
|
||||
t.add_column('Value')
|
||||
for key, value in stats['APRSClientStats'].items():
|
||||
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)
|
||||
|
||||
# now show the packet list
|
||||
packet_list = stats.get("PacketList")
|
||||
t = Table(title="Packet List")
|
||||
t.add_column("Key")
|
||||
t.add_column("Value")
|
||||
t.add_row("Total Received", str(packet_list["rx"]))
|
||||
t.add_row("Total Sent", str(packet_list["tx"]))
|
||||
packet_list = stats.get('PacketList')
|
||||
t = Table(title='Packet List')
|
||||
t.add_column('Key')
|
||||
t.add_column('Value')
|
||||
t.add_row('Total Received', str(packet_list['rx']))
|
||||
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)
|
||||
|
||||
# now show the seen list
|
||||
seen_list = stats.get("SeenList")
|
||||
seen_list = stats.get('SeenList')
|
||||
sorted_seen_list = sorted(
|
||||
seen_list.items(),
|
||||
)
|
||||
t = Table(title="Seen List")
|
||||
t.add_column("Callsign")
|
||||
t.add_column("Message Count")
|
||||
t.add_column("Last Heard")
|
||||
t = Table(title='Seen List')
|
||||
t.add_column('Callsign')
|
||||
t.add_column('Message Count')
|
||||
t.add_column('Last Heard')
|
||||
for key, value in sorted_seen_list:
|
||||
t.add_row(
|
||||
key,
|
||||
str(value["count"]),
|
||||
str(value["last"]),
|
||||
str(value['count']),
|
||||
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)
|
||||
|
||||
# now show the watch list
|
||||
watch_list = stats.get("WatchList")
|
||||
watch_list = stats.get('WatchList')
|
||||
sorted_watch_list = sorted(
|
||||
watch_list.items(),
|
||||
)
|
||||
t = Table(title="Watch List")
|
||||
t.add_column("Callsign")
|
||||
t.add_column("Last Heard")
|
||||
t = Table(title='Watch List')
|
||||
t.add_column('Callsign')
|
||||
t.add_column('Last Heard')
|
||||
for key, value in sorted_watch_list:
|
||||
t.add_row(
|
||||
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)
|
||||
|
@ -13,9 +13,9 @@ from oslo_config import cfg
|
||||
from rich.console import Console
|
||||
|
||||
import aprsd
|
||||
from aprsd import (
|
||||
from aprsd import ( # noqa: F401
|
||||
cli_helper,
|
||||
conf, # noqa
|
||||
conf,
|
||||
)
|
||||
|
||||
# local imports here
|
||||
|
@ -4,14 +4,11 @@ import inspect
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
import re
|
||||
import sys
|
||||
from traceback import print_tb
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import click
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
@ -22,12 +19,12 @@ from aprsd import plugin as aprsd_plugin
|
||||
from aprsd.main import cli
|
||||
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
||||
|
||||
LOG = logging.getLogger("APRSD")
|
||||
PYPI_URL = "https://pypi.org/search/"
|
||||
LOG = logging.getLogger('APRSD')
|
||||
PYPI_URL = 'https://pypi.org/search/'
|
||||
|
||||
|
||||
def onerror(name):
|
||||
print(f"Error importing module {name}")
|
||||
print(f'Error importing module {name}')
|
||||
type, value, traceback = sys.exc_info()
|
||||
print_tb(traceback)
|
||||
|
||||
@ -43,19 +40,19 @@ def is_plugin(obj):
|
||||
def plugin_type(obj):
|
||||
for c in inspect.getmro(obj):
|
||||
if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
|
||||
return "RegexCommand"
|
||||
return 'RegexCommand'
|
||||
if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
|
||||
return "WatchList"
|
||||
return 'WatchList'
|
||||
if issubclass(c, aprsd_plugin.APRSDPluginBase):
|
||||
return "APRSDPluginBase"
|
||||
return 'APRSDPluginBase'
|
||||
|
||||
return "Unknown"
|
||||
return 'Unknown'
|
||||
|
||||
|
||||
def walk_package(package):
|
||||
return pkgutil.walk_packages(
|
||||
package.__path__,
|
||||
package.__name__ + ".",
|
||||
package.__name__ + '.',
|
||||
onerror=onerror,
|
||||
)
|
||||
|
||||
@ -65,23 +62,23 @@ def get_module_info(package_name, module_name, module_path):
|
||||
return None
|
||||
|
||||
dir_path = os.path.realpath(module_path)
|
||||
pattern = "*.py"
|
||||
pattern = '*.py'
|
||||
|
||||
obj_list = []
|
||||
|
||||
for path, _subdirs, files in os.walk(dir_path):
|
||||
for name in files:
|
||||
if fnmatch.fnmatch(name, pattern):
|
||||
module = smuggle(f"{path}/{name}")
|
||||
module = smuggle(f'{path}/{name}')
|
||||
for mem_name, obj in inspect.getmembers(module):
|
||||
if inspect.isclass(obj) and is_plugin(obj):
|
||||
obj_list.append(
|
||||
{
|
||||
"package": package_name,
|
||||
"name": mem_name,
|
||||
"obj": obj,
|
||||
"version": obj.version,
|
||||
"path": f"{'.'.join([module_name, obj.__name__])}",
|
||||
'package': package_name,
|
||||
'name': mem_name,
|
||||
'obj': obj,
|
||||
'version': obj.version,
|
||||
'path': f'{".".join([module_name, obj.__name__])}',
|
||||
},
|
||||
)
|
||||
|
||||
@ -92,20 +89,18 @@ def _get_installed_aprsd_items():
|
||||
# installed plugins
|
||||
plugins = {}
|
||||
extensions = {}
|
||||
for finder, name, ispkg in pkgutil.iter_modules():
|
||||
if name.startswith("aprsd_"):
|
||||
print(f"Found aprsd_ module: {name}")
|
||||
if ispkg:
|
||||
module = importlib.import_module(name)
|
||||
pkgs = walk_package(module)
|
||||
for pkg in pkgs:
|
||||
pkg_info = get_module_info(
|
||||
module.__name__, pkg.name, module.__path__[0]
|
||||
)
|
||||
if "plugin" in name:
|
||||
plugins[name] = pkg_info
|
||||
elif "extension" in name:
|
||||
extensions[name] = pkg_info
|
||||
for _finder, name, ispkg in pkgutil.iter_modules():
|
||||
if ispkg and name.startswith('aprsd_'):
|
||||
module = importlib.import_module(name)
|
||||
pkgs = walk_package(module)
|
||||
for pkg in pkgs:
|
||||
pkg_info = get_module_info(
|
||||
module.__name__, pkg.name, module.__path__[0]
|
||||
)
|
||||
if 'plugin' in name:
|
||||
plugins[name] = pkg_info
|
||||
elif 'extension' in name:
|
||||
extensions[name] = pkg_info
|
||||
return plugins, extensions
|
||||
|
||||
|
||||
@ -131,154 +126,141 @@ def show_built_in_plugins(console):
|
||||
cls = entry[1]
|
||||
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
|
||||
info = {
|
||||
"name": cls.__qualname__,
|
||||
"path": f"{cls.__module__}.{cls.__qualname__}",
|
||||
"version": cls.version,
|
||||
"docstring": cls.__doc__,
|
||||
"short_desc": cls.short_description,
|
||||
'name': cls.__qualname__,
|
||||
'path': f'{cls.__module__}.{cls.__qualname__}',
|
||||
'version': cls.version,
|
||||
'docstring': cls.__doc__,
|
||||
'short_desc': cls.short_description,
|
||||
}
|
||||
|
||||
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
|
||||
info["command_regex"] = cls.command_regex
|
||||
info["type"] = "RegexCommand"
|
||||
info['command_regex'] = cls.command_regex
|
||||
info['type'] = 'RegexCommand'
|
||||
|
||||
if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
|
||||
info["type"] = "WatchList"
|
||||
info['type'] = 'WatchList'
|
||||
|
||||
plugins.append(info)
|
||||
|
||||
plugins = sorted(plugins, key=lambda i: i["name"])
|
||||
plugins = sorted(plugins, key=lambda i: i['name'])
|
||||
|
||||
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("Info", style="bold yellow")
|
||||
table.add_column("Type", style="bold green")
|
||||
table.add_column("Plugin Path", style="bold blue")
|
||||
table.add_column('Plugin Name', style='cyan', no_wrap=True)
|
||||
table.add_column('Info', style='bold yellow')
|
||||
table.add_column('Type', style='bold green')
|
||||
table.add_column('Plugin Path', style='bold blue')
|
||||
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)
|
||||
|
||||
|
||||
def _get_pypi_packages():
|
||||
query = "aprsd"
|
||||
snippets = []
|
||||
s = requests.Session()
|
||||
for page in range(1, 3):
|
||||
params = {"q": query, "page": page}
|
||||
r = s.get(PYPI_URL, params=params)
|
||||
soup = BeautifulSoup(r.text, "html.parser")
|
||||
snippets += soup.select('a[class*="snippet"]')
|
||||
if not hasattr(s, "start_url"):
|
||||
s.start_url = r.url.rsplit("&page", maxsplit=1).pop(0)
|
||||
if simple_r := requests.get(
|
||||
'https://pypi.org/simple',
|
||||
headers={'Accept': 'application/vnd.pypi.simple.v1+json'},
|
||||
):
|
||||
simple_response = simple_r.json()
|
||||
else:
|
||||
simple_response = {}
|
||||
|
||||
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):
|
||||
snippets = _get_pypi_packages()
|
||||
packages = _get_pypi_packages()
|
||||
|
||||
title = Text.assemble(
|
||||
("Pypi.org APRSD Installable Plugin Packages\n\n", "bold magenta"),
|
||||
("Install any of the following plugins with\n", "bold yellow"),
|
||||
("'pip install ", "bold white"),
|
||||
("<Plugin Package Name>'", "cyan"),
|
||||
('Pypi.org APRSD Installable Plugin Packages\n\n', 'bold magenta'),
|
||||
('Install any of the following plugins with\n', 'bold yellow'),
|
||||
("'pip install ", 'bold white'),
|
||||
("<Plugin Package Name>'", 'cyan'),
|
||||
)
|
||||
|
||||
table = Table(title=title)
|
||||
table.add_column("Plugin Package Name", style="cyan", no_wrap=True)
|
||||
table.add_column("Description", style="yellow")
|
||||
table.add_column("Version", style="yellow", justify="center")
|
||||
table.add_column("Released", style="bold green", justify="center")
|
||||
table.add_column("Installed?", style="red", justify="center")
|
||||
for snippet in snippets:
|
||||
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:"
|
||||
table.add_column('Plugin Package Name', style='cyan', no_wrap=True)
|
||||
table.add_column('Description', style='yellow')
|
||||
table.add_column('Version', style='yellow', justify='center')
|
||||
table.add_column('Released', style='bold green', justify='center')
|
||||
table.add_column('Installed?', style='red', justify='center')
|
||||
emoji = ':open_file_folder:'
|
||||
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 or "-plugin" not in package:
|
||||
if 'aprsd-' not in package_name or '-plugin' not in package_name:
|
||||
continue
|
||||
|
||||
under = package.replace("-", "_")
|
||||
if under in installed_plugins:
|
||||
installed = "Yes"
|
||||
else:
|
||||
installed = "No"
|
||||
|
||||
under = package_name.replace('-', '_')
|
||||
installed = 'Yes' if under in installed_plugins else 'No'
|
||||
table.add_row(
|
||||
f"[link={link}]{emoji}[/link] {package}",
|
||||
f'[link={link}]{emoji}[/link] {package_name}',
|
||||
description,
|
||||
version,
|
||||
created,
|
||||
installed,
|
||||
)
|
||||
|
||||
console.print("\n")
|
||||
console.print('\n')
|
||||
console.print(table)
|
||||
|
||||
|
||||
def show_pypi_extensions(installed_extensions, console):
|
||||
snippets = _get_pypi_packages()
|
||||
packages = _get_pypi_packages()
|
||||
|
||||
title = Text.assemble(
|
||||
("Pypi.org APRSD Installable Extension Packages\n\n", "bold magenta"),
|
||||
("Install any of the following extensions by running\n", "bold yellow"),
|
||||
("'pip install ", "bold white"),
|
||||
("<Plugin Package Name>'", "cyan"),
|
||||
('Pypi.org APRSD Installable Extension Packages\n\n', 'bold magenta'),
|
||||
('Install any of the following extensions by running\n', 'bold yellow'),
|
||||
("'pip install ", 'bold white'),
|
||||
("<Plugin Package Name>'", 'cyan'),
|
||||
)
|
||||
table = Table(title=title)
|
||||
table.add_column("Extension Package Name", style="cyan", no_wrap=True)
|
||||
table.add_column("Description", style="yellow")
|
||||
table.add_column("Version", style="yellow", justify="center")
|
||||
table.add_column("Released", style="bold green", justify="center")
|
||||
table.add_column("Installed?", style="red", justify="center")
|
||||
for snippet in snippets:
|
||||
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:"
|
||||
table.add_column('Extension Package Name', style='cyan', no_wrap=True)
|
||||
table.add_column('Description', style='yellow')
|
||||
table.add_column('Version', style='yellow', justify='center')
|
||||
table.add_column('Released', style='bold green', justify='center')
|
||||
table.add_column('Installed?', style='red', justify='center')
|
||||
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
|
||||
|
||||
under = package.replace("-", "_")
|
||||
if under in installed_extensions:
|
||||
installed = "Yes"
|
||||
else:
|
||||
installed = "No"
|
||||
|
||||
under = package_name.replace('-', '_')
|
||||
installed = 'Yes' if under in installed_extensions else 'No'
|
||||
table.add_row(
|
||||
f"[link={link}]{emoji}[/link] {package}",
|
||||
f'[link={link}]{emoji}[/link] {package_name}',
|
||||
description,
|
||||
version,
|
||||
created,
|
||||
installed,
|
||||
)
|
||||
|
||||
console.print("\n")
|
||||
console.print('\n')
|
||||
console.print(table)
|
||||
|
||||
|
||||
@ -287,24 +269,24 @@ def show_installed_plugins(installed_plugins, console):
|
||||
return
|
||||
|
||||
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("Plugin Name", style="cyan", no_wrap=True)
|
||||
table.add_column("Version", style="yellow", justify="center")
|
||||
table.add_column("Type", style="bold green")
|
||||
table.add_column("Plugin Path", style="bold blue")
|
||||
table.add_column('Package Name', style=' bold white', no_wrap=True)
|
||||
table.add_column('Plugin Name', style='cyan', no_wrap=True)
|
||||
table.add_column('Version', style='yellow', justify='center')
|
||||
table.add_column('Type', style='bold green')
|
||||
table.add_column('Plugin Path', style='bold blue')
|
||||
for name in installed_plugins:
|
||||
for plugin in installed_plugins[name]:
|
||||
table.add_row(
|
||||
name.replace("_", "-"),
|
||||
plugin["name"],
|
||||
plugin["version"],
|
||||
plugin_type(plugin["obj"]),
|
||||
plugin["path"],
|
||||
name.replace('_', '-'),
|
||||
plugin['name'],
|
||||
plugin['version'],
|
||||
plugin_type(plugin['obj']),
|
||||
plugin['path'],
|
||||
)
|
||||
|
||||
console.print("\n")
|
||||
console.print('\n')
|
||||
console.print(table)
|
||||
|
||||
|
||||
@ -316,14 +298,14 @@ def list_plugins(ctx):
|
||||
"""List the built in plugins available to APRSD."""
|
||||
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)
|
||||
|
||||
status.update("Fetching pypi.org plugins")
|
||||
status.update('Fetching pypi.org plugins')
|
||||
installed_plugins = get_installed_plugins()
|
||||
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)
|
||||
|
||||
|
||||
@ -335,7 +317,9 @@ def list_extensions(ctx):
|
||||
"""List the built in plugins available to APRSD."""
|
||||
console = Console()
|
||||
|
||||
with console.status("Show APRSD Extensions") as status:
|
||||
status.update("Fetching pypi.org APRSD Extensions")
|
||||
with console.status('Show APRSD Extensions') as status:
|
||||
status.update('Fetching pypi.org APRSD Extensions')
|
||||
|
||||
status.update('Looking for installed APRSD Extensions')
|
||||
installed_extensions = get_installed_extensions()
|
||||
show_pypi_extensions(installed_extensions, console)
|
||||
|
@ -113,6 +113,7 @@ class ListenStatsThread(APRSDThread):
|
||||
stats_json = collector.Collector().collect()
|
||||
stats = stats_json["PacketList"]
|
||||
total_rx = stats["rx"]
|
||||
packet_count = len(stats["packets"])
|
||||
rx_delta = total_rx - self._last_total_rx
|
||||
rate = rx_delta / 10
|
||||
|
||||
@ -120,7 +121,8 @@ class ListenStatsThread(APRSDThread):
|
||||
LOGU.opt(colors=True).info(
|
||||
f"<green>RX Rate: {rate} pps</green> "
|
||||
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
|
||||
|
||||
|
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
|
||||
ARG INSTALL_TYPE=pypi
|
||||
|
||||
@ -8,7 +8,7 @@ ARG BRANCH=master
|
||||
ARG BUILDX_QEMU_ENV
|
||||
|
||||
ENV APRSD_BRANCH=${BRANCH:-master}
|
||||
ENV TZ=${TZ:-US/Eastern}
|
||||
#ENV TZ=${TZ:-US/Eastern}
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
ENV APRSD_PIP_VERSION=${VERSION}
|
||||
@ -23,49 +23,59 @@ ENV PIP_DEFAULT_TIMEOUT=100 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
|
||||
RUN set -ex \
|
||||
# Create a non-root user
|
||||
&& addgroup --system --gid 1001 appgroup \
|
||||
&& useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
|
||||
&& usermod -aG sudo appuser \
|
||||
#RUN set -ex \
|
||||
# # Create a non-root user
|
||||
# && addgroup --system --gid 1001 appgroup \
|
||||
# && useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
|
||||
# && usermod -aG sudo appuser
|
||||
|
||||
RUN apk add git
|
||||
|
||||
#RUN set -ex \
|
||||
# Upgrade the package index and install security upgrades
|
||||
&& apt-get update \
|
||||
&& apt-get upgrade -y \
|
||||
&& apt-get install -y git build-essential curl libffi-dev \
|
||||
python3-dev libssl-dev libxml2-dev libxslt-dev telnet sudo fortune \
|
||||
# && apt-get update \
|
||||
# && apt-get upgrade -y \
|
||||
# && apt-get install -y git build-essential curl libffi-dev \
|
||||
# python3-dev libssl-dev libxml2-dev libxslt-dev telnet sudo fortune \
|
||||
# Install dependencies
|
||||
# Clean up
|
||||
&& apt-get autoremove -y \
|
||||
&& apt-get clean -y
|
||||
# && apt-get autoremove -y \
|
||||
# && apt-get clean -y
|
||||
|
||||
|
||||
|
||||
### Final stage
|
||||
FROM build AS install
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip3 install -U pip
|
||||
RUN mkdir /config
|
||||
RUN chown -R appuser:appgroup /app
|
||||
RUN chown -R appuser:appgroup /config
|
||||
USER appuser
|
||||
RUN uv venv
|
||||
RUN uv pip install -U pip
|
||||
|
||||
RUN if [ "$INSTALL_TYPE" = "pypi" ]; then \
|
||||
pip3 install aprsd==$APRSD_PIP_VERSION; \
|
||||
uv pip install aprsd==$APRSD_PIP_VERSION; \
|
||||
elif [ "$INSTALL_TYPE" = "github" ]; then \
|
||||
git clone -b $APRSD_BRANCH https://github.com/craigerl/aprsd; \
|
||||
cd /app/aprsd && pip install .; \
|
||||
ls -al /app/.local/lib/python3.11/site-packages/aprsd*; \
|
||||
cd /app/aprsd && uv pip install .; \
|
||||
ls -al /app/.venv/lib/python3.11/site-packages/aprsd*; \
|
||||
fi
|
||||
RUN pip install gevent uwsgi
|
||||
# RUN uv pip install gevent uwsgi
|
||||
RUN echo "PATH=\$PATH:/usr/games:/app/.local/bin" >> /app/.bashrc
|
||||
RUN which aprsd
|
||||
RUN aprsd sample-config > /config/aprsd.conf
|
||||
RUN aprsd --version
|
||||
#RUN which aprsd
|
||||
RUN uv run aprsd sample-config > /config/aprsd.conf
|
||||
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/admin.sh /app
|
||||
ADD bin/listen.sh /app
|
||||
ADD bin/healthcheck.sh /app
|
||||
|
||||
|
||||
FROM install AS final
|
||||
RUN ls -al /app
|
||||
# For the web admin interface
|
||||
EXPOSE 8001
|
||||
|
||||
@ -74,7 +84,7 @@ ENTRYPOINT ["/app/setup.sh"]
|
||||
CMD ["server"]
|
||||
|
||||
# Set the user to run the application
|
||||
USER appuser
|
||||
# USER appuser
|
||||
|
||||
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
|
||||
set -x
|
||||
|
||||
source /app/.venv/bin/activate
|
||||
|
||||
if [ ! -z "${APRSD_PLUGINS}" ]; then
|
||||
OLDIFS=$IFS
|
||||
IFS=','
|
||||
@ -9,7 +11,7 @@ if [ ! -z "${APRSD_PLUGINS}" ]; then
|
||||
IFS=$OLDIFS
|
||||
# call your procedure/other scripts here below
|
||||
echo "Installing '$plugin'"
|
||||
pip3 install --user $plugin
|
||||
uv pip install $plugin
|
||||
done
|
||||
fi
|
||||
if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
||||
@ -20,7 +22,7 @@ if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
||||
IFS=$OLDIFS
|
||||
# call your procedure/other scripts here below
|
||||
echo "Installing '$extension'"
|
||||
pip3 install --user $extension
|
||||
uv pip install $extension
|
||||
done
|
||||
fi
|
||||
|
||||
@ -40,5 +42,7 @@ fi
|
||||
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.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}
|
||||
#
|
||||
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
|
||||
set -x
|
||||
|
||||
source /app/.venv/bin/activate
|
||||
|
||||
if [ ! -z "${APRSD_PLUGINS}" ]; then
|
||||
OLDIFS=$IFS
|
||||
IFS=','
|
||||
@ -9,7 +11,7 @@ if [ ! -z "${APRSD_PLUGINS}" ]; then
|
||||
IFS=$OLDIFS
|
||||
# call your procedure/other scripts here below
|
||||
echo "Installing '$plugin'"
|
||||
pip3 install --user $plugin
|
||||
uv pip install --user $plugin
|
||||
done
|
||||
fi
|
||||
|
||||
@ -21,7 +23,7 @@ if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
||||
IFS=$OLDIFS
|
||||
# call your procedure/other scripts here below
|
||||
echo "Installing '$extension'"
|
||||
pip3 install --user $extension
|
||||
uv pip install --user $extension
|
||||
done
|
||||
fi
|
||||
|
||||
@ -35,7 +37,7 @@ echo "Log level is set to ${LOG_LEVEL}";
|
||||
APRSD_CONFIG="/config/aprsd.conf"
|
||||
if [ ! -e "$APRSD_CONFIG" ]; then
|
||||
echo "'$APRSD_CONFIG' File does not exist. Creating."
|
||||
aprsd sample-config > $APRSD_CONFIG
|
||||
uv run aprsd sample-config > $APRSD_CONFIG
|
||||
fi
|
||||
|
||||
export COLUMNS=200
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
|
||||
source /app/.venv/bin/activate
|
||||
|
||||
# The default command
|
||||
# Override the command in docker-compose.yml to change
|
||||
# what command you want to run in the container
|
||||
@ -18,7 +20,7 @@ if [ ! -z "${APRSD_PLUGINS}" ]; then
|
||||
IFS=$OLDIFS
|
||||
# call your procedure/other scripts here below
|
||||
echo "Installing '$plugin'"
|
||||
pip3 install --user $plugin
|
||||
uv pip install $plugin
|
||||
done
|
||||
fi
|
||||
|
||||
@ -30,7 +32,7 @@ if [ ! -z "${APRSD_EXTENSIONS}" ]; then
|
||||
IFS=$OLDIFS
|
||||
# call your procedure/other scripts here below
|
||||
echo "Installing '$extension'"
|
||||
pip3 install --user $extension
|
||||
uv pip install $extension
|
||||
done
|
||||
fi
|
||||
|
||||
|
@ -68,14 +68,6 @@ aprsd.cmds.server module
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.cmds.webchat module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: aprsd.cmds.webchat
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -44,14 +44,6 @@ aprsd.conf.plugin\_common module
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.conf.plugin\_email module
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: aprsd.conf.plugin_email
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -4,14 +4,6 @@ aprsd.plugins package
|
||||
Submodules
|
||||
----------
|
||||
|
||||
aprsd.plugins.email module
|
||||
--------------------------
|
||||
|
||||
.. automodule:: aprsd.plugins.email
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.plugins.fortune module
|
||||
----------------------------
|
||||
|
||||
@ -20,14 +12,6 @@ aprsd.plugins.fortune module
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.plugins.location module
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: aprsd.plugins.location
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.plugins.notify module
|
||||
---------------------------
|
||||
|
||||
|
@ -16,7 +16,6 @@ Subpackages
|
||||
aprsd.stats
|
||||
aprsd.threads
|
||||
aprsd.utils
|
||||
aprsd.web
|
||||
|
||||
Submodules
|
||||
----------
|
||||
@ -45,14 +44,6 @@ aprsd.main module
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.messaging module
|
||||
----------------------
|
||||
|
||||
.. automodule:: aprsd.messaging
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.plugin module
|
||||
-------------------
|
||||
|
||||
@ -69,14 +60,6 @@ aprsd.plugin\_utils module
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.wsgi module
|
||||
-----------------
|
||||
|
||||
.. automodule:: aprsd.wsgi
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -12,18 +12,10 @@ aprsd.threads.aprsd module
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.threads.keep\_alive module
|
||||
--------------------------------
|
||||
aprsd.threads.keepalive module
|
||||
------------------------------
|
||||
|
||||
.. automodule:: aprsd.threads.keep_alive
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.threads.log\_monitor module
|
||||
---------------------------------
|
||||
|
||||
.. automodule:: aprsd.threads.log_monitor
|
||||
.. automodule:: aprsd.threads.keepalive
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
@ -28,6 +28,14 @@ aprsd.utils.json module
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
aprsd.utils.keepalive\_collector module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: aprsd.utils.keepalive_collector
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
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
|
||||
|
||||
from aprsd import plugin
|
||||
|
||||
from aprsd import packets, plugin
|
||||
|
||||
LOG = logging.getLogger("APRSD")
|
||||
|
||||
@ -14,7 +13,7 @@ class HelloPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||
command_regex = "^[hH]"
|
||||
command_name = "hello"
|
||||
|
||||
def process(self, packet):
|
||||
def process(self, packet: packets.MessagePacket):
|
||||
LOG.info("HelloPlugin")
|
||||
reply = f"Hello '{packet.from_call}'"
|
||||
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
|
||||
# and refuse to install the project if the version does not match. See
|
||||
# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
dynamic = ["version", "dependencies", "optional-dependencies"]
|
||||
|
||||
@ -173,3 +173,20 @@ skip_gitignore = true
|
||||
branch = true
|
||||
|
||||
[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 is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --annotation-style=line requirements-dev.in
|
||||
#
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --resolver backtracking --annotation-style=line requirements-dev.in -o requirements-dev.txt
|
||||
alabaster==1.0.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
|
||||
certifi==2024.12.14 # via requests
|
||||
cfgv==3.4.0 # via pre-commit
|
||||
chardet==5.2.0 # via tox
|
||||
charset-normalizer==3.4.0 # via requests
|
||||
click==8.1.7 # via pip-tools
|
||||
charset-normalizer==3.4.1 # via requests
|
||||
click==8.1.8 # via pip-tools
|
||||
colorama==0.4.6 # via tox
|
||||
distlib==0.3.9 # via virtualenv
|
||||
docutils==0.21.2 # via m2r, sphinx
|
||||
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
|
||||
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
|
||||
markupsafe==3.0.2 # via jinja2
|
||||
mistune==0.8.4 # via m2r
|
||||
nodeenv==1.9.1 # via pre-commit
|
||||
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
|
||||
platformdirs==4.3.6 # via tox, virtualenv
|
||||
pluggy==1.5.0 # via tox
|
||||
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-hooks==1.2.0 # via build, pip-tools
|
||||
pyyaml==6.0.2 # via pre-commit
|
||||
requests==2.32.3 # via sphinx
|
||||
setuptools==75.7.0 # via pip-tools
|
||||
snowballstemmer==2.2.0 # via sphinx
|
||||
sphinx==8.1.3 # via -r requirements-dev.in
|
||||
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
|
||||
tox==4.23.2 # via -r requirements-dev.in
|
||||
typing-extensions==4.12.2 # via tox
|
||||
urllib3==2.2.3 # via requests
|
||||
virtualenv==20.28.0 # via pre-commit, tox
|
||||
wheel==0.45.1 # via -r requirements-dev.in, pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
||||
# setuptools
|
||||
urllib3==2.3.0 # via requests
|
||||
virtualenv==20.28.1 # via pre-commit, tox
|
||||
wheel==0.45.1 # via pip-tools, -r requirements-dev.in
|
||||
|
@ -1,6 +1,4 @@
|
||||
aprslib>=0.7.0
|
||||
# For the list-plugins pypi.org search scraping
|
||||
beautifulsoup4
|
||||
click
|
||||
dataclasses-json
|
||||
haversine
|
||||
|
@ -1,17 +1,12 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --annotation-style=line requirements.in
|
||||
#
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --resolver backtracking --annotation-style=line requirements.in -o requirements.txt
|
||||
aprslib==0.7.2 # via -r requirements.in
|
||||
attrs==24.3.0 # via ax253, kiss3, rush
|
||||
ax253==0.1.5.post1 # via kiss3
|
||||
beautifulsoup4==4.12.3 # via -r requirements.in
|
||||
bitarray==3.0.0 # via ax253, kiss3
|
||||
certifi==2024.12.14 # via requests
|
||||
charset-normalizer==3.4.0 # via requests
|
||||
click==8.1.7 # via -r requirements.in
|
||||
charset-normalizer==3.4.1 # via requests
|
||||
click==8.1.8 # via -r requirements.in
|
||||
commonmark==0.9.1 # via rich
|
||||
dataclasses-json==0.6.7 # via -r requirements.in
|
||||
debtcollector==3.0.0 # via oslo-config
|
||||
@ -20,7 +15,7 @@ idna==3.10 # via requests
|
||||
importlib-metadata==8.5.0 # via ax253, kiss3
|
||||
kiss3==8.0.0 # 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
|
||||
netaddr==1.3.0 # via oslo-config
|
||||
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
|
||||
pbr==6.1.0 # via oslo-i18n, stevedore
|
||||
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-asyncio==0.6 # via kiss3
|
||||
pytz==2024.2 # via -r requirements.in
|
||||
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
|
||||
rich==12.6.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
|
||||
thesmuggler==1.0.1 # 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
|
||||
tzlocal==5.2 # via -r requirements.in
|
||||
update-checker==0.18.0 # via -r requirements.in
|
||||
urllib3==2.2.3 # via requests
|
||||
wrapt==1.17.0 # via -r requirements.in, debtcollector
|
||||
urllib3==2.3.0 # via requests
|
||||
wrapt==1.17.0 # via debtcollector, -r requirements.in
|
||||
zipp==3.21.0 # via importlib-metadata
|
||||
|
@ -2,9 +2,9 @@ from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd import (
|
||||
from aprsd import ( # noqa: F401
|
||||
client,
|
||||
conf, # noqa: F401
|
||||
conf,
|
||||
packets,
|
||||
)
|
||||
from aprsd.plugins import notify as notify_plugin
|
||||
|
@ -3,8 +3,8 @@ from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd import (
|
||||
conf, # noqa: F401
|
||||
from aprsd import ( # noqa: F401
|
||||
conf,
|
||||
packets,
|
||||
plugins,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user