mirror of https://github.com/craigerl/aprsd.git
Merge pull request #107 from craigerl/oslo-config
Convert config to oslo_config
This commit is contained in:
commit
473f00973b
34
ChangeLog
34
ChangeLog
|
@ -1,9 +1,43 @@
|
||||||
CHANGES
|
CHANGES
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
* Decouple admin web interface from server command
|
||||||
|
* Dockerfile now produces aprsd.conf
|
||||||
|
* Fix some unit tests and loading of CONF w/o file
|
||||||
|
* Added missing conf
|
||||||
|
* Removed references to old custom config
|
||||||
|
* Convert config to oslo\_config
|
||||||
|
* Added rain formatting unit tests to WeatherPacket
|
||||||
|
* Fix Rain reporting in WeatherPacket send
|
||||||
|
* Removed Packet.send()
|
||||||
|
* Removed watchlist plugins
|
||||||
|
* Fix PluginManager.get\_plugins
|
||||||
|
* Cleaned up PluginManager
|
||||||
|
* Cleaned up PluginManager
|
||||||
|
* Update routing for weatherpacket
|
||||||
|
* Fix some WeatherPacket formatting
|
||||||
|
* Fix pep8 violation
|
||||||
|
* Add packet filtering for aprsd listen
|
||||||
|
* Added WeatherPacket encoding
|
||||||
|
* Updated webchat and listen for queue based RX
|
||||||
|
* reworked collecting and reporting stats
|
||||||
|
* Removed unused threading code
|
||||||
|
* Change RX packet processing to enqueu
|
||||||
|
* Make tracking objectstores work w/o initializing
|
||||||
|
* Cleaned up packet transmit class attributes
|
||||||
|
* Fix packets timestamp to int
|
||||||
|
* More messaging -> packets cleanup
|
||||||
|
* Cleaned out all references to messaging
|
||||||
|
* Added contructing a GPSPacket for sending
|
||||||
|
* cleanup webchat
|
||||||
|
* Reworked all packet processing
|
||||||
|
* Updated plugins and plugin interfaces for Packet
|
||||||
|
* Started using dataclasses to describe packets
|
||||||
|
|
||||||
v2.6.1
|
v2.6.1
|
||||||
------
|
------
|
||||||
|
|
||||||
|
* v2.6.1
|
||||||
* Fixed position report for webchat beacon
|
* Fixed position report for webchat beacon
|
||||||
* Try and fix broken 32bit qemu builds on 64bit system
|
* Try and fix broken 32bit qemu builds on 64bit system
|
||||||
* Add unit tests for webchat
|
* Add unit tests for webchat
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -22,7 +22,7 @@ dev: venv ## Create a python virtual environment for development of aprsd
|
||||||
|
|
||||||
run: venv ## Create a virtual environment for running aprsd commands
|
run: venv ## Create a virtual environment for running aprsd commands
|
||||||
|
|
||||||
docs: build
|
docs: dev
|
||||||
cp README.rst docs/readme.rst
|
cp README.rst docs/readme.rst
|
||||||
cp Changelog docs/changelog.rst
|
cp Changelog docs/changelog.rst
|
||||||
tox -edocs
|
tox -edocs
|
||||||
|
|
205
README.rst
205
README.rst
|
@ -52,39 +52,49 @@ Current list of built-in plugins
|
||||||
::
|
::
|
||||||
|
|
||||||
└─> aprsd list-plugins
|
└─> aprsd list-plugins
|
||||||
🐍 APRSD Built-in Plugins 🐍
|
🐍 APRSD Built-in Plugins 🐍
|
||||||
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
┃ Plugin Name ┃ Info ┃ Type ┃ Plugin Path ┃
|
┃ Plugin Name ┃ Info ┃ Type ┃ Plugin Path ┃
|
||||||
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||||
│ AVWXWeatherPlugin │ AVWX weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.AVWXWeatherPlugin │
|
│ AVWXWeatherPlugin │ AVWX weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.AVWXWeatherPlugin │
|
||||||
│ EmailPlugin │ Send and Receive email │ RegexCommand │ aprsd.plugins.email.EmailPlugin │
|
│ EmailPlugin │ Send and Receive email │ RegexCommand │ aprsd.plugins.email.EmailPlugin │
|
||||||
│ FortunePlugin │ Give me a fortune │ RegexCommand │ aprsd.plugins.fortune.FortunePlugin │
|
│ 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 │
|
│ 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 │
|
│ 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 │
|
│ OWMWeatherPlugin │ OpenWeatherMap weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.OWMWeatherPlugin │
|
||||||
│ PingPlugin │ reply with a Pong! │ RegexCommand │ aprsd.plugins.ping.PingPlugin │
|
│ 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 │
|
│ 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 │
|
│ TimeOWMPlugin │ Current time of GPS beacon's timezone. Uses OpenWeatherMap │ RegexCommand │ aprsd.plugins.time.TimeOWMPlugin │
|
||||||
│ TimeOpenCageDataPlugin │ Current time of GPS beacon timezone. Uses OpenCage │ RegexCommand │ aprsd.plugins.time.TimeOpenCageDataPlugin │
|
│ TimePlugin │ What is the current local time. │ RegexCommand │ aprsd.plugins.time.TimePlugin │
|
||||||
│ 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 │
|
||||||
│ 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 │
|
||||||
│ 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 │
|
||||||
│ VersionPlugin │ What is the APRSD Version │ RegexCommand │ aprsd.plugins.version.VersionPlugin │
|
└───────────────────┴────────────────────────────────────────────────────────────┴──────────────┴─────────────────────────────────────────┘
|
||||||
└────────────────────────┴────────────────────────────────────────────────────────────┴──────────────┴───────────────────────────────────────────┘
|
|
||||||
|
|
||||||
|
|
||||||
Pypi.org APRSD Installable Plugin Packages
|
Pypi.org APRSD Installable Plugin Packages
|
||||||
|
|
||||||
Install any of the following plugins with pip install <Plugin Package Name>
|
Install any of the following plugins with 'pip install <Plugin Package Name>'
|
||||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
||||||
┃ Plugin Package Name ┃ Description ┃ Version ┃ Released ┃ Installed? ┃
|
┃ Plugin Package Name ┃ Description ┃ Version ┃ Released ┃ Installed? ┃
|
||||||
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
|
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
|
||||||
│ 📂 aprsd-stock-plugin │ Ham Radio APRSD Plugin for fetching stock quotes │ 0.1.2 │ Nov 9, 2021 │ No │
|
│ 📂 aprsd-stock-plugin │ Ham Radio APRSD Plugin for fetching stock quotes │ 0.1.3 │ Dec 2, 2022 │ No │
|
||||||
│ 📂 aprsd-weewx-plugin │ HAM Radio APRSD that reports weather from a weewx weather station. │ 0.1.4 │ Dec 7, 2021 │ No │
|
│ 📂 aprsd-sentry-plugin │ Ham radio APRSD plugin that does.... │ 0.1.2 │ Dec 2, 2022 │ No │
|
||||||
│ 📂 aprsd-telegram-plugin │ Ham Radio APRS APRSD plugin for Telegram IM service │ 0.1.2 │ Nov 9, 2021 │ No │
|
│ 📂 aprsd-timeopencage-plugin │ APRSD plugin for fetching time based on GPS location │ 0.1.0 │ Dec 2, 2022 │ No │
|
||||||
│ 📂 aprsd-twitter-plugin │ Python APRSD plugin to send tweets │ 0.3.0 │ Dec 7, 2021 │ No │
|
│ 📂 aprsd-weewx-plugin │ HAM Radio APRSD that reports weather from a weewx weather station. │ 0.1.4 │ Dec 7, 2021 │ Yes │
|
||||||
│ 📂 aprsd-slack-plugin │ Amateur radio APRS daemon which listens for messages and responds │ 1.0.4 │ Jan 15, 2021 │ No │
|
│ 📂 aprsd-repeat-plugins │ APRSD Plugins for the REPEAT service │ 1.0.12 │ Dec 2, 2022 │ No │
|
||||||
└──────────────────────────┴────────────────────────────────────────────────────────────────────┴─────────┴──────────────┴────────────┘
|
│ 📂 aprsd-telegram-plugin │ Ham Radio APRS APRSD plugin for Telegram IM service │ 0.1.3 │ Dec 2, 2022 │ No │
|
||||||
|
│ 📂 aprsd-twitter-plugin │ Python APRSD plugin to send tweets │ 0.3.0 │ Dec 7, 2021 │ No │
|
||||||
|
│ 📂 aprsd-slack-plugin │ Amateur radio APRS daemon which listens for messages and responds │ 1.0.5 │ Dec 18, 2022 │ No │
|
||||||
|
└──────────────────────────────┴────────────────────────────────────────────────────────────────────┴─────────┴──────────────┴────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
🐍 APRSD Installed 3rd party Plugins 🐍
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ Package Name ┃ Plugin Name ┃ Version ┃ Type ┃ Plugin Path ┃
|
||||||
|
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||||
|
│ aprsd-weewx-plugin │ WeewxMQTTPlugin │ 1.0 │ RegexCommand │ aprsd_weewx_plugin.weewx.WeewxMQTTPlugin │
|
||||||
|
└────────────────────┴─────────────────┴─────────┴──────────────┴──────────────────────────────────────────┘
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
=============
|
=============
|
||||||
|
@ -99,10 +109,10 @@ Example usage
|
||||||
``aprsd -h``
|
``aprsd -h``
|
||||||
|
|
||||||
Help
|
Help
|
||||||
----
|
====
|
||||||
::
|
::
|
||||||
|
|
||||||
└─[$] > aprsd -h
|
└─> aprsd -h
|
||||||
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
@ -116,18 +126,19 @@ Help
|
||||||
healthcheck Check the health of the running aprsd server.
|
healthcheck Check the health of the running aprsd server.
|
||||||
list-plugins List the built in plugins available to APRSD.
|
list-plugins List the built in plugins available to APRSD.
|
||||||
listen Listen to packets on the APRS-IS Network based on FILTER.
|
listen Listen to packets on the APRS-IS Network based on FILTER.
|
||||||
sample-config This dumps the config to stdout.
|
sample-config Generate a sample Config file from aprsd and all...
|
||||||
send-message Send a message to a callsign via APRS_IS.
|
send-message Send a message to a callsign via APRS_IS.
|
||||||
server Start the aprsd server gateway process.
|
server Start the aprsd server gateway process.
|
||||||
version Show the APRSD version.
|
version Show the APRSD version.
|
||||||
|
webchat Web based HAM Radio chat program!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Commands
|
Commands
|
||||||
--------
|
========
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
^^^^^^^^^^^^^
|
=============
|
||||||
This command outputs a sample config yml formatted block that you can edit
|
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``
|
and use to pass in to ``aprsd`` with ``-c``. By default aprsd looks in ``~/.config/aprsd/aprsd.yml``
|
||||||
|
|
||||||
|
@ -136,108 +147,10 @@ and use to pass in to ``aprsd`` with ``-c``. By default aprsd looks in ``~/.con
|
||||||
::
|
::
|
||||||
|
|
||||||
└─> aprsd sample-config
|
└─> aprsd sample-config
|
||||||
aprs:
|
...
|
||||||
# Set enabled to False if there is no internet connectivity.
|
|
||||||
# This is useful for a direwolf KISS aprs connection only.
|
|
||||||
|
|
||||||
# Get the passcode for your callsign here:
|
|
||||||
# https://apps.magicbug.co.uk/passcode
|
|
||||||
enabled: true
|
|
||||||
host: rotate.aprs2.net
|
|
||||||
login: CALLSIGN
|
|
||||||
password: '00000'
|
|
||||||
port: 14580
|
|
||||||
aprsd:
|
|
||||||
dateformat: '%m/%d/%Y %I:%M:%S %p'
|
|
||||||
email:
|
|
||||||
enabled: true
|
|
||||||
imap:
|
|
||||||
debug: false
|
|
||||||
host: imap.gmail.com
|
|
||||||
login: IMAP_USERNAME
|
|
||||||
password: IMAP_PASSWORD
|
|
||||||
port: 993
|
|
||||||
use_ssl: true
|
|
||||||
shortcuts:
|
|
||||||
aa: 5551239999@vtext.com
|
|
||||||
cl: craiglamparter@somedomain.org
|
|
||||||
wb: 555309@vtext.com
|
|
||||||
smtp:
|
|
||||||
debug: false
|
|
||||||
host: smtp.gmail.com
|
|
||||||
login: SMTP_USERNAME
|
|
||||||
password: SMTP_PASSWORD
|
|
||||||
port: 465
|
|
||||||
use_ssl: false
|
|
||||||
enabled_plugins:
|
|
||||||
- aprsd.plugins.email.EmailPlugin
|
|
||||||
- aprsd.plugins.fortune.FortunePlugin
|
|
||||||
- aprsd.plugins.location.LocationPlugin
|
|
||||||
- aprsd.plugins.ping.PingPlugin
|
|
||||||
- aprsd.plugins.query.QueryPlugin
|
|
||||||
- aprsd.plugins.stock.StockPlugin
|
|
||||||
- aprsd.plugins.time.TimePlugin
|
|
||||||
- aprsd.plugins.weather.USWeatherPlugin
|
|
||||||
- aprsd.plugins.version.VersionPlugin
|
|
||||||
logfile: /tmp/aprsd.log
|
|
||||||
logformat: '[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s] %(message)s
|
|
||||||
- [%(pathname)s:%(lineno)d]'
|
|
||||||
rich_logging: false
|
|
||||||
save_location: /Users/i530566/.config/aprsd/
|
|
||||||
trace: false
|
|
||||||
units: imperial
|
|
||||||
watch_list:
|
|
||||||
alert_callsign: NOCALL
|
|
||||||
alert_time_seconds: 43200
|
|
||||||
callsigns: []
|
|
||||||
enabled: false
|
|
||||||
enabled_plugins:
|
|
||||||
- aprsd.plugins.notify.NotifySeenPlugin
|
|
||||||
packet_keep_count: 10
|
|
||||||
web:
|
|
||||||
enabled: true
|
|
||||||
host: 0.0.0.0
|
|
||||||
logging_enabled: true
|
|
||||||
port: 8001
|
|
||||||
users:
|
|
||||||
admin: password-here
|
|
||||||
ham:
|
|
||||||
callsign: NOCALL
|
|
||||||
kiss:
|
|
||||||
serial:
|
|
||||||
baudrate: 9600
|
|
||||||
device: /dev/ttyS0
|
|
||||||
enabled: false
|
|
||||||
tcp:
|
|
||||||
enabled: false
|
|
||||||
host: direwolf.ip.address
|
|
||||||
port: '8001'
|
|
||||||
services:
|
|
||||||
aprs.fi:
|
|
||||||
# Get the apiKey from your aprs.fi account here:
|
|
||||||
# http://aprs.fi/account
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
avwx:
|
|
||||||
# (Optional for AVWXWeatherPlugin)
|
|
||||||
# Use hosted avwx-api here: https://avwx.rest
|
|
||||||
# or deploy your own from here:
|
|
||||||
# https://github.com/avwx-rest/avwx-api
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
base_url: http://host:port
|
|
||||||
opencagedata:
|
|
||||||
# (Optional for TimeOpenCageDataPlugin)
|
|
||||||
# Get the apiKey from your opencagedata account here:
|
|
||||||
# https://opencagedata.com/dashboard#api-keys
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
openweathermap:
|
|
||||||
# (Optional for OWMWeatherPlugin)
|
|
||||||
# Get the apiKey from your
|
|
||||||
# openweathermap account here:
|
|
||||||
# https://home.openweathermap.org/api_keys
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
|
|
||||||
server
|
server
|
||||||
^^^^^^
|
======
|
||||||
|
|
||||||
This is the main server command that will listen to APRS-IS servers and
|
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
|
look for incomming commands to the callsign configured in the config file
|
||||||
|
@ -277,7 +190,7 @@ look for incomming commands to the callsign configured in the config file
|
||||||
|
|
||||||
|
|
||||||
send-message
|
send-message
|
||||||
^^^^^^^^^^^^
|
============
|
||||||
|
|
||||||
This command is typically used for development to send another aprsd instance
|
This command is typically used for development to send another aprsd instance
|
||||||
test messages
|
test messages
|
||||||
|
@ -310,7 +223,7 @@ test messages
|
||||||
|
|
||||||
|
|
||||||
SEND EMAIL (radio to smtp server)
|
SEND EMAIL (radio to smtp server)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
=================================
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -332,7 +245,7 @@ SEND EMAIL (radio to smtp server)
|
||||||
|
|
||||||
|
|
||||||
RECEIVE EMAIL (imap server to radio)
|
RECEIVE EMAIL (imap server to radio)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
====================================
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -349,7 +262,7 @@ RECEIVE EMAIL (imap server to radio)
|
||||||
|
|
||||||
|
|
||||||
LOCATION
|
LOCATION
|
||||||
^^^^^^^^
|
========
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -384,7 +297,7 @@ Development
|
||||||
* ``make``
|
* ``make``
|
||||||
|
|
||||||
Workflow
|
Workflow
|
||||||
--------
|
========
|
||||||
|
|
||||||
While working aprsd, The workflow is as follows:
|
While working aprsd, The workflow is as follows:
|
||||||
|
|
||||||
|
@ -413,7 +326,7 @@ While working aprsd, The workflow is as follows:
|
||||||
|
|
||||||
|
|
||||||
Release
|
Release
|
||||||
-------
|
=======
|
||||||
|
|
||||||
To do release to pypi:
|
To do release to pypi:
|
||||||
|
|
||||||
|
@ -435,10 +348,10 @@ To do release to pypi:
|
||||||
|
|
||||||
|
|
||||||
Docker Container
|
Docker Container
|
||||||
----------------
|
================
|
||||||
|
|
||||||
Building
|
Building
|
||||||
^^^^^^^^
|
========
|
||||||
|
|
||||||
There are 2 versions of the container Dockerfile that can be used.
|
There are 2 versions of the container Dockerfile that can be used.
|
||||||
The main Dockerfile, which is for building the official release container
|
The main Dockerfile, which is for building the official release container
|
||||||
|
@ -447,18 +360,18 @@ which is used for building a container based off of a git branch of
|
||||||
the repo.
|
the repo.
|
||||||
|
|
||||||
Official Build
|
Official Build
|
||||||
^^^^^^^^^^^^^^
|
==============
|
||||||
|
|
||||||
``docker build -t hemna6969/aprsd:latest .``
|
``docker build -t hemna6969/aprsd:latest .``
|
||||||
|
|
||||||
Development Build
|
Development Build
|
||||||
^^^^^^^^^^^^^^^^^
|
=================
|
||||||
|
|
||||||
``docker build -t hemna6969/aprsd:latest -f Dockerfile-dev .``
|
``docker build -t hemna6969/aprsd:latest -f Dockerfile-dev .``
|
||||||
|
|
||||||
|
|
||||||
Running the container
|
Running the container
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
=====================
|
||||||
|
|
||||||
There is a ``docker-compose.yml`` file in the ``docker/`` directory
|
There is a ``docker-compose.yml`` file in the ``docker/`` directory
|
||||||
that can be used to run your container. To provide the container
|
that can be used to run your container. To provide the container
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
# python included libs
|
# python included libs
|
||||||
import datetime
|
import datetime
|
||||||
|
from importlib.metadata import entry_points
|
||||||
|
from importlib.metadata import version as metadata_version
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
@ -29,19 +31,20 @@ import time
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import click_completion
|
import click_completion
|
||||||
|
from oslo_config import cfg, generator
|
||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import cli_helper
|
from aprsd import cli_helper, packets, stats, threads, utils
|
||||||
from aprsd import config as aprsd_config
|
|
||||||
from aprsd import packets, stats, threads, utils
|
|
||||||
|
|
||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
# logging.basicConfig(level=logging.DEBUG) # level=10
|
# logging.basicConfig(level=logging.DEBUG) # level=10
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||||
flask_enabled = False
|
flask_enabled = False
|
||||||
|
rpc_serv = None
|
||||||
|
|
||||||
|
|
||||||
def custom_startswith(string, incomplete):
|
def custom_startswith(string, incomplete):
|
||||||
|
@ -91,6 +94,7 @@ def signal_handler(sig, frame):
|
||||||
LOG.info(stats.APRSDStats())
|
LOG.info(stats.APRSDStats())
|
||||||
# signal.signal(signal.SIGTERM, sys.exit(0))
|
# signal.signal(signal.SIGTERM, sys.exit(0))
|
||||||
# sys.exit(0)
|
# sys.exit(0)
|
||||||
|
|
||||||
if flask_enabled:
|
if flask_enabled:
|
||||||
signal.signal(signal.SIGTERM, sys.exit(0))
|
signal.signal(signal.SIGTERM, sys.exit(0))
|
||||||
|
|
||||||
|
@ -111,8 +115,32 @@ def check_version(ctx):
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def sample_config(ctx):
|
def sample_config(ctx):
|
||||||
"""This dumps the config to stdout."""
|
"""Generate a sample Config file from aprsd and all installed plugins."""
|
||||||
click.echo(aprsd_config.dump_default_cfg())
|
|
||||||
|
def get_namespaces():
|
||||||
|
args = []
|
||||||
|
|
||||||
|
selected = entry_points(group="oslo.config.opts")
|
||||||
|
for entry in selected:
|
||||||
|
if "aprsd" in entry.name:
|
||||||
|
args.append("--namespace")
|
||||||
|
args.append(entry.name)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
args = get_namespaces()
|
||||||
|
config_version = metadata_version("oslo.config")
|
||||||
|
logging.basicConfig(level=logging.WARN)
|
||||||
|
conf = cfg.ConfigOpts()
|
||||||
|
generator.register_cli_opts(conf)
|
||||||
|
try:
|
||||||
|
conf(args, version=config_version)
|
||||||
|
except cfg.RequiredOptError:
|
||||||
|
conf.print_help()
|
||||||
|
if not sys.argv[1:]:
|
||||||
|
raise SystemExit
|
||||||
|
raise
|
||||||
|
generator.generate(conf)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
|
|
@ -1,13 +1,24 @@
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
import aprsd
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd.logging import log
|
from aprsd.logging import log
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
home = str(Path.home())
|
||||||
|
DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
|
||||||
|
DEFAULT_SAVE_FILE = f"{home}/.config/aprsd/aprsd.p"
|
||||||
|
DEFAULT_CONFIG_FILE = f"{home}/.config/aprsd/aprsd.conf"
|
||||||
|
|
||||||
|
|
||||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
common_options = [
|
common_options = [
|
||||||
|
@ -27,7 +38,7 @@ common_options = [
|
||||||
"--config",
|
"--config",
|
||||||
"config_file",
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=aprsd_config.DEFAULT_CONFIG_FILE,
|
default=DEFAULT_CONFIG_FILE,
|
||||||
help="The aprsd config file to use for options.",
|
help="The aprsd config file to use for options.",
|
||||||
),
|
),
|
||||||
click.option(
|
click.option(
|
||||||
|
@ -51,18 +62,32 @@ def process_standard_options(f: F) -> F:
|
||||||
def new_func(*args, **kwargs):
|
def new_func(*args, **kwargs):
|
||||||
ctx = args[0]
|
ctx = args[0]
|
||||||
ctx.ensure_object(dict)
|
ctx.ensure_object(dict)
|
||||||
|
config_file_found = True
|
||||||
|
if kwargs["config_file"]:
|
||||||
|
default_config_files = [kwargs["config_file"]]
|
||||||
|
else:
|
||||||
|
default_config_files = None
|
||||||
|
try:
|
||||||
|
CONF(
|
||||||
|
[], project="aprsd", version=aprsd.__version__,
|
||||||
|
default_config_files=default_config_files,
|
||||||
|
)
|
||||||
|
except cfg.ConfigFilesNotFoundError:
|
||||||
|
config_file_found = False
|
||||||
ctx.obj["loglevel"] = kwargs["loglevel"]
|
ctx.obj["loglevel"] = kwargs["loglevel"]
|
||||||
ctx.obj["config_file"] = kwargs["config_file"]
|
# ctx.obj["config_file"] = kwargs["config_file"]
|
||||||
ctx.obj["quiet"] = kwargs["quiet"]
|
ctx.obj["quiet"] = kwargs["quiet"]
|
||||||
ctx.obj["config"] = aprsd_config.parse_config(kwargs["config_file"])
|
|
||||||
log.setup_logging(
|
log.setup_logging(
|
||||||
ctx.obj["config"],
|
|
||||||
ctx.obj["loglevel"],
|
ctx.obj["loglevel"],
|
||||||
ctx.obj["quiet"],
|
ctx.obj["quiet"],
|
||||||
)
|
)
|
||||||
if ctx.obj["config"]["aprsd"].get("trace", False):
|
if CONF.trace_enabled:
|
||||||
trace.setup_tracing(["method", "api"])
|
trace.setup_tracing(["method", "api"])
|
||||||
|
|
||||||
|
if not config_file_found:
|
||||||
|
LOG = logging.getLogger("APRSD") # noqa: N806
|
||||||
|
LOG.error("No config file found!! run 'aprsd sample-config'")
|
||||||
|
|
||||||
del kwargs["loglevel"]
|
del kwargs["loglevel"]
|
||||||
del kwargs["config_file"]
|
del kwargs["config_file"]
|
||||||
del kwargs["quiet"]
|
del kwargs["quiet"]
|
||||||
|
|
114
aprsd/client.py
114
aprsd/client.py
|
@ -4,14 +4,15 @@ import time
|
||||||
|
|
||||||
import aprslib
|
import aprslib
|
||||||
from aprslib.exceptions import LoginError
|
from aprslib.exceptions import LoginError
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
|
||||||
from aprsd import exception
|
from aprsd import exception
|
||||||
from aprsd.clients import aprsis, kiss
|
from aprsd.clients import aprsis, kiss
|
||||||
from aprsd.packets import core, packet_list
|
from aprsd.packets import core, packet_list
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
TRANSPORT_APRSIS = "aprsis"
|
TRANSPORT_APRSIS = "aprsis"
|
||||||
TRANSPORT_TCPKISS = "tcpkiss"
|
TRANSPORT_TCPKISS = "tcpkiss"
|
||||||
|
@ -28,7 +29,6 @@ class Client:
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
_client = None
|
_client = None
|
||||||
config = None
|
|
||||||
|
|
||||||
connected = False
|
connected = False
|
||||||
server_string = None
|
server_string = None
|
||||||
|
@ -41,11 +41,6 @@ class Client:
|
||||||
# Put any initialization here.
|
# Put any initialization here.
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, config=None):
|
|
||||||
"""Initialize the object instance."""
|
|
||||||
if config:
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
def set_filter(self, filter):
|
def set_filter(self, filter):
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
if self._client:
|
if self._client:
|
||||||
|
@ -74,12 +69,12 @@ class Client:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def is_enabled(config):
|
def is_enabled():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def transport(config):
|
def transport():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
@ -90,26 +85,38 @@ class Client:
|
||||||
class APRSISClient(Client):
|
class APRSISClient(Client):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_enabled(config):
|
def is_enabled():
|
||||||
# Defaults to True if the enabled flag is non existent
|
# Defaults to True if the enabled flag is non existent
|
||||||
try:
|
try:
|
||||||
return config["aprs"].get("enabled", True)
|
return CONF.aprs_network.enabled
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_configured(config):
|
def is_configured():
|
||||||
if APRSISClient.is_enabled(config):
|
if APRSISClient.is_enabled():
|
||||||
# Ensure that the config vars are correctly set
|
# Ensure that the config vars are correctly set
|
||||||
config.check_option("aprs.login")
|
if not CONF.aprs_network.login:
|
||||||
config.check_option("aprs.password")
|
LOG.error("Config aprs_network.login not set.")
|
||||||
config.check_option("aprs.host")
|
raise exception.MissingConfigOptionException(
|
||||||
return True
|
"aprs_network.login is not set.",
|
||||||
|
)
|
||||||
|
if not CONF.aprs_network.password:
|
||||||
|
LOG.error("Config aprs_network.password not set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"aprs_network.password is not set.",
|
||||||
|
)
|
||||||
|
if not CONF.aprs_network.host:
|
||||||
|
LOG.error("Config aprs_network.host not set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"aprs_network.host is not set.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def transport(config):
|
def transport():
|
||||||
return TRANSPORT_APRSIS
|
return TRANSPORT_APRSIS
|
||||||
|
|
||||||
def decode_packet(self, *args, **kwargs):
|
def decode_packet(self, *args, **kwargs):
|
||||||
|
@ -118,10 +125,10 @@ class APRSISClient(Client):
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def setup_connection(self):
|
def setup_connection(self):
|
||||||
user = self.config["aprs"]["login"]
|
user = CONF.aprs_network.login
|
||||||
password = self.config["aprs"]["password"]
|
password = CONF.aprs_network.password
|
||||||
host = self.config["aprs"].get("host", "rotate.aprs.net")
|
host = CONF.aprs_network.host
|
||||||
port = self.config["aprs"].get("port", 14580)
|
port = CONF.aprs_network.port
|
||||||
connected = False
|
connected = False
|
||||||
backoff = 1
|
backoff = 1
|
||||||
aprs_client = None
|
aprs_client = None
|
||||||
|
@ -151,45 +158,43 @@ class APRSISClient(Client):
|
||||||
class KISSClient(Client):
|
class KISSClient(Client):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_enabled(config):
|
def is_enabled():
|
||||||
"""Return if tcp or serial KISS is enabled."""
|
"""Return if tcp or serial KISS is enabled."""
|
||||||
if "kiss" not in config:
|
if CONF.kiss_serial.enabled:
|
||||||
return False
|
|
||||||
|
|
||||||
if config.get("kiss.serial.enabled", default=False):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if config.get("kiss.tcp.enabled", default=False):
|
if CONF.kiss_tcp.enabled:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_configured(config):
|
def is_configured():
|
||||||
# Ensure that the config vars are correctly set
|
# Ensure that the config vars are correctly set
|
||||||
if KISSClient.is_enabled(config):
|
if KISSClient.is_enabled():
|
||||||
config.check_option(
|
transport = KISSClient.transport()
|
||||||
"aprsd.callsign",
|
|
||||||
default_fail=aprsd_config.DEFAULT_CONFIG_DICT["aprsd"]["callsign"],
|
|
||||||
)
|
|
||||||
transport = KISSClient.transport(config)
|
|
||||||
if transport == TRANSPORT_SERIALKISS:
|
if transport == TRANSPORT_SERIALKISS:
|
||||||
config.check_option("kiss.serial")
|
if not CONF.kiss_serial.device:
|
||||||
config.check_option("kiss.serial.device")
|
LOG.error("KISS serial enabled, but no device is set.")
|
||||||
|
raise exception.MissingConfigOptionException(
|
||||||
|
"kiss_serial.device is not set.",
|
||||||
|
)
|
||||||
elif transport == TRANSPORT_TCPKISS:
|
elif transport == TRANSPORT_TCPKISS:
|
||||||
config.check_option("kiss.tcp")
|
if not CONF.kiss_tcp.host:
|
||||||
config.check_option("kiss.tcp.host")
|
LOG.error("KISS TCP enabled, but no host is set.")
|
||||||
config.check_option("kiss.tcp.port")
|
raise exception.MissingConfigOptionException(
|
||||||
|
"kiss_tcp.host is not set.",
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
return True
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def transport(config):
|
def transport():
|
||||||
if config.get("kiss.serial.enabled", default=False):
|
if CONF.kiss_serial.enabled:
|
||||||
return TRANSPORT_SERIALKISS
|
return TRANSPORT_SERIALKISS
|
||||||
|
|
||||||
if config.get("kiss.tcp.enabled", default=False):
|
if CONF.kiss_tcp.enabled:
|
||||||
return TRANSPORT_TCPKISS
|
return TRANSPORT_TCPKISS
|
||||||
|
|
||||||
def decode_packet(self, *args, **kwargs):
|
def decode_packet(self, *args, **kwargs):
|
||||||
|
@ -208,7 +213,7 @@ class KISSClient(Client):
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def setup_connection(self):
|
def setup_connection(self):
|
||||||
client = kiss.KISS3Client(self.config)
|
client = kiss.KISS3Client()
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,8 +227,7 @@ class ClientFactory:
|
||||||
# Put any initialization here.
|
# Put any initialization here.
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self):
|
||||||
self.config = config
|
|
||||||
self._builders = {}
|
self._builders = {}
|
||||||
|
|
||||||
def register(self, key, builder):
|
def register(self, key, builder):
|
||||||
|
@ -231,23 +235,23 @@ class ClientFactory:
|
||||||
|
|
||||||
def create(self, key=None):
|
def create(self, key=None):
|
||||||
if not key:
|
if not key:
|
||||||
if APRSISClient.is_enabled(self.config):
|
if APRSISClient.is_enabled():
|
||||||
key = TRANSPORT_APRSIS
|
key = TRANSPORT_APRSIS
|
||||||
elif KISSClient.is_enabled(self.config):
|
elif KISSClient.is_enabled():
|
||||||
key = KISSClient.transport(self.config)
|
key = KISSClient.transport()
|
||||||
|
|
||||||
LOG.debug(f"GET client '{key}'")
|
LOG.debug(f"GET client '{key}'")
|
||||||
builder = self._builders.get(key)
|
builder = self._builders.get(key)
|
||||||
if not builder:
|
if not builder:
|
||||||
raise ValueError(key)
|
raise ValueError(key)
|
||||||
return builder(self.config)
|
return builder()
|
||||||
|
|
||||||
def is_client_enabled(self):
|
def is_client_enabled(self):
|
||||||
"""Make sure at least one client is enabled."""
|
"""Make sure at least one client is enabled."""
|
||||||
enabled = False
|
enabled = False
|
||||||
for key in self._builders.keys():
|
for key in self._builders.keys():
|
||||||
try:
|
try:
|
||||||
enabled |= self._builders[key].is_enabled(self.config)
|
enabled |= self._builders[key].is_enabled()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -257,7 +261,7 @@ class ClientFactory:
|
||||||
enabled = False
|
enabled = False
|
||||||
for key in self._builders.keys():
|
for key in self._builders.keys():
|
||||||
try:
|
try:
|
||||||
enabled |= self._builders[key].is_configured(self.config)
|
enabled |= self._builders[key].is_configured()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
except exception.MissingConfigOptionException as ex:
|
except exception.MissingConfigOptionException as ex:
|
||||||
|
@ -270,11 +274,11 @@ class ClientFactory:
|
||||||
return enabled
|
return enabled
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setup(config):
|
def setup():
|
||||||
"""Create and register all possible client objects."""
|
"""Create and register all possible client objects."""
|
||||||
global factory
|
global factory
|
||||||
|
|
||||||
factory = ClientFactory(config)
|
factory = ClientFactory()
|
||||||
factory.register(TRANSPORT_APRSIS, APRSISClient)
|
factory.register(TRANSPORT_APRSIS, APRSISClient)
|
||||||
factory.register(TRANSPORT_TCPKISS, KISSClient)
|
factory.register(TRANSPORT_TCPKISS, KISSClient)
|
||||||
factory.register(TRANSPORT_SERIALKISS, KISSClient)
|
factory.register(TRANSPORT_SERIALKISS, KISSClient)
|
||||||
|
|
|
@ -6,13 +6,15 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
from aprsd import cli_helper, client, packets, plugin, stats, utils
|
from aprsd import cli_helper, client, conf, packets, plugin
|
||||||
from aprsd.aprsd import cli
|
from aprsd.aprsd import cli
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||||
|
|
||||||
|
@ -68,50 +70,40 @@ def test_plugin(
|
||||||
message,
|
message,
|
||||||
):
|
):
|
||||||
"""Test an individual APRSD plugin given a python path."""
|
"""Test an individual APRSD plugin given a python path."""
|
||||||
config = ctx.obj["config"]
|
|
||||||
|
|
||||||
flat_config = utils.flatten_dict(config)
|
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||||
LOG.info("Using CONFIG values:")
|
|
||||||
for x in flat_config:
|
|
||||||
if "password" in x or "aprsd.web.users.admin" in x:
|
|
||||||
LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
|
|
||||||
else:
|
|
||||||
LOG.info(f"{x} = {flat_config[x]}")
|
|
||||||
|
|
||||||
if not aprs_login:
|
if not aprs_login:
|
||||||
if not config.exists("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)
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
fromcall = config.get("aprs.login")
|
fromcall = CONF.aprs_network.login
|
||||||
else:
|
else:
|
||||||
fromcall = aprs_login
|
fromcall = aprs_login
|
||||||
|
|
||||||
if not plugin_path:
|
if not plugin_path:
|
||||||
click.echo(ctx.get_help())
|
click.echo(ctx.get_help())
|
||||||
click.echo("")
|
click.echo("")
|
||||||
ctx.fail("Failed to provide -p option to test a plugin")
|
click.echo("Failed to provide -p option to test a plugin")
|
||||||
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
|
|
||||||
if type(message) is tuple:
|
if type(message) is tuple:
|
||||||
message = " ".join(message)
|
message = " ".join(message)
|
||||||
|
|
||||||
if config["aprsd"].get("trace", False):
|
if CONF.trace_enabled:
|
||||||
trace.setup_tracing(["method", "api"])
|
trace.setup_tracing(["method", "api"])
|
||||||
|
|
||||||
client.Client(config)
|
client.Client()
|
||||||
stats.APRSDStats(config)
|
|
||||||
packets.PacketTrack(config=config)
|
|
||||||
packets.WatchList(config=config)
|
|
||||||
packets.SeenList(config=config)
|
|
||||||
|
|
||||||
pm = plugin.PluginManager(config)
|
pm = plugin.PluginManager()
|
||||||
if load_all:
|
if load_all:
|
||||||
pm.setup_plugins()
|
pm.setup_plugins()
|
||||||
else:
|
else:
|
||||||
pm._init()
|
pm._init()
|
||||||
obj = pm._create_class(plugin_path, plugin.APRSDPluginBase, config=config)
|
obj = pm._create_class(plugin_path, plugin.APRSDPluginBase)
|
||||||
if not obj:
|
if not obj:
|
||||||
click.echo(ctx.get_help())
|
click.echo(ctx.get_help())
|
||||||
click.echo("")
|
click.echo("")
|
||||||
|
@ -125,14 +117,13 @@ def test_plugin(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
pm._pluggy_pm.register(obj)
|
pm._pluggy_pm.register(obj)
|
||||||
login = config["aprs"]["login"]
|
|
||||||
|
|
||||||
packet = {
|
packet = packets.MessagePacket(
|
||||||
"from": fromcall, "addresse": login,
|
from_call=fromcall,
|
||||||
"message_text": message,
|
to_call=CONF.callsign,
|
||||||
"format": "message",
|
msgNo=1,
|
||||||
"msgNo": 1,
|
message_text=message,
|
||||||
}
|
)
|
||||||
LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'")
|
LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'")
|
||||||
|
|
||||||
for x in range(number):
|
for x in range(number):
|
||||||
|
|
|
@ -10,11 +10,12 @@ import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from oslo_config import cfg
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import cli_helper, client, packets, stats, threads, utils
|
from aprsd import cli_helper, client, packets, stats, threads
|
||||||
from aprsd.aprsd import cli
|
from aprsd.aprsd import cli
|
||||||
from aprsd.threads import rx
|
from aprsd.threads import rx
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ from aprsd.threads import rx
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
# logging.basicConfig(level=logging.DEBUG) # level=10
|
# logging.basicConfig(level=logging.DEBUG) # level=10
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
CONF = cfg.CONF
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,8 +40,8 @@ def signal_handler(sig, frame):
|
||||||
|
|
||||||
|
|
||||||
class APRSDListenThread(rx.APRSDRXThread):
|
class APRSDListenThread(rx.APRSDRXThread):
|
||||||
def __init__(self, config, packet_queue, packet_filter=None):
|
def __init__(self, packet_queue, packet_filter=None):
|
||||||
super().__init__(config, packet_queue)
|
super().__init__(packet_queue)
|
||||||
self.packet_filter = packet_filter
|
self.packet_filter = packet_filter
|
||||||
|
|
||||||
def process_packet(self, *args, **kwargs):
|
def process_packet(self, *args, **kwargs):
|
||||||
|
@ -118,7 +120,6 @@ def listen(
|
||||||
"""
|
"""
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
config = ctx.obj["config"]
|
|
||||||
|
|
||||||
if not aprs_login:
|
if not aprs_login:
|
||||||
click.echo(ctx.get_help())
|
click.echo(ctx.get_help())
|
||||||
|
@ -132,27 +133,19 @@ def listen(
|
||||||
ctx.fail("Must set --aprs-password or APRS_PASSWORD")
|
ctx.fail("Must set --aprs-password or APRS_PASSWORD")
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
||||||
config["aprs"]["login"] = aprs_login
|
# CONF.aprs_network.login = aprs_login
|
||||||
config["aprs"]["password"] = aprs_password
|
# config["aprs"]["password"] = aprs_password
|
||||||
|
|
||||||
LOG.info(f"APRSD Listen Started version: {aprsd.__version__}")
|
LOG.info(f"APRSD Listen Started version: {aprsd.__version__}")
|
||||||
|
|
||||||
flat_config = utils.flatten_dict(config)
|
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||||
LOG.info("Using CONFIG values:")
|
|
||||||
for x in flat_config:
|
|
||||||
if "password" in x or "aprsd.web.users.admin" in x:
|
|
||||||
LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
|
|
||||||
else:
|
|
||||||
LOG.info(f"{x} = {flat_config[x]}")
|
|
||||||
|
|
||||||
stats.APRSDStats(config)
|
|
||||||
|
|
||||||
# Try and load saved MsgTrack list
|
# Try and load saved MsgTrack list
|
||||||
LOG.debug("Loading saved MsgTrack object.")
|
LOG.debug("Loading saved MsgTrack object.")
|
||||||
|
|
||||||
# Initialize the client factory and create
|
# Initialize the client factory and create
|
||||||
# The correct client object ready for use
|
# The correct client object ready for use
|
||||||
client.ClientFactory.setup(config)
|
client.ClientFactory.setup()
|
||||||
# Make sure we have 1 client transport enabled
|
# Make sure we have 1 client transport enabled
|
||||||
if not client.factory.is_client_enabled():
|
if not client.factory.is_client_enabled():
|
||||||
LOG.error("No Clients are enabled in config.")
|
LOG.error("No Clients are enabled in config.")
|
||||||
|
@ -166,12 +159,11 @@ def listen(
|
||||||
LOG.debug(f"Filter by '{filter}'")
|
LOG.debug(f"Filter by '{filter}'")
|
||||||
aprs_client.set_filter(filter)
|
aprs_client.set_filter(filter)
|
||||||
|
|
||||||
keepalive = threads.KeepAliveThread(config=config)
|
keepalive = threads.KeepAliveThread()
|
||||||
keepalive.start()
|
keepalive.start()
|
||||||
|
|
||||||
LOG.debug("Create APRSDListenThread")
|
LOG.debug("Create APRSDListenThread")
|
||||||
listen_thread = APRSDListenThread(
|
listen_thread = APRSDListenThread(
|
||||||
config=config,
|
|
||||||
packet_queue=threads.packet_queue,
|
packet_queue=threads.packet_queue,
|
||||||
packet_filter=packet_filter,
|
packet_filter=packet_filter,
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,13 +5,16 @@ import time
|
||||||
import aprslib
|
import aprslib
|
||||||
from aprslib.exceptions import LoginError
|
from aprslib.exceptions import LoginError
|
||||||
import click
|
import click
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import cli_helper, client, packets
|
from aprsd import cli_helper, client, packets
|
||||||
|
from aprsd import conf # noqa : F401
|
||||||
from aprsd.aprsd import cli
|
from aprsd.aprsd import cli
|
||||||
from aprsd.threads import tx
|
from aprsd.threads import tx
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,24 +65,24 @@ def send_message(
|
||||||
):
|
):
|
||||||
"""Send a message to a callsign via APRS_IS."""
|
"""Send a message to a callsign via APRS_IS."""
|
||||||
global got_ack, got_response
|
global got_ack, got_response
|
||||||
config = ctx.obj["config"]
|
|
||||||
quiet = ctx.obj["quiet"]
|
quiet = ctx.obj["quiet"]
|
||||||
|
|
||||||
if not aprs_login:
|
if not aprs_login:
|
||||||
if not config.exists("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)
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
config["aprs"]["login"] = aprs_login
|
aprs_login = CONF.aprs_network.login
|
||||||
|
|
||||||
if not aprs_password:
|
if not aprs_password:
|
||||||
if not config.exists("aprs.password"):
|
LOG.warning(CONF.aprs_network.password)
|
||||||
|
if not CONF.aprs_network.password:
|
||||||
click.echo("Must set --aprs-password or APRS_PASSWORD")
|
click.echo("Must set --aprs-password or APRS_PASSWORD")
|
||||||
ctx.exit(-1)
|
ctx.exit(-1)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
config["aprs"]["password"] = aprs_password
|
aprs_password = CONF.aprs_network.password
|
||||||
|
|
||||||
LOG.info(f"APRSD LISTEN Started version: {aprsd.__version__}")
|
LOG.info(f"APRSD LISTEN Started version: {aprsd.__version__}")
|
||||||
if type(command) is tuple:
|
if type(command) is tuple:
|
||||||
|
@ -90,9 +93,9 @@ def send_message(
|
||||||
else:
|
else:
|
||||||
LOG.info(f"L'{aprs_login}' To'{tocallsign}' C'{command}'")
|
LOG.info(f"L'{aprs_login}' To'{tocallsign}' C'{command}'")
|
||||||
|
|
||||||
packets.PacketList(config=config)
|
packets.PacketList()
|
||||||
packets.WatchList(config=config)
|
packets.WatchList()
|
||||||
packets.SeenList(config=config)
|
packets.SeenList()
|
||||||
|
|
||||||
got_ack = False
|
got_ack = False
|
||||||
got_response = False
|
got_response = False
|
||||||
|
@ -109,7 +112,7 @@ def send_message(
|
||||||
else:
|
else:
|
||||||
got_response = True
|
got_response = True
|
||||||
from_call = packet.from_call
|
from_call = packet.from_call
|
||||||
our_call = config["aprsd"]["callsign"].lower()
|
our_call = CONF.callsign.lower()
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.AckPacket(
|
packets.AckPacket(
|
||||||
from_call=our_call,
|
from_call=our_call,
|
||||||
|
@ -127,7 +130,7 @@ def send_message(
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.ClientFactory.setup(config)
|
client.ClientFactory.setup()
|
||||||
client.factory.create().client
|
client.factory.create().client
|
||||||
except LoginError:
|
except LoginError:
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
|
@ -3,16 +3,18 @@ import signal
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import (
|
from aprsd import (
|
||||||
cli_helper, client, flask, packets, plugin, stats, threads, utils,
|
cli_helper, client, packets, plugin, rpc_server, threads, utils,
|
||||||
)
|
)
|
||||||
from aprsd import aprsd as aprsd_main
|
from aprsd import aprsd as aprsd_main
|
||||||
from aprsd.aprsd import cli
|
from aprsd.aprsd import cli
|
||||||
from aprsd.threads import rx
|
from aprsd.threads import rx
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,17 +34,9 @@ LOG = logging.getLogger("APRSD")
|
||||||
@cli_helper.process_standard_options
|
@cli_helper.process_standard_options
|
||||||
def server(ctx, flush):
|
def server(ctx, flush):
|
||||||
"""Start the aprsd server gateway process."""
|
"""Start the aprsd server gateway process."""
|
||||||
ctx.obj["config_file"]
|
|
||||||
loglevel = ctx.obj["loglevel"]
|
|
||||||
quiet = ctx.obj["quiet"]
|
|
||||||
config = ctx.obj["config"]
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, aprsd_main.signal_handler)
|
signal.signal(signal.SIGINT, aprsd_main.signal_handler)
|
||||||
signal.signal(signal.SIGTERM, aprsd_main.signal_handler)
|
signal.signal(signal.SIGTERM, aprsd_main.signal_handler)
|
||||||
|
|
||||||
if not quiet:
|
|
||||||
click.echo("Load config")
|
|
||||||
|
|
||||||
level, msg = utils._check_version()
|
level, msg = utils._check_version()
|
||||||
if level:
|
if level:
|
||||||
LOG.warning(msg)
|
LOG.warning(msg)
|
||||||
|
@ -50,19 +44,11 @@ def server(ctx, flush):
|
||||||
LOG.info(msg)
|
LOG.info(msg)
|
||||||
LOG.info(f"APRSD Started version: {aprsd.__version__}")
|
LOG.info(f"APRSD Started version: {aprsd.__version__}")
|
||||||
|
|
||||||
flat_config = utils.flatten_dict(config)
|
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||||
LOG.info("Using CONFIG values:")
|
|
||||||
for x in flat_config:
|
|
||||||
if "password" in x or "aprsd.web.users.admin" in x:
|
|
||||||
LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
|
|
||||||
else:
|
|
||||||
LOG.info(f"{x} = {flat_config[x]}")
|
|
||||||
|
|
||||||
stats.APRSDStats(config)
|
|
||||||
|
|
||||||
# Initialize the client factory and create
|
# Initialize the client factory and create
|
||||||
# The correct client object ready for use
|
# The correct client object ready for use
|
||||||
client.ClientFactory.setup(config)
|
client.ClientFactory.setup()
|
||||||
# Make sure we have 1 client transport enabled
|
# Make sure we have 1 client transport enabled
|
||||||
if not client.factory.is_client_enabled():
|
if not client.factory.is_client_enabled():
|
||||||
LOG.error("No Clients are enabled in config.")
|
LOG.error("No Clients are enabled in config.")
|
||||||
|
@ -77,30 +63,28 @@ def server(ctx, flush):
|
||||||
client.factory.create().client
|
client.factory.create().client
|
||||||
|
|
||||||
# Now load the msgTrack from disk if any
|
# Now load the msgTrack from disk if any
|
||||||
packets.PacketList(config=config)
|
packets.PacketList()
|
||||||
if flush:
|
if flush:
|
||||||
LOG.debug("Deleting saved MsgTrack.")
|
LOG.debug("Deleting saved MsgTrack.")
|
||||||
packets.PacketTrack(config=config).flush()
|
packets.PacketTrack().flush()
|
||||||
packets.WatchList(config=config)
|
packets.WatchList()
|
||||||
packets.SeenList(config=config)
|
packets.SeenList()
|
||||||
else:
|
else:
|
||||||
# Try and load saved MsgTrack list
|
# Try and load saved MsgTrack list
|
||||||
LOG.debug("Loading saved MsgTrack object.")
|
LOG.debug("Loading saved MsgTrack object.")
|
||||||
packets.PacketTrack(config=config).load()
|
packets.PacketTrack().load()
|
||||||
packets.WatchList(config=config).load()
|
packets.WatchList().load()
|
||||||
packets.SeenList(config=config).load()
|
packets.SeenList().load()
|
||||||
|
|
||||||
# Create the initial PM singleton and Register plugins
|
# Create the initial PM singleton and Register plugins
|
||||||
LOG.info("Loading Plugin Manager and registering plugins")
|
LOG.info("Loading Plugin Manager and registering plugins")
|
||||||
plugin_manager = plugin.PluginManager(config)
|
plugin_manager = plugin.PluginManager()
|
||||||
plugin_manager.setup_plugins()
|
plugin_manager.setup_plugins()
|
||||||
|
|
||||||
rx_thread = rx.APRSDPluginRXThread(
|
rx_thread = rx.APRSDPluginRXThread(
|
||||||
packet_queue=threads.packet_queue,
|
packet_queue=threads.packet_queue,
|
||||||
config=config,
|
|
||||||
)
|
)
|
||||||
process_thread = rx.APRSDPluginProcessPacketThread(
|
process_thread = rx.APRSDPluginProcessPacketThread(
|
||||||
config=config,
|
|
||||||
packet_queue=threads.packet_queue,
|
packet_queue=threads.packet_queue,
|
||||||
)
|
)
|
||||||
rx_thread.start()
|
rx_thread.start()
|
||||||
|
@ -108,21 +92,13 @@ def server(ctx, flush):
|
||||||
|
|
||||||
packets.PacketTrack().restart()
|
packets.PacketTrack().restart()
|
||||||
|
|
||||||
keepalive = threads.KeepAliveThread(config=config)
|
keepalive = threads.KeepAliveThread()
|
||||||
keepalive.start()
|
keepalive.start()
|
||||||
|
|
||||||
web_enabled = config.get("aprsd.web.enabled", default=False)
|
if CONF.rpc_settings.enabled:
|
||||||
|
rpc = rpc_server.APRSDRPCThread()
|
||||||
|
rpc.start()
|
||||||
|
log_monitor = threads.log_monitor.LogMonitorThread()
|
||||||
|
log_monitor.start()
|
||||||
|
|
||||||
if web_enabled:
|
|
||||||
aprsd_main.flask_enabled = True
|
|
||||||
(socketio, app) = flask.init_flask(config, loglevel, quiet)
|
|
||||||
socketio.run(
|
|
||||||
app,
|
|
||||||
allow_unsafe_werkzeug=True,
|
|
||||||
host=config["aprsd"]["web"]["host"],
|
|
||||||
port=config["aprsd"]["web"]["port"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# If there are items in the msgTracker, then save them
|
|
||||||
LOG.info("APRSD Exiting.")
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -15,23 +15,24 @@ from flask.logging import default_handler
|
||||||
import flask_classful
|
import flask_classful
|
||||||
from flask_httpauth import HTTPBasicAuth
|
from flask_httpauth import HTTPBasicAuth
|
||||||
from flask_socketio import Namespace, SocketIO
|
from flask_socketio import Namespace, SocketIO
|
||||||
|
from oslo_config import cfg
|
||||||
from user_agents import parse as ua_parse
|
from user_agents import parse as ua_parse
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import cli_helper, client
|
from aprsd import cli_helper, client, conf, packets, stats, threads, utils
|
||||||
from aprsd import config as aprsd_config
|
|
||||||
from aprsd import packets, stats, threads, utils
|
|
||||||
from aprsd.aprsd import cli
|
from aprsd.aprsd import cli
|
||||||
from aprsd.logging import rich as aprsd_logging
|
from aprsd.logging import rich as aprsd_logging
|
||||||
from aprsd.threads import rx, tx
|
from aprsd.threads import rx, tx
|
||||||
from aprsd.utils import objectstore, trace
|
from aprsd.utils import objectstore, trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
auth = HTTPBasicAuth()
|
auth = HTTPBasicAuth()
|
||||||
users = None
|
users = None
|
||||||
|
socketio = None
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
|
@ -128,16 +129,16 @@ class SentMessages(objectstore.ObjectStoreMixin):
|
||||||
def verify_password(username, password):
|
def verify_password(username, password):
|
||||||
global users
|
global users
|
||||||
|
|
||||||
if username in users and check_password_hash(users.get(username), password):
|
if username in users and check_password_hash(users[username], password):
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
|
||||||
class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
|
class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
|
||||||
"""Class that handles packets being sent to us."""
|
"""Class that handles packets being sent to us."""
|
||||||
def __init__(self, config, packet_queue, socketio):
|
def __init__(self, packet_queue, socketio):
|
||||||
self.socketio = socketio
|
self.socketio = socketio
|
||||||
self.connected = False
|
self.connected = False
|
||||||
super().__init__(config, packet_queue)
|
super().__init__(packet_queue)
|
||||||
|
|
||||||
def process_ack_packet(self, packet: packets.AckPacket):
|
def process_ack_packet(self, packet: packets.AckPacket):
|
||||||
super().process_ack_packet(packet)
|
super().process_ack_packet(packet)
|
||||||
|
@ -174,21 +175,16 @@ class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
|
||||||
|
|
||||||
|
|
||||||
class WebChatFlask(flask_classful.FlaskView):
|
class WebChatFlask(flask_classful.FlaskView):
|
||||||
config = None
|
|
||||||
|
|
||||||
def set_config(self, config):
|
def set_config(self):
|
||||||
global users
|
global users
|
||||||
self.config = config
|
|
||||||
self.users = {}
|
self.users = {}
|
||||||
for user in self.config["aprsd"]["web"]["users"]:
|
user = CONF.admin.user
|
||||||
self.users[user] = generate_password_hash(
|
self.users[user] = generate_password_hash(CONF.admin.password)
|
||||||
self.config["aprsd"]["web"]["users"][user],
|
|
||||||
)
|
|
||||||
|
|
||||||
users = self.users
|
users = self.users
|
||||||
|
|
||||||
def _get_transport(self, stats):
|
def _get_transport(self, stats):
|
||||||
if self.config["aprs"].get("enabled", True):
|
if CONF.aprs_network.enabled:
|
||||||
transport = "aprs-is"
|
transport = "aprs-is"
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
||||||
|
@ -196,27 +192,22 @@ class WebChatFlask(flask_classful.FlaskView):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# We might be connected to a KISS socket?
|
# We might be connected to a KISS socket?
|
||||||
if client.KISSClient.is_enabled(self.config):
|
if client.KISSClient.is_enabled():
|
||||||
transport = client.KISSClient.transport(self.config)
|
transport = client.KISSClient.transport()
|
||||||
if transport == client.TRANSPORT_TCPKISS:
|
if transport == client.TRANSPORT_TCPKISS:
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
"TCPKISS://{}:{}".format(
|
"TCPKISS://{}:{}".format(
|
||||||
self.config["kiss"]["tcp"]["host"],
|
CONF.kiss_tcp.host,
|
||||||
self.config["kiss"]["tcp"]["port"],
|
CONF.kiss_tcp.port,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif transport == client.TRANSPORT_SERIALKISS:
|
elif transport == client.TRANSPORT_SERIALKISS:
|
||||||
# for pep8 violation
|
# for pep8 violation
|
||||||
kiss_default = aprsd_config.DEFAULT_DATE_FORMAT["kiss"]
|
|
||||||
default_baudrate = kiss_default["serial"]["baudrate"]
|
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
"SerialKISS://{}@{} baud".format(
|
"SerialKISS://{}@{} baud".format(
|
||||||
self.config["kiss"]["serial"]["device"],
|
CONF.kiss_serial.device,
|
||||||
self.config["kiss"]["serial"].get(
|
CONF.kiss_serial.baudrate,
|
||||||
"baudrate",
|
),
|
||||||
default_baudrate,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return transport, aprs_connection
|
return transport, aprs_connection
|
||||||
|
@ -250,7 +241,7 @@ class WebChatFlask(flask_classful.FlaskView):
|
||||||
html_template,
|
html_template,
|
||||||
initial_stats=stats,
|
initial_stats=stats,
|
||||||
aprs_connection=aprs_connection,
|
aprs_connection=aprs_connection,
|
||||||
callsign=self.config["aprsd"]["callsign"],
|
callsign=CONF.callsign,
|
||||||
version=aprsd.__version__,
|
version=aprsd.__version__,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -287,14 +278,12 @@ class WebChatFlask(flask_classful.FlaskView):
|
||||||
|
|
||||||
class SendMessageNamespace(Namespace):
|
class SendMessageNamespace(Namespace):
|
||||||
"""Class to handle the socketio interactions."""
|
"""Class to handle the socketio interactions."""
|
||||||
_config = None
|
|
||||||
got_ack = False
|
got_ack = False
|
||||||
reply_sent = False
|
reply_sent = False
|
||||||
msg = None
|
msg = None
|
||||||
request = None
|
request = None
|
||||||
|
|
||||||
def __init__(self, namespace=None, config=None):
|
def __init__(self, namespace=None, config=None):
|
||||||
self._config = config
|
|
||||||
super().__init__(namespace)
|
super().__init__(namespace)
|
||||||
|
|
||||||
def on_connect(self):
|
def on_connect(self):
|
||||||
|
@ -312,7 +301,7 @@ class SendMessageNamespace(Namespace):
|
||||||
global socketio
|
global socketio
|
||||||
LOG.debug(f"WS: on_send {data}")
|
LOG.debug(f"WS: on_send {data}")
|
||||||
self.request = data
|
self.request = data
|
||||||
data["from"] = self._config["aprs"]["login"]
|
data["from"] = CONF.callsign
|
||||||
pkt = packets.MessagePacket(
|
pkt = packets.MessagePacket(
|
||||||
from_call=data["from"],
|
from_call=data["from"],
|
||||||
to_call=data["to"].upper(),
|
to_call=data["to"].upper(),
|
||||||
|
@ -338,7 +327,7 @@ class SendMessageNamespace(Namespace):
|
||||||
|
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.GPSPacket(
|
packets.GPSPacket(
|
||||||
from_call=self._config["aprs"]["login"],
|
from_call=CONF.callsign,
|
||||||
to_call="APDW16",
|
to_call="APDW16",
|
||||||
latitude=lat,
|
latitude=lat,
|
||||||
longitude=long,
|
longitude=long,
|
||||||
|
@ -354,25 +343,16 @@ class SendMessageNamespace(Namespace):
|
||||||
LOG.debug(f"WS json {data}")
|
LOG.debug(f"WS json {data}")
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(config, flask_app, loglevel, quiet):
|
def setup_logging(flask_app, loglevel, quiet):
|
||||||
flask_log = logging.getLogger("werkzeug")
|
flask_log = logging.getLogger("werkzeug")
|
||||||
flask_app.logger.removeHandler(default_handler)
|
flask_app.logger.removeHandler(default_handler)
|
||||||
flask_log.removeHandler(default_handler)
|
flask_log.removeHandler(default_handler)
|
||||||
|
|
||||||
log_level = aprsd_config.LOG_LEVELS[loglevel]
|
log_level = conf.log.LOG_LEVELS[loglevel]
|
||||||
flask_log.setLevel(log_level)
|
flask_log.setLevel(log_level)
|
||||||
date_format = config["aprsd"].get(
|
date_format = CONF.logging.date_format
|
||||||
"dateformat",
|
|
||||||
aprsd_config.DEFAULT_DATE_FORMAT,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not config["aprsd"]["web"].get("logging_enabled", False):
|
if CONF.logging.rich_logging and not quiet:
|
||||||
# disable web logging
|
|
||||||
flask_log.disabled = True
|
|
||||||
flask_app.logger.disabled = True
|
|
||||||
# return
|
|
||||||
|
|
||||||
if config["aprsd"].get("rich_logging", False) and not quiet:
|
|
||||||
log_format = "%(message)s"
|
log_format = "%(message)s"
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
rh = aprsd_logging.APRSDRichHandler(
|
rh = aprsd_logging.APRSDRichHandler(
|
||||||
|
@ -382,13 +362,10 @@ def setup_logging(config, flask_app, loglevel, quiet):
|
||||||
rh.setFormatter(log_formatter)
|
rh.setFormatter(log_formatter)
|
||||||
flask_log.addHandler(rh)
|
flask_log.addHandler(rh)
|
||||||
|
|
||||||
log_file = config["aprsd"].get("logfile", None)
|
log_file = CONF.logging.logfile
|
||||||
|
|
||||||
if log_file:
|
if log_file:
|
||||||
log_format = config["aprsd"].get(
|
log_format = CONF.loging.logformat
|
||||||
"logformat",
|
|
||||||
aprsd_config.DEFAULT_LOG_FORMAT,
|
|
||||||
)
|
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
fh = RotatingFileHandler(
|
fh = RotatingFileHandler(
|
||||||
log_file, maxBytes=(10248576 * 5),
|
log_file, maxBytes=(10248576 * 5),
|
||||||
|
@ -399,7 +376,7 @@ def setup_logging(config, flask_app, loglevel, quiet):
|
||||||
|
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def init_flask(config, loglevel, quiet):
|
def init_flask(loglevel, quiet):
|
||||||
global socketio
|
global socketio
|
||||||
|
|
||||||
flask_app = flask.Flask(
|
flask_app = flask.Flask(
|
||||||
|
@ -408,9 +385,9 @@ def init_flask(config, loglevel, quiet):
|
||||||
static_folder="web/chat/static",
|
static_folder="web/chat/static",
|
||||||
template_folder="web/chat/templates",
|
template_folder="web/chat/templates",
|
||||||
)
|
)
|
||||||
setup_logging(config, flask_app, loglevel, quiet)
|
setup_logging(flask_app, loglevel, quiet)
|
||||||
server = WebChatFlask()
|
server = WebChatFlask()
|
||||||
server.set_config(config)
|
server.set_config()
|
||||||
flask_app.route("/", methods=["GET"])(server.index)
|
flask_app.route("/", methods=["GET"])(server.index)
|
||||||
flask_app.route("/stats", methods=["GET"])(server.stats)
|
flask_app.route("/stats", methods=["GET"])(server.stats)
|
||||||
# flask_app.route("/send-message", methods=["GET"])(server.send_message)
|
# flask_app.route("/send-message", methods=["GET"])(server.send_message)
|
||||||
|
@ -427,7 +404,7 @@ def init_flask(config, loglevel, quiet):
|
||||||
|
|
||||||
socketio.on_namespace(
|
socketio.on_namespace(
|
||||||
SendMessageNamespace(
|
SendMessageNamespace(
|
||||||
"/sendmsg", config=config,
|
"/sendmsg",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return socketio, flask_app
|
return socketio, flask_app
|
||||||
|
@ -457,17 +434,12 @@ def init_flask(config, loglevel, quiet):
|
||||||
@cli_helper.process_standard_options
|
@cli_helper.process_standard_options
|
||||||
def webchat(ctx, flush, port):
|
def webchat(ctx, flush, port):
|
||||||
"""Web based HAM Radio chat program!"""
|
"""Web based HAM Radio chat program!"""
|
||||||
ctx.obj["config_file"]
|
|
||||||
loglevel = ctx.obj["loglevel"]
|
loglevel = ctx.obj["loglevel"]
|
||||||
quiet = ctx.obj["quiet"]
|
quiet = ctx.obj["quiet"]
|
||||||
config = ctx.obj["config"]
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
|
||||||
if not quiet:
|
|
||||||
click.echo("Load config")
|
|
||||||
|
|
||||||
level, msg = utils._check_version()
|
level, msg = utils._check_version()
|
||||||
if level:
|
if level:
|
||||||
LOG.warning(msg)
|
LOG.warning(msg)
|
||||||
|
@ -475,19 +447,11 @@ def webchat(ctx, flush, port):
|
||||||
LOG.info(msg)
|
LOG.info(msg)
|
||||||
LOG.info(f"APRSD Started version: {aprsd.__version__}")
|
LOG.info(f"APRSD Started version: {aprsd.__version__}")
|
||||||
|
|
||||||
flat_config = utils.flatten_dict(config)
|
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||||
LOG.info("Using CONFIG values:")
|
|
||||||
for x in flat_config:
|
|
||||||
if "password" in x or "aprsd.web.users.admin" in x:
|
|
||||||
LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
|
|
||||||
else:
|
|
||||||
LOG.info(f"{x} = {flat_config[x]}")
|
|
||||||
|
|
||||||
stats.APRSDStats(config)
|
|
||||||
|
|
||||||
# Initialize the client factory and create
|
# Initialize the client factory and create
|
||||||
# The correct client object ready for use
|
# The correct client object ready for use
|
||||||
client.ClientFactory.setup(config)
|
client.ClientFactory.setup()
|
||||||
# Make sure we have 1 client transport enabled
|
# Make sure we have 1 client transport enabled
|
||||||
if not client.factory.is_client_enabled():
|
if not client.factory.is_client_enabled():
|
||||||
LOG.error("No Clients are enabled in config.")
|
LOG.error("No Clients are enabled in config.")
|
||||||
|
@ -497,32 +461,30 @@ def webchat(ctx, flush, port):
|
||||||
LOG.error("APRS client is not properly configured in config file.")
|
LOG.error("APRS client is not properly configured in config file.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
packets.PacketList(config=config)
|
packets.PacketList()
|
||||||
packets.PacketTrack(config=config)
|
packets.PacketTrack()
|
||||||
packets.WatchList(config=config)
|
packets.WatchList()
|
||||||
packets.SeenList(config=config)
|
packets.SeenList()
|
||||||
|
|
||||||
(socketio, app) = init_flask(config, loglevel, quiet)
|
(socketio, app) = init_flask(loglevel, quiet)
|
||||||
rx_thread = rx.APRSDPluginRXThread(
|
rx_thread = rx.APRSDPluginRXThread(
|
||||||
config=config,
|
|
||||||
packet_queue=threads.packet_queue,
|
packet_queue=threads.packet_queue,
|
||||||
)
|
)
|
||||||
rx_thread.start()
|
rx_thread.start()
|
||||||
process_thread = WebChatProcessPacketThread(
|
process_thread = WebChatProcessPacketThread(
|
||||||
config=config,
|
|
||||||
packet_queue=threads.packet_queue,
|
packet_queue=threads.packet_queue,
|
||||||
socketio=socketio,
|
socketio=socketio,
|
||||||
)
|
)
|
||||||
process_thread.start()
|
process_thread.start()
|
||||||
|
|
||||||
keepalive = threads.KeepAliveThread(config=config)
|
keepalive = threads.KeepAliveThread()
|
||||||
LOG.info("Start KeepAliveThread")
|
LOG.info("Start KeepAliveThread")
|
||||||
keepalive.start()
|
keepalive.start()
|
||||||
LOG.info("Start socketio.run()")
|
LOG.info("Start socketio.run()")
|
||||||
socketio.run(
|
socketio.run(
|
||||||
app,
|
app,
|
||||||
ssl_context="adhoc",
|
ssl_context="adhoc",
|
||||||
host=config["aprsd"]["web"]["host"],
|
host=CONF.admin.web_ip,
|
||||||
port=port,
|
port=port,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd.conf import client, common, log, plugin_common, plugin_email
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
log.register_opts(CONF)
|
||||||
|
common.register_opts(CONF)
|
||||||
|
client.register_opts(CONF)
|
||||||
|
|
||||||
|
# plugins
|
||||||
|
plugin_common.register_opts(CONF)
|
||||||
|
plugin_email.register_opts(CONF)
|
||||||
|
|
||||||
|
|
||||||
|
def set_lib_defaults():
|
||||||
|
"""Update default value for configuration options from other namespace.
|
||||||
|
Example, oslo lib config options. This is needed for
|
||||||
|
config generator tool to pick these default value changes.
|
||||||
|
https://docs.openstack.org/oslo.config/latest/cli/
|
||||||
|
generator.html#modifying-defaults-from-other-namespaces
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Update default value of oslo_log default_log_levels and
|
||||||
|
# logging_context_format_string config option.
|
||||||
|
set_log_defaults()
|
||||||
|
|
||||||
|
|
||||||
|
def set_log_defaults():
|
||||||
|
# logging.set_defaults(default_log_levels=logging.get_default_log_levels())
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def conf_to_dict():
|
||||||
|
"""Convert the CONF options to a single level dictionary."""
|
||||||
|
entries = {}
|
||||||
|
|
||||||
|
def _sanitize(opt, value):
|
||||||
|
"""Obfuscate values of options declared secret."""
|
||||||
|
return value if not opt.secret else "*" * 4
|
||||||
|
|
||||||
|
for opt_name in sorted(CONF._opts):
|
||||||
|
opt = CONF._get_opt_info(opt_name)["opt"]
|
||||||
|
val = str(_sanitize(opt, getattr(CONF, opt_name)))
|
||||||
|
entries[str(opt)] = val
|
||||||
|
|
||||||
|
for group_name in list(CONF._groups):
|
||||||
|
group_attr = CONF.GroupAttr(CONF, CONF._get_group(group_name))
|
||||||
|
for opt_name in sorted(CONF._groups[group_name]._opts):
|
||||||
|
opt = CONF._get_opt_info(opt_name, group_name)["opt"]
|
||||||
|
val = str(_sanitize(opt, getattr(group_attr, opt_name)))
|
||||||
|
gname_opt_name = f"{group_name}.{opt_name}"
|
||||||
|
entries[gname_opt_name] = val
|
||||||
|
|
||||||
|
return entries
|
|
@ -0,0 +1,102 @@
|
||||||
|
"""
|
||||||
|
The options for logging setup
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_LOGIN = "NOCALL"
|
||||||
|
|
||||||
|
aprs_group = cfg.OptGroup(
|
||||||
|
name="aprs_network",
|
||||||
|
title="APRS-IS Network settings",
|
||||||
|
)
|
||||||
|
kiss_serial_group = cfg.OptGroup(
|
||||||
|
name="kiss_serial",
|
||||||
|
title="KISS Serial device connection",
|
||||||
|
)
|
||||||
|
kiss_tcp_group = cfg.OptGroup(
|
||||||
|
name="kiss_tcp",
|
||||||
|
title="KISS TCP/IP Device connection",
|
||||||
|
)
|
||||||
|
aprs_opts = [
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"enabled",
|
||||||
|
default=True,
|
||||||
|
help="Set enabled to False if there is no internet connectivity."
|
||||||
|
"This is useful for a direwolf KISS aprs connection only.",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"login",
|
||||||
|
default=DEFAULT_LOGIN,
|
||||||
|
help="APRS Username",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"password",
|
||||||
|
secret=True,
|
||||||
|
help="APRS Password "
|
||||||
|
"Get the passcode for your callsign here: "
|
||||||
|
"https://apps.magicbug.co.uk/passcode",
|
||||||
|
),
|
||||||
|
cfg.HostnameOpt(
|
||||||
|
"host",
|
||||||
|
default="noam.aprs2.net",
|
||||||
|
help="The APRS-IS hostname",
|
||||||
|
),
|
||||||
|
cfg.PortOpt(
|
||||||
|
"port",
|
||||||
|
default=14580,
|
||||||
|
help="APRS-IS port",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
kiss_serial_opts = [
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"enabled",
|
||||||
|
default=False,
|
||||||
|
help="Enable Serial KISS interface connection.",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"device",
|
||||||
|
help="Serial Device file to use. /dev/ttyS0",
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
"baudrate",
|
||||||
|
default=9600,
|
||||||
|
help="The Serial device baud rate for communication",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
kiss_tcp_opts = [
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"enabled",
|
||||||
|
default=False,
|
||||||
|
help="Enable Serial KISS interface connection.",
|
||||||
|
),
|
||||||
|
cfg.HostnameOpt(
|
||||||
|
"host",
|
||||||
|
help="The KISS TCP Host to connect to.",
|
||||||
|
),
|
||||||
|
cfg.PortOpt(
|
||||||
|
"port",
|
||||||
|
default=8001,
|
||||||
|
help="The KISS TCP/IP network port",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(config):
|
||||||
|
config.register_group(aprs_group)
|
||||||
|
config.register_opts(aprs_opts, group=aprs_group)
|
||||||
|
config.register_group(kiss_serial_group)
|
||||||
|
config.register_group(kiss_tcp_group)
|
||||||
|
config.register_opts(kiss_serial_opts, group=kiss_serial_group)
|
||||||
|
config.register_opts(kiss_tcp_opts, group=kiss_tcp_group)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
return {
|
||||||
|
aprs_group.name: aprs_opts,
|
||||||
|
kiss_serial_group.name: kiss_serial_opts,
|
||||||
|
kiss_tcp_group.name: kiss_tcp_opts,
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
APRSD_DEFAULT_MAGIC_WORD = "CHANGEME!!!"
|
||||||
|
|
||||||
|
admin_group = cfg.OptGroup(
|
||||||
|
name="admin",
|
||||||
|
title="Admin web interface settings",
|
||||||
|
)
|
||||||
|
watch_list_group = cfg.OptGroup(
|
||||||
|
name="watch_list",
|
||||||
|
title="Watch List settings",
|
||||||
|
)
|
||||||
|
rpc_group = cfg.OptGroup(
|
||||||
|
name="rpc_settings",
|
||||||
|
title="RPC Settings for admin <--> web",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
aprsd_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"callsign",
|
||||||
|
required=True,
|
||||||
|
help="Callsign to use for messages sent by APRSD",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"enable_save",
|
||||||
|
default=True,
|
||||||
|
help="Enable saving of watch list, packet tracker between restarts.",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"save_location",
|
||||||
|
default="~/.config/aprsd",
|
||||||
|
help="Save location for packet tracking files.",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"trace_enabled",
|
||||||
|
default=False,
|
||||||
|
help="Enable code tracing",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"units",
|
||||||
|
default="imperial",
|
||||||
|
help="Units for display, imperial or metric",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
watch_list_opts = [
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"enabled",
|
||||||
|
default=False,
|
||||||
|
help="Enable the watch list feature. Still have to enable "
|
||||||
|
"the correct plugin. Built-in plugin to use is "
|
||||||
|
"aprsd.plugins.notify.NotifyPlugin",
|
||||||
|
),
|
||||||
|
cfg.ListOpt(
|
||||||
|
"callsigns",
|
||||||
|
help="Callsigns to watch for messsages",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"alert_callsign",
|
||||||
|
help="The Ham Callsign to send messages to for watch list alerts.",
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
"packet_keep_count",
|
||||||
|
default=10,
|
||||||
|
help="The number of packets to store.",
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
"alert_time_seconds",
|
||||||
|
default=3600,
|
||||||
|
help="Time to wait before alert is sent on new message for "
|
||||||
|
"users in callsigns.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
admin_opts = [
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"web_enabled",
|
||||||
|
default=False,
|
||||||
|
help="Enable the Admin Web Interface",
|
||||||
|
),
|
||||||
|
cfg.IPOpt(
|
||||||
|
"web_ip",
|
||||||
|
default="0.0.0.0",
|
||||||
|
help="The ip address to listen on",
|
||||||
|
),
|
||||||
|
cfg.PortOpt(
|
||||||
|
"web_port",
|
||||||
|
default=8001,
|
||||||
|
help="The port to listen on",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"user",
|
||||||
|
default="admin",
|
||||||
|
help="The admin user for the admin web interface",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"password",
|
||||||
|
secret=True,
|
||||||
|
help="Admin interface password",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
rpc_opts = [
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"enabled",
|
||||||
|
default=True,
|
||||||
|
help="Enable RPC calls",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"ip",
|
||||||
|
default="localhost",
|
||||||
|
help="The ip address to listen on",
|
||||||
|
),
|
||||||
|
cfg.PortOpt(
|
||||||
|
"port",
|
||||||
|
default=18861,
|
||||||
|
help="The port to listen on",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"magic_word",
|
||||||
|
default=APRSD_DEFAULT_MAGIC_WORD,
|
||||||
|
help="Magic word to authenticate requests between client/server",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
enabled_plugins_opts = [
|
||||||
|
cfg.ListOpt(
|
||||||
|
"enabled_plugins",
|
||||||
|
default=[
|
||||||
|
"aprsd.plugins.email.EmailPlugin",
|
||||||
|
"aprsd.plugins.fortune.FortunePlugin",
|
||||||
|
"aprsd.plugins.location.LocationPlugin",
|
||||||
|
"aprsd.plugins.ping.PingPlugin",
|
||||||
|
"aprsd.plugins.query.QueryPlugin",
|
||||||
|
"aprsd.plugins.time.TimePlugin",
|
||||||
|
"aprsd.plugins.weather.OWMWeatherPlugin",
|
||||||
|
"aprsd.plugins.version.VersionPlugin",
|
||||||
|
],
|
||||||
|
help="Comma separated list of enabled plugins for APRSD."
|
||||||
|
"To enable installed external plugins add them here."
|
||||||
|
"The full python path to the class name must be used",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(config):
|
||||||
|
config.register_opts(aprsd_opts)
|
||||||
|
config.register_opts(enabled_plugins_opts)
|
||||||
|
config.register_group(admin_group)
|
||||||
|
config.register_opts(admin_opts, group=admin_group)
|
||||||
|
config.register_group(watch_list_group)
|
||||||
|
config.register_opts(watch_list_opts, group=watch_list_group)
|
||||||
|
config.register_group(rpc_group)
|
||||||
|
config.register_opts(rpc_opts, group=rpc_group)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
return {
|
||||||
|
"DEFAULT": (aprsd_opts + enabled_plugins_opts),
|
||||||
|
admin_group.name: admin_opts,
|
||||||
|
watch_list_group.name: watch_list_opts,
|
||||||
|
rpc_group.name: rpc_opts,
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
"""
|
||||||
|
The options for logging setup
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
LOG_LEVELS = {
|
||||||
|
"CRITICAL": logging.CRITICAL,
|
||||||
|
"ERROR": logging.ERROR,
|
||||||
|
"WARNING": logging.WARNING,
|
||||||
|
"INFO": logging.INFO,
|
||||||
|
"DEBUG": logging.DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
|
||||||
|
DEFAULT_LOG_FORMAT = (
|
||||||
|
"[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s]"
|
||||||
|
" %(message)s - [%(pathname)s:%(lineno)d]"
|
||||||
|
)
|
||||||
|
|
||||||
|
logging_group = cfg.OptGroup(
|
||||||
|
name="logging",
|
||||||
|
title="Logging options",
|
||||||
|
)
|
||||||
|
logging_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"date_format",
|
||||||
|
default=DEFAULT_DATE_FORMAT,
|
||||||
|
help="Date format for log entries",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"rich_logging",
|
||||||
|
default=True,
|
||||||
|
help="Enable Rich logging",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"logfile",
|
||||||
|
default=None,
|
||||||
|
help="File to log to",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"logformat",
|
||||||
|
default=DEFAULT_LOG_FORMAT,
|
||||||
|
help="Log file format, unless rich_logging enabled.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(config):
|
||||||
|
config.register_group(logging_group)
|
||||||
|
config.register_opts(logging_opts, group=logging_group)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
return {
|
||||||
|
logging_group.name: (
|
||||||
|
logging_opts
|
||||||
|
),
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Copyright 2015 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is the single point of entry to generate the sample configuration
|
||||||
|
file for Nova. It collects all the necessary info from the other modules
|
||||||
|
in this package. It is assumed that:
|
||||||
|
|
||||||
|
* every other module in this package has a 'list_opts' function which
|
||||||
|
return a dict where
|
||||||
|
* the keys are strings which are the group names
|
||||||
|
* the value of each key is a list of config options for that group
|
||||||
|
* the nova.conf package doesn't have further packages with config options
|
||||||
|
* this module is only used in the context of sample file generation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
|
||||||
|
LIST_OPTS_FUNC_NAME = "list_opts"
|
||||||
|
|
||||||
|
|
||||||
|
def _tupleize(dct):
|
||||||
|
"""Take the dict of options and convert to the 2-tuple format."""
|
||||||
|
return [(key, val) for key, val in dct.items()]
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
opts = collections.defaultdict(list)
|
||||||
|
module_names = _list_module_names()
|
||||||
|
imported_modules = _import_modules(module_names)
|
||||||
|
_append_config_options(imported_modules, opts)
|
||||||
|
return _tupleize(opts)
|
||||||
|
|
||||||
|
|
||||||
|
def _list_module_names():
|
||||||
|
module_names = []
|
||||||
|
package_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]):
|
||||||
|
if modname == "opts" or ispkg:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
module_names.append(modname)
|
||||||
|
return module_names
|
||||||
|
|
||||||
|
|
||||||
|
def _import_modules(module_names):
|
||||||
|
imported_modules = []
|
||||||
|
for modname in module_names:
|
||||||
|
mod = importlib.import_module("aprsd.conf." + modname)
|
||||||
|
if not hasattr(mod, LIST_OPTS_FUNC_NAME):
|
||||||
|
msg = "The module 'aprsd.conf.%s' should have a '%s' "\
|
||||||
|
"function which returns the config options." % \
|
||||||
|
(modname, LIST_OPTS_FUNC_NAME)
|
||||||
|
raise Exception(msg)
|
||||||
|
else:
|
||||||
|
imported_modules.append(mod)
|
||||||
|
return imported_modules
|
||||||
|
|
||||||
|
|
||||||
|
def _append_config_options(imported_modules, config_options):
|
||||||
|
for mod in imported_modules:
|
||||||
|
configs = mod.list_opts()
|
||||||
|
for key, val in configs.items():
|
||||||
|
config_options[key].extend(val)
|
|
@ -0,0 +1,83 @@
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
aprsfi_group = cfg.OptGroup(
|
||||||
|
name="aprs_fi",
|
||||||
|
title="APRS.FI website settings",
|
||||||
|
)
|
||||||
|
query_group = cfg.OptGroup(
|
||||||
|
name="query_plugin",
|
||||||
|
title="Options for the Query Plugin",
|
||||||
|
)
|
||||||
|
avwx_group = cfg.OptGroup(
|
||||||
|
name="avwx_plugin",
|
||||||
|
title="Options for the AVWXWeatherPlugin",
|
||||||
|
)
|
||||||
|
owm_wx_group = cfg.OptGroup(
|
||||||
|
name="owm_weather_plugin",
|
||||||
|
title="Options for the OWMWeatherPlugin",
|
||||||
|
)
|
||||||
|
|
||||||
|
aprsfi_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"apiKey",
|
||||||
|
help="Get the apiKey from your aprs.fi account here:"
|
||||||
|
"http://aprs.fi/account",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
query_plugin_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"callsign",
|
||||||
|
help="The Ham callsign to allow access to the query plugin from RF.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
owm_wx_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"apiKey",
|
||||||
|
help="OWMWeatherPlugin api key to OpenWeatherMap's API."
|
||||||
|
"This plugin uses the openweathermap API to fetch"
|
||||||
|
"location and weather information."
|
||||||
|
"To use this plugin you need to get an openweathermap"
|
||||||
|
"account and apikey."
|
||||||
|
"https://home.openweathermap.org/api_keys",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
avwx_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"apiKey",
|
||||||
|
help="avwx-api is an opensource project that has"
|
||||||
|
"a hosted service here: https://avwx.rest/"
|
||||||
|
"You can launch your own avwx-api in a container"
|
||||||
|
"by cloning the githug repo here:"
|
||||||
|
"https://github.com/avwx-rest/AVWX-API",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"base_url",
|
||||||
|
default="https://avwx.rest",
|
||||||
|
help="The base url for the avwx API. If you are hosting your own"
|
||||||
|
"Here is where you change the url to point to yours.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(config):
|
||||||
|
config.register_group(aprsfi_group)
|
||||||
|
config.register_opts(aprsfi_opts, group=aprsfi_group)
|
||||||
|
config.register_group(query_group)
|
||||||
|
config.register_opts(query_plugin_opts, group=query_group)
|
||||||
|
config.register_group(owm_wx_group)
|
||||||
|
config.register_opts(owm_wx_opts, group=owm_wx_group)
|
||||||
|
config.register_group(avwx_group)
|
||||||
|
config.register_opts(avwx_opts, group=avwx_group)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
return {
|
||||||
|
aprsfi_group.name: aprsfi_opts,
|
||||||
|
query_group.name: query_plugin_opts,
|
||||||
|
owm_wx_group.name: owm_wx_opts,
|
||||||
|
avwx_group.name: avwx_opts,
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
email_group = cfg.OptGroup(
|
||||||
|
name="email_plugin",
|
||||||
|
title="Options for the APRSD Email plugin",
|
||||||
|
)
|
||||||
|
|
||||||
|
email_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"callsign",
|
||||||
|
required=True,
|
||||||
|
help="(Required) Callsign to validate for doing email commands."
|
||||||
|
"Only this callsign can check email. This is also where the "
|
||||||
|
"email notifications for new emails will be sent.",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"enabled",
|
||||||
|
default=False,
|
||||||
|
help="Enable the Email plugin?",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"debug",
|
||||||
|
default=False,
|
||||||
|
help="Enable the Email plugin Debugging?",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
email_imap_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"imap_login",
|
||||||
|
help="Login username/email for IMAP server",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"imap_password",
|
||||||
|
secret=True,
|
||||||
|
help="Login password for IMAP server",
|
||||||
|
),
|
||||||
|
cfg.HostnameOpt(
|
||||||
|
"imap_host",
|
||||||
|
help="Hostname/IP of the IMAP server",
|
||||||
|
),
|
||||||
|
cfg.PortOpt(
|
||||||
|
"imap_port",
|
||||||
|
default=993,
|
||||||
|
help="Port to use for IMAP server",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"imap_use_ssl",
|
||||||
|
default=True,
|
||||||
|
help="Use SSL for connection to IMAP Server",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
email_smtp_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"smtp_login",
|
||||||
|
help="Login username/email for SMTP server",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"smtp_password",
|
||||||
|
secret=True,
|
||||||
|
help="Login password for SMTP server",
|
||||||
|
),
|
||||||
|
cfg.HostnameOpt(
|
||||||
|
"smtp_host",
|
||||||
|
help="Hostname/IP of the SMTP server",
|
||||||
|
),
|
||||||
|
cfg.PortOpt(
|
||||||
|
"smtp_port",
|
||||||
|
default=465,
|
||||||
|
help="Port to use for SMTP server",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"smtp_use_ssl",
|
||||||
|
default=True,
|
||||||
|
help="Use SSL for connection to SMTP Server",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
email_shortcuts_opts = [
|
||||||
|
cfg.ListOpt(
|
||||||
|
"email_shortcuts",
|
||||||
|
help="List of email shortcuts for checking/sending email "
|
||||||
|
"For Exmaple: wb=walt@walt.com,cl=cl@cl.com\n"
|
||||||
|
"Means use 'wb' to send an email to walt@walt.com",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
ALL_OPTS = (
|
||||||
|
email_opts
|
||||||
|
+ email_imap_opts
|
||||||
|
+ email_smtp_opts
|
||||||
|
+ email_shortcuts_opts
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(config):
|
||||||
|
config.register_group(email_group)
|
||||||
|
config.register_opts(ALL_OPTS, group=email_group)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
return {
|
||||||
|
email_group.name: ALL_OPTS,
|
||||||
|
}
|
404
aprsd/config.py
404
aprsd/config.py
|
@ -1,404 +0,0 @@
|
||||||
import collections
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import click
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from aprsd import exception, utils
|
|
||||||
|
|
||||||
|
|
||||||
home = str(Path.home())
|
|
||||||
DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
|
|
||||||
DEFAULT_SAVE_FILE = f"{home}/.config/aprsd/aprsd.p"
|
|
||||||
DEFAULT_CONFIG_FILE = f"{home}/.config/aprsd/aprsd.yml"
|
|
||||||
|
|
||||||
LOG_LEVELS = {
|
|
||||||
"CRITICAL": logging.CRITICAL,
|
|
||||||
"ERROR": logging.ERROR,
|
|
||||||
"WARNING": logging.WARNING,
|
|
||||||
"INFO": logging.INFO,
|
|
||||||
"DEBUG": logging.DEBUG,
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
|
|
||||||
DEFAULT_LOG_FORMAT = (
|
|
||||||
"[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s]"
|
|
||||||
" %(message)s - [%(pathname)s:%(lineno)d]"
|
|
||||||
)
|
|
||||||
|
|
||||||
QUEUE_DATE_FORMAT = "[%m/%d/%Y] [%I:%M:%S %p]"
|
|
||||||
QUEUE_LOG_FORMAT = (
|
|
||||||
"%(asctime)s [%(threadName)-20.20s] [%(levelname)-5.5s]"
|
|
||||||
" %(message)s - [%(pathname)s:%(lineno)d]"
|
|
||||||
)
|
|
||||||
|
|
||||||
CORE_MESSAGE_PLUGINS = [
|
|
||||||
"aprsd.plugins.email.EmailPlugin",
|
|
||||||
"aprsd.plugins.fortune.FortunePlugin",
|
|
||||||
"aprsd.plugins.location.LocationPlugin",
|
|
||||||
"aprsd.plugins.ping.PingPlugin",
|
|
||||||
"aprsd.plugins.query.QueryPlugin",
|
|
||||||
"aprsd.plugins.time.TimePlugin",
|
|
||||||
"aprsd.plugins.weather.USWeatherPlugin",
|
|
||||||
"aprsd.plugins.version.VersionPlugin",
|
|
||||||
]
|
|
||||||
|
|
||||||
CORE_NOTIFY_PLUGINS = [
|
|
||||||
"aprsd.plugins.notify.NotifySeenPlugin",
|
|
||||||
]
|
|
||||||
|
|
||||||
ALL_PLUGINS = []
|
|
||||||
for i in CORE_MESSAGE_PLUGINS:
|
|
||||||
ALL_PLUGINS.append(i)
|
|
||||||
for i in CORE_NOTIFY_PLUGINS:
|
|
||||||
ALL_PLUGINS.append(i)
|
|
||||||
|
|
||||||
# an example of what should be in the ~/.aprsd/config.yml
|
|
||||||
DEFAULT_CONFIG_DICT = {
|
|
||||||
"ham": {"callsign": "NOCALL"},
|
|
||||||
"aprs": {
|
|
||||||
"enabled": True,
|
|
||||||
# Only used as the login for aprsis.
|
|
||||||
"login": "CALLSIGN",
|
|
||||||
"password": "00000",
|
|
||||||
"host": "rotate.aprs2.net",
|
|
||||||
"port": 14580,
|
|
||||||
},
|
|
||||||
"kiss": {
|
|
||||||
"tcp": {
|
|
||||||
"enabled": False,
|
|
||||||
"host": "direwolf.ip.address",
|
|
||||||
"port": "8001",
|
|
||||||
},
|
|
||||||
"serial": {
|
|
||||||
"enabled": False,
|
|
||||||
"device": "/dev/ttyS0",
|
|
||||||
"baudrate": 9600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"aprsd": {
|
|
||||||
# Callsign to use for all packets to/from aprsd instance
|
|
||||||
# regardless of the client (aprsis vs kiss)
|
|
||||||
"callsign": "NOCALL",
|
|
||||||
"logfile": "/tmp/aprsd.log",
|
|
||||||
"logformat": DEFAULT_LOG_FORMAT,
|
|
||||||
"dateformat": DEFAULT_DATE_FORMAT,
|
|
||||||
"save_location": DEFAULT_CONFIG_DIR,
|
|
||||||
"rich_logging": True,
|
|
||||||
"trace": False,
|
|
||||||
"enabled_plugins": ALL_PLUGINS,
|
|
||||||
"units": "imperial",
|
|
||||||
"watch_list": {
|
|
||||||
"enabled": False,
|
|
||||||
# Who gets the alert?
|
|
||||||
"alert_callsign": "NOCALL",
|
|
||||||
# 43200 is 12 hours
|
|
||||||
"alert_time_seconds": 43200,
|
|
||||||
# How many packets to save in a ring Buffer
|
|
||||||
# for a particular callsign
|
|
||||||
"packet_keep_count": 10,
|
|
||||||
"callsigns": [],
|
|
||||||
},
|
|
||||||
"web": {
|
|
||||||
"enabled": True,
|
|
||||||
"logging_enabled": True,
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"port": 8001,
|
|
||||||
"users": {
|
|
||||||
"admin": "password-here",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"enabled": True,
|
|
||||||
"shortcuts": {
|
|
||||||
"aa": "5551239999@vtext.com",
|
|
||||||
"cl": "craiglamparter@somedomain.org",
|
|
||||||
"wb": "555309@vtext.com",
|
|
||||||
},
|
|
||||||
"smtp": {
|
|
||||||
"login": "SMTP_USERNAME",
|
|
||||||
"password": "SMTP_PASSWORD",
|
|
||||||
"host": "smtp.gmail.com",
|
|
||||||
"port": 465,
|
|
||||||
"use_ssl": False,
|
|
||||||
"debug": False,
|
|
||||||
},
|
|
||||||
"imap": {
|
|
||||||
"login": "IMAP_USERNAME",
|
|
||||||
"password": "IMAP_PASSWORD",
|
|
||||||
"host": "imap.gmail.com",
|
|
||||||
"port": 993,
|
|
||||||
"use_ssl": True,
|
|
||||||
"debug": False,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"services": {
|
|
||||||
"aprs.fi": {"apiKey": "APIKEYVALUE"},
|
|
||||||
"openweathermap": {"apiKey": "APIKEYVALUE"},
|
|
||||||
"opencagedata": {"apiKey": "APIKEYVALUE"},
|
|
||||||
"avwx": {"base_url": "http://host:port", "apiKey": "APIKEYVALUE"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Config(collections.UserDict):
|
|
||||||
def _get(self, d, keys, default=None):
|
|
||||||
"""
|
|
||||||
Example:
|
|
||||||
d = {'meta': {'status': 'OK', 'status_code': 200}}
|
|
||||||
_get(d, ['meta', 'status_code']) # => 200
|
|
||||||
_get(d, ['garbage', 'status_code']) # => None
|
|
||||||
_get(d, ['meta', 'garbage'], default='-') # => '-'
|
|
||||||
|
|
||||||
"""
|
|
||||||
if type(keys) is str and "." in keys:
|
|
||||||
keys = keys.split(".")
|
|
||||||
|
|
||||||
assert type(keys) is list
|
|
||||||
if d is None:
|
|
||||||
return default
|
|
||||||
|
|
||||||
if not keys:
|
|
||||||
return d
|
|
||||||
|
|
||||||
if type(d) is str:
|
|
||||||
return default
|
|
||||||
|
|
||||||
return self._get(d.get(keys[0]), keys[1:], default)
|
|
||||||
|
|
||||||
def get(self, path, default=None):
|
|
||||||
return self._get(self.data, path, default=default)
|
|
||||||
|
|
||||||
def exists(self, path):
|
|
||||||
"""See if a conf value exists."""
|
|
||||||
test = "-3.14TEST41.3-"
|
|
||||||
return self.get(path, default=test) != test
|
|
||||||
|
|
||||||
def check_option(self, path, default_fail=None):
|
|
||||||
"""Make sure the config option doesn't have default value."""
|
|
||||||
if not self.exists(path):
|
|
||||||
if type(path) is list:
|
|
||||||
path = ".".join(path)
|
|
||||||
raise exception.MissingConfigOptionException(path)
|
|
||||||
|
|
||||||
val = self.get(path)
|
|
||||||
if val == default_fail:
|
|
||||||
# We have to fail and bail if the user hasn't edited
|
|
||||||
# this config option.
|
|
||||||
raise exception.ConfigOptionBogusDefaultException(
|
|
||||||
path, default_fail,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def add_config_comments(raw_yaml):
|
|
||||||
end_idx = utils.end_substr(raw_yaml, "ham:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = utils.insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # Callsign that owns this instance of APRSD.",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
end_idx = utils.end_substr(raw_yaml, "aprsd:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = utils.insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # Callsign to use for all APRSD Packets as the to/from."
|
|
||||||
"\n # regardless of client type (aprsis vs tcpkiss vs serial)",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
end_idx = utils.end_substr(raw_yaml, "aprs:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = utils.insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # Set enabled to False if there is no internet connectivity."
|
|
||||||
"\n # This is useful for a direwolf KISS aprs connection only. "
|
|
||||||
"\n"
|
|
||||||
"\n # Get the passcode for your callsign here: "
|
|
||||||
"\n # https://apps.magicbug.co.uk/passcode",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = utils.end_substr(raw_yaml, "aprs.fi:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = utils.insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # Get the apiKey from your aprs.fi account here: "
|
|
||||||
"\n # http://aprs.fi/account",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = utils.end_substr(raw_yaml, "opencagedata:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = utils.insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # (Optional for TimeOpenCageDataPlugin) "
|
|
||||||
"\n # Get the apiKey from your opencagedata account here: "
|
|
||||||
"\n # https://opencagedata.com/dashboard#api-keys",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = utils.end_substr(raw_yaml, "openweathermap:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = utils.insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # (Optional for OWMWeatherPlugin) "
|
|
||||||
"\n # Get the apiKey from your "
|
|
||||||
"\n # openweathermap account here: "
|
|
||||||
"\n # https://home.openweathermap.org/api_keys",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = utils.end_substr(raw_yaml, "avwx:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = utils.insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # (Optional for AVWXWeatherPlugin) "
|
|
||||||
"\n # Use hosted avwx-api here: https://avwx.rest "
|
|
||||||
"\n # or deploy your own from here: "
|
|
||||||
"\n # https://github.com/avwx-rest/avwx-api",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
return raw_yaml
|
|
||||||
|
|
||||||
|
|
||||||
def dump_default_cfg():
|
|
||||||
return add_config_comments(
|
|
||||||
yaml.dump(
|
|
||||||
DEFAULT_CONFIG_DICT,
|
|
||||||
indent=4,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_default_config():
|
|
||||||
"""Create a default config file."""
|
|
||||||
# make sure the directory location exists
|
|
||||||
config_file_expanded = os.path.expanduser(DEFAULT_CONFIG_FILE)
|
|
||||||
config_dir = os.path.dirname(config_file_expanded)
|
|
||||||
if not os.path.exists(config_dir):
|
|
||||||
click.echo(f"Config dir '{config_dir}' doesn't exist, creating.")
|
|
||||||
utils.mkdir_p(config_dir)
|
|
||||||
with open(config_file_expanded, "w+") as cf:
|
|
||||||
cf.write(dump_default_cfg())
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(config_file):
|
|
||||||
"""This tries to read the yaml config from <config_file>."""
|
|
||||||
config_file_expanded = os.path.expanduser(config_file)
|
|
||||||
if os.path.exists(config_file_expanded):
|
|
||||||
with open(config_file_expanded) as stream:
|
|
||||||
config = yaml.load(stream, Loader=yaml.FullLoader)
|
|
||||||
return Config(config)
|
|
||||||
else:
|
|
||||||
if config_file == DEFAULT_CONFIG_FILE:
|
|
||||||
click.echo(
|
|
||||||
f"{config_file_expanded} is missing, creating config file",
|
|
||||||
)
|
|
||||||
create_default_config()
|
|
||||||
msg = (
|
|
||||||
"Default config file created at {}. Please edit with your "
|
|
||||||
"settings.".format(config_file)
|
|
||||||
)
|
|
||||||
click.echo(msg)
|
|
||||||
else:
|
|
||||||
# The user provided a config file path different from the
|
|
||||||
# Default, so we won't try and create it, just bitch and bail.
|
|
||||||
msg = f"Custom config file '{config_file}' is missing."
|
|
||||||
click.echo(msg)
|
|
||||||
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
|
|
||||||
# This method tries to parse the config yaml file
|
|
||||||
# and consume the settings.
|
|
||||||
# If the required params don't exist,
|
|
||||||
# it will look in the environment
|
|
||||||
def parse_config(config_file):
|
|
||||||
config = get_config(config_file)
|
|
||||||
|
|
||||||
def fail(msg):
|
|
||||||
click.echo(msg)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
def check_option(config, path, default_fail=None):
|
|
||||||
try:
|
|
||||||
config.check_option(path, default_fail=default_fail)
|
|
||||||
except Exception as ex:
|
|
||||||
fail(repr(ex))
|
|
||||||
else:
|
|
||||||
return config
|
|
||||||
|
|
||||||
# special check here to make sure user has edited the config file
|
|
||||||
# and changed the ham callsign
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
"ham.callsign",
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["ham"]["callsign"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
"aprsd.callsign",
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["callsign"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure they change the admin password
|
|
||||||
if config.get("aprsd.web.enabled") is True:
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "web", "users", "admin"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["web"]["users"]["admin"],
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.get("aprsd.watch_list.enabled") is True:
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "watch_list", "alert_callsign"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["watch_list"]["alert_callsign"],
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.get("aprsd.email.enabled") is True:
|
|
||||||
# Check IMAP server settings
|
|
||||||
check_option(config, ["aprsd", "email", "imap", "host"])
|
|
||||||
check_option(config, ["aprsd", "email", "imap", "port"])
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "imap", "login"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["imap"]["login"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "imap", "password"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["imap"]["password"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check SMTP server settings
|
|
||||||
check_option(config, ["aprsd", "email", "smtp", "host"])
|
|
||||||
check_option(config, ["aprsd", "email", "smtp", "port"])
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "smtp", "login"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["smtp"]["login"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "smtp", "password"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["smtp"]["password"],
|
|
||||||
)
|
|
||||||
|
|
||||||
return config
|
|
667
aprsd/flask.py
667
aprsd/flask.py
|
@ -2,100 +2,42 @@ import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import aprslib
|
|
||||||
from aprslib.exceptions import LoginError
|
|
||||||
import flask
|
import flask
|
||||||
from flask import request
|
|
||||||
from flask.logging import default_handler
|
from flask.logging import default_handler
|
||||||
import flask_classful
|
import flask_classful
|
||||||
from flask_httpauth import HTTPBasicAuth
|
from flask_httpauth import HTTPBasicAuth
|
||||||
from flask_socketio import Namespace, SocketIO
|
from flask_socketio import Namespace, SocketIO
|
||||||
|
from oslo_config import cfg
|
||||||
|
import rpyc
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
import wrapt
|
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import client
|
from aprsd import cli_helper, client, conf, packets, plugin, threads
|
||||||
from aprsd import config as aprsd_config
|
from aprsd.conf import common
|
||||||
from aprsd import packets, plugin, stats, threads, utils
|
|
||||||
from aprsd.clients import aprsis
|
|
||||||
from aprsd.logging import log
|
|
||||||
from aprsd.logging import rich as aprsd_logging
|
from aprsd.logging import rich as aprsd_logging
|
||||||
from aprsd.threads import tx
|
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
auth = HTTPBasicAuth()
|
auth = HTTPBasicAuth()
|
||||||
users = None
|
users = None
|
||||||
|
app = None
|
||||||
|
|
||||||
|
|
||||||
class SentMessages:
|
class AuthSocketStream(rpyc.SocketStream):
|
||||||
_instance = None
|
"""Used to authenitcate the RPC stream to remote."""
|
||||||
lock = threading.Lock()
|
|
||||||
|
|
||||||
msgs = {}
|
@classmethod
|
||||||
|
def connect(cls, *args, authorizer=None, **kwargs):
|
||||||
|
stream_obj = super().connect(*args, **kwargs)
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
if callable(authorizer):
|
||||||
"""This magic turns this into a singleton."""
|
authorizer(stream_obj.sock)
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super().__new__(cls)
|
|
||||||
# Put any initialization here.
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
return stream_obj
|
||||||
def add(self, packet):
|
|
||||||
self.msgs[packet.msgNo] = self._create(packet.msgNo)
|
|
||||||
self.msgs[packet.msgNo]["from"] = packet.from_call
|
|
||||||
self.msgs[packet.msgNo]["to"] = packet.to_call
|
|
||||||
self.msgs[packet.msgNo]["message"] = packet.message_text.rstrip("\n")
|
|
||||||
packet._build_raw()
|
|
||||||
self.msgs[packet.msgNo]["raw"] = packet.raw.rstrip("\n")
|
|
||||||
|
|
||||||
def _create(self, id):
|
|
||||||
return {
|
|
||||||
"id": id,
|
|
||||||
"ts": time.time(),
|
|
||||||
"ack": False,
|
|
||||||
"from": None,
|
|
||||||
"to": None,
|
|
||||||
"raw": None,
|
|
||||||
"message": None,
|
|
||||||
"status": None,
|
|
||||||
"last_update": None,
|
|
||||||
"reply": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.msgs.keys())
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def get(self, id):
|
|
||||||
if id in self.msgs:
|
|
||||||
return self.msgs[id]
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def get_all(self):
|
|
||||||
return self.msgs
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def set_status(self, id, status):
|
|
||||||
self.msgs[id]["last_update"] = str(datetime.datetime.now())
|
|
||||||
self.msgs[id]["status"] = status
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def ack(self, id):
|
|
||||||
"""The message got an ack!"""
|
|
||||||
self.msgs[id]["last_update"] = str(datetime.datetime.now())
|
|
||||||
self.msgs[id]["ack"] = True
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def reply(self, id, packet):
|
|
||||||
"""We got a packet back from the sent message."""
|
|
||||||
self.msgs[id]["reply"] = packet
|
|
||||||
|
|
||||||
|
|
||||||
# HTTPBasicAuth doesn't work on a class method.
|
# HTTPBasicAuth doesn't work on a class method.
|
||||||
|
@ -109,215 +51,168 @@ def verify_password(username, password):
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
|
||||||
class SendMessageThread(threads.APRSDRXThread):
|
class RPCClient:
|
||||||
"""Thread for sending a message from web."""
|
_instance = None
|
||||||
|
_rpc_client = None
|
||||||
|
|
||||||
aprsis_client = None
|
def __new__(cls, *args, **kwargs):
|
||||||
request = None
|
if cls._instance is None:
|
||||||
got_ack = False
|
cls._instance = super().__new__(cls)
|
||||||
got_reply = False
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, config, info, packet, namespace):
|
def __init__(self):
|
||||||
self.config = config
|
self._check_settings()
|
||||||
self.request = info
|
self.get_rpc_client()
|
||||||
self.packet = packet
|
|
||||||
self.namespace = namespace
|
|
||||||
self.start_time = datetime.datetime.now()
|
|
||||||
msg = "({} -> {}) : {}".format(
|
|
||||||
info["from"],
|
|
||||||
info["to"],
|
|
||||||
info["message"],
|
|
||||||
)
|
|
||||||
super().__init__(f"WEB_SEND_MSG-{msg}")
|
|
||||||
|
|
||||||
def setup_connection(self):
|
def _check_settings(self):
|
||||||
user = self.request["from"]
|
if not CONF.rpc_settings.enabled:
|
||||||
password = self.request["password"]
|
LOG.error("RPC is not enabled, no way to get stats!!")
|
||||||
host = self.config["aprs"].get("host", "rotate.aprs.net")
|
|
||||||
port = self.config["aprs"].get("port", 14580)
|
|
||||||
connected = False
|
|
||||||
backoff = 1
|
|
||||||
while not connected:
|
|
||||||
try:
|
|
||||||
LOG.info("Creating aprslib client")
|
|
||||||
|
|
||||||
aprs_client = aprsis.Aprsdis(
|
if CONF.rpc_settings.magic_word == common.APRSD_DEFAULT_MAGIC_WORD:
|
||||||
user,
|
LOG.warning("You are using the default RPC magic word!!!")
|
||||||
passwd=password,
|
LOG.warning("edit aprsd.conf and change rpc_settings.magic_word")
|
||||||
host=host,
|
|
||||||
port=port,
|
|
||||||
)
|
|
||||||
# Force the logging to be the same
|
|
||||||
aprs_client.logger = LOG
|
|
||||||
aprs_client.connect()
|
|
||||||
connected = True
|
|
||||||
backoff = 1
|
|
||||||
except LoginError as e:
|
|
||||||
LOG.error(f"Failed to login to APRS-IS Server '{e}'")
|
|
||||||
connected = False
|
|
||||||
raise e
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
|
|
||||||
time.sleep(backoff)
|
|
||||||
backoff = backoff * 2
|
|
||||||
continue
|
|
||||||
LOG.debug(f"Logging in to APRS-IS with user '{user}'")
|
|
||||||
return aprs_client
|
|
||||||
|
|
||||||
def run(self):
|
def _rpyc_connect(
|
||||||
LOG.debug("Starting")
|
self, host, port,
|
||||||
from_call = self.request["from"]
|
service=rpyc.VoidService,
|
||||||
to_call = self.request["to"]
|
config={}, ipv6=False,
|
||||||
message = self.request["message"]
|
keepalive=False, authorizer=None,
|
||||||
LOG.info(
|
):
|
||||||
"From: '{}' To: '{}' Send '{}'".format(
|
|
||||||
from_call,
|
|
||||||
to_call,
|
|
||||||
message,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
print(f"Connecting to RPC host {host}:{port}")
|
||||||
try:
|
try:
|
||||||
self.aprs_client = self.setup_connection()
|
s = AuthSocketStream.connect(
|
||||||
except LoginError as e:
|
host, port, ipv6=ipv6, keepalive=keepalive,
|
||||||
f"Failed to setup Connection {e}"
|
authorizer=authorizer,
|
||||||
|
|
||||||
tx.send(
|
|
||||||
self.packet,
|
|
||||||
direct=True,
|
|
||||||
aprs_client=self.aprs_client,
|
|
||||||
)
|
|
||||||
SentMessages().set_status(self.packet.msgNo, "Sent")
|
|
||||||
|
|
||||||
while not self.thread_stop:
|
|
||||||
can_loop = self.loop()
|
|
||||||
if not can_loop:
|
|
||||||
self.stop()
|
|
||||||
threads.APRSDThreadList().remove(self)
|
|
||||||
LOG.debug("Exiting")
|
|
||||||
|
|
||||||
def process_ack_packet(self, packet):
|
|
||||||
global socketio
|
|
||||||
ack_num = packet.msgNo
|
|
||||||
LOG.info(f"We got ack for our sent message {ack_num}")
|
|
||||||
packet.log("RXACK")
|
|
||||||
SentMessages().ack(self.packet.msgNo)
|
|
||||||
stats.APRSDStats().ack_rx_inc()
|
|
||||||
socketio.emit(
|
|
||||||
"ack", SentMessages().get(self.packet.msgNo),
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
if self.request["wait_reply"] == "0" or self.got_reply:
|
|
||||||
# We aren't waiting for a reply, so we can bail
|
|
||||||
self.stop()
|
|
||||||
self.thread_stop = self.aprs_client.thread_stop = True
|
|
||||||
|
|
||||||
def process_our_message_packet(self, packet):
|
|
||||||
global socketio
|
|
||||||
packets.PacketList().rx(packet)
|
|
||||||
stats.APRSDStats().msgs_rx_inc()
|
|
||||||
msg_number = packet.msgNo
|
|
||||||
SentMessages().reply(self.packet.msgNo, packet)
|
|
||||||
SentMessages().set_status(self.packet.msgNo, "Got Reply")
|
|
||||||
socketio.emit(
|
|
||||||
"reply", SentMessages().get(self.packet.msgNo),
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
tx.send(
|
|
||||||
packets.AckPacket(
|
|
||||||
from_call=self.request["from"],
|
|
||||||
to_call=packet.from_call,
|
|
||||||
msgNo=msg_number,
|
|
||||||
),
|
|
||||||
direct=True,
|
|
||||||
aprs_client=self.aprsis_client,
|
|
||||||
)
|
|
||||||
SentMessages().set_status(self.packet.msgNo, "Ack Sent")
|
|
||||||
|
|
||||||
# Now we can exit, since we are done.
|
|
||||||
self.got_reply = True
|
|
||||||
if self.got_ack:
|
|
||||||
self.stop()
|
|
||||||
self.thread_stop = self.aprs_client.thread_stop = True
|
|
||||||
|
|
||||||
def process_packet(self, *args, **kwargs):
|
|
||||||
packet = self._client.decode_packet(*args, **kwargs)
|
|
||||||
packet.log(header="RX Packet")
|
|
||||||
|
|
||||||
if isinstance(packet, packets.AckPacket):
|
|
||||||
self.process_ack_packet(packet)
|
|
||||||
else:
|
|
||||||
self.process_our_message_packet(packet)
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
# we have a general time limit expecting results of
|
|
||||||
# around 120 seconds before we exit
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
start_delta = str(now - self.start_time)
|
|
||||||
delta = utils.parse_delta_str(start_delta)
|
|
||||||
d = datetime.timedelta(**delta)
|
|
||||||
max_timeout = {"hours": 0.0, "minutes": 1, "seconds": 0}
|
|
||||||
max_delta = datetime.timedelta(**max_timeout)
|
|
||||||
if d > max_delta:
|
|
||||||
LOG.error("XXXXXX Haven't completed everything in 60 seconds. BAIL!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.got_ack and self.got_reply:
|
|
||||||
LOG.warning("We got everything already. BAIL")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# This will register a packet consumer with aprslib
|
|
||||||
# When new packets come in the consumer will process
|
|
||||||
# the packet
|
|
||||||
self.aprs_client.consumer(
|
|
||||||
self.process_packet, raw=False, blocking=False,
|
|
||||||
)
|
)
|
||||||
except aprslib.exceptions.ConnectionDrop:
|
return rpyc.utils.factory.connect_stream(s, service, config=config)
|
||||||
LOG.error("Connection dropped.")
|
except ConnectionRefusedError:
|
||||||
return False
|
LOG.error(f"Failed to connect to RPC host {host}")
|
||||||
|
return None
|
||||||
|
|
||||||
return True
|
def get_rpc_client(self):
|
||||||
|
if not self._rpc_client:
|
||||||
|
magic = CONF.rpc_settings.magic_word
|
||||||
|
self._rpc_client = self._rpyc_connect(
|
||||||
|
CONF.rpc_settings.ip,
|
||||||
|
CONF.rpc_settings.port,
|
||||||
|
authorizer=lambda sock: sock.send(magic.encode()),
|
||||||
|
)
|
||||||
|
return self._rpc_client
|
||||||
|
|
||||||
|
def get_stats_dict(self):
|
||||||
|
cl = self.get_rpc_client()
|
||||||
|
result = {}
|
||||||
|
if not cl:
|
||||||
|
return result
|
||||||
|
|
||||||
|
try:
|
||||||
|
rpc_stats_dict = cl.root.get_stats()
|
||||||
|
result = json.loads(rpc_stats_dict)
|
||||||
|
except EOFError:
|
||||||
|
LOG.error("Lost connection to RPC Host")
|
||||||
|
self._rpc_client = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_packet_track(self):
|
||||||
|
cl = self.get_rpc_client()
|
||||||
|
result = None
|
||||||
|
if not cl:
|
||||||
|
return result
|
||||||
|
try:
|
||||||
|
result = cl.root.get_packet_track()
|
||||||
|
except EOFError:
|
||||||
|
LOG.error("Lost connection to RPC Host")
|
||||||
|
self._rpc_client = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_packet_list(self):
|
||||||
|
cl = self.get_rpc_client()
|
||||||
|
result = None
|
||||||
|
if not cl:
|
||||||
|
return result
|
||||||
|
try:
|
||||||
|
result = cl.root.get_packet_list()
|
||||||
|
except EOFError:
|
||||||
|
LOG.error("Lost connection to RPC Host")
|
||||||
|
self._rpc_client = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_watch_list(self):
|
||||||
|
cl = self.get_rpc_client()
|
||||||
|
result = None
|
||||||
|
if not cl:
|
||||||
|
return result
|
||||||
|
try:
|
||||||
|
result = cl.root.get_watch_list()
|
||||||
|
except EOFError:
|
||||||
|
LOG.error("Lost connection to RPC Host")
|
||||||
|
self._rpc_client = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_seen_list(self):
|
||||||
|
cl = self.get_rpc_client()
|
||||||
|
result = None
|
||||||
|
if not cl:
|
||||||
|
return result
|
||||||
|
try:
|
||||||
|
result = cl.root.get_seen_list()
|
||||||
|
except EOFError:
|
||||||
|
LOG.error("Lost connection to RPC Host")
|
||||||
|
self._rpc_client = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_log_entries(self):
|
||||||
|
cl = self.get_rpc_client()
|
||||||
|
result = None
|
||||||
|
if not cl:
|
||||||
|
return result
|
||||||
|
try:
|
||||||
|
result_str = cl.root.get_log_entries()
|
||||||
|
result = json.loads(result_str)
|
||||||
|
except EOFError:
|
||||||
|
LOG.error("Lost connection to RPC Host")
|
||||||
|
self._rpc_client = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class APRSDFlask(flask_classful.FlaskView):
|
class APRSDFlask(flask_classful.FlaskView):
|
||||||
config = None
|
|
||||||
|
|
||||||
def set_config(self, config):
|
def set_config(self):
|
||||||
global users
|
global users
|
||||||
self.config = config
|
|
||||||
self.users = {}
|
self.users = {}
|
||||||
for user in self.config["aprsd"]["web"]["users"]:
|
user = CONF.admin.user
|
||||||
self.users[user] = generate_password_hash(
|
self.users[user] = generate_password_hash(CONF.admin.password)
|
||||||
self.config["aprsd"]["web"]["users"][user],
|
|
||||||
)
|
|
||||||
|
|
||||||
users = self.users
|
users = self.users
|
||||||
|
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def index(self):
|
def index(self):
|
||||||
stats = self._stats()
|
stats = self._stats()
|
||||||
|
print(stats)
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"watch list? {}".format(
|
"watch list? {}".format(
|
||||||
self.config["aprsd"]["watch_list"],
|
CONF.watch_list.callsigns,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
wl = packets.WatchList()
|
wl = RPCClient().get_watch_list()
|
||||||
if wl.is_enabled():
|
if wl and wl.is_enabled():
|
||||||
watch_count = len(wl)
|
watch_count = len(wl)
|
||||||
watch_age = wl.max_delta()
|
watch_age = wl.max_delta()
|
||||||
else:
|
else:
|
||||||
watch_count = 0
|
watch_count = 0
|
||||||
watch_age = 0
|
watch_age = 0
|
||||||
|
|
||||||
sl = packets.SeenList()
|
sl = RPCClient().get_seen_list()
|
||||||
seen_count = len(sl)
|
if sl:
|
||||||
|
seen_count = len(sl)
|
||||||
|
else:
|
||||||
|
seen_count = 0
|
||||||
|
|
||||||
pm = plugin.PluginManager()
|
pm = plugin.PluginManager()
|
||||||
plugins = pm.get_plugins()
|
plugins = pm.get_plugins()
|
||||||
plugin_count = len(plugins)
|
plugin_count = len(plugins)
|
||||||
|
|
||||||
if self.config["aprs"].get("enabled", True):
|
if CONF.aprs_network.enabled:
|
||||||
transport = "aprs-is"
|
transport = "aprs-is"
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
||||||
|
@ -325,33 +220,37 @@ class APRSDFlask(flask_classful.FlaskView):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# We might be connected to a KISS socket?
|
# We might be connected to a KISS socket?
|
||||||
if client.KISSClient.kiss_enabled(self.config):
|
if client.KISSClient.kiss_enabled():
|
||||||
transport = client.KISSClient.transport(self.config)
|
transport = client.KISSClient.transport()
|
||||||
if transport == client.TRANSPORT_TCPKISS:
|
if transport == client.TRANSPORT_TCPKISS:
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
"TCPKISS://{}:{}".format(
|
"TCPKISS://{}:{}".format(
|
||||||
self.config["kiss"]["tcp"]["host"],
|
CONF.kiss_tcp.host,
|
||||||
self.config["kiss"]["tcp"]["port"],
|
CONF.kiss_tcp.port,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif transport == client.TRANSPORT_SERIALKISS:
|
elif transport == client.TRANSPORT_SERIALKISS:
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
"SerialKISS://{}@{} baud".format(
|
"SerialKISS://{}@{} baud".format(
|
||||||
self.config["kiss"]["serial"]["device"],
|
CONF.kiss_serial.device,
|
||||||
self.config["kiss"]["serial"]["baudrate"],
|
CONF.kiss_serial.baudrate,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
stats["transport"] = transport
|
stats["transport"] = transport
|
||||||
stats["aprs_connection"] = aprs_connection
|
stats["aprs_connection"] = aprs_connection
|
||||||
|
entries = conf.conf_to_dict()
|
||||||
|
|
||||||
return flask.render_template(
|
return flask.render_template(
|
||||||
"index.html",
|
"index.html",
|
||||||
initial_stats=stats,
|
initial_stats=stats,
|
||||||
aprs_connection=aprs_connection,
|
aprs_connection=aprs_connection,
|
||||||
callsign=self.config["aprs"]["login"],
|
callsign=CONF.callsign,
|
||||||
version=aprsd.__version__,
|
version=aprsd.__version__,
|
||||||
config_json=json.dumps(self.config.data),
|
config_json=json.dumps(
|
||||||
|
entries, indent=4,
|
||||||
|
sort_keys=True, default=str,
|
||||||
|
),
|
||||||
watch_count=watch_count,
|
watch_count=watch_count,
|
||||||
watch_age=watch_age,
|
watch_age=watch_age,
|
||||||
seen_count=seen_count,
|
seen_count=seen_count,
|
||||||
|
@ -368,32 +267,18 @@ class APRSDFlask(flask_classful.FlaskView):
|
||||||
|
|
||||||
return flask.render_template("messages.html", messages=json.dumps(msgs))
|
return flask.render_template("messages.html", messages=json.dumps(msgs))
|
||||||
|
|
||||||
@auth.login_required
|
|
||||||
def send_message_status(self):
|
|
||||||
LOG.debug(request)
|
|
||||||
msgs = SentMessages()
|
|
||||||
info = msgs.get_all()
|
|
||||||
return json.dumps(info)
|
|
||||||
|
|
||||||
@auth.login_required
|
|
||||||
def send_message(self):
|
|
||||||
LOG.debug(request)
|
|
||||||
if request.method == "GET":
|
|
||||||
return flask.render_template(
|
|
||||||
"send-message.html",
|
|
||||||
callsign=self.config["aprs"]["login"],
|
|
||||||
version=aprsd.__version__,
|
|
||||||
)
|
|
||||||
|
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def packets(self):
|
def packets(self):
|
||||||
packet_list = packets.PacketList().get()
|
packet_list = RPCClient().get_packet_list()
|
||||||
tmp_list = []
|
if packet_list:
|
||||||
for pkt in packet_list:
|
packets = packet_list.get()
|
||||||
tmp_list.append(pkt.json)
|
tmp_list = []
|
||||||
|
for pkt in packets:
|
||||||
|
tmp_list.append(pkt.json)
|
||||||
|
|
||||||
LOG.info(f"PACKETS {tmp_list}")
|
return json.dumps(tmp_list)
|
||||||
return json.dumps(tmp_list)
|
else:
|
||||||
|
return json.dumps([])
|
||||||
|
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def plugins(self):
|
def plugins(self):
|
||||||
|
@ -410,39 +295,69 @@ class APRSDFlask(flask_classful.FlaskView):
|
||||||
return json.dumps({"messages": "saved"})
|
return json.dumps({"messages": "saved"})
|
||||||
|
|
||||||
def _stats(self):
|
def _stats(self):
|
||||||
stats_obj = stats.APRSDStats()
|
track = RPCClient().get_packet_track()
|
||||||
track = packets.PacketTrack()
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
time_format = "%m-%d-%Y %H:%M:%S"
|
time_format = "%m-%d-%Y %H:%M:%S"
|
||||||
|
|
||||||
stats_dict = stats_obj.stats()
|
stats_dict = RPCClient().get_stats_dict()
|
||||||
|
if not stats_dict:
|
||||||
# Convert the watch_list entries to age
|
stats_dict = {
|
||||||
wl = packets.WatchList()
|
"aprsd": {},
|
||||||
new_list = {}
|
"aprs-is": {"server": ""},
|
||||||
for call in wl.get_all():
|
"messages": {
|
||||||
# call_date = datetime.datetime.strptime(
|
"sent": 0,
|
||||||
# str(wl.last_seen(call)),
|
"received": 0,
|
||||||
# "%Y-%m-%d %H:%M:%S.%f",
|
},
|
||||||
# )
|
"email": {
|
||||||
new_list[call] = {
|
"sent": 0,
|
||||||
"last": wl.age(call),
|
"received": 0,
|
||||||
"packets": wl.get(call)["packets"].get(),
|
},
|
||||||
|
"seen_list": {
|
||||||
|
"sent": 0,
|
||||||
|
"received": 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Convert the watch_list entries to age
|
||||||
|
wl = RPCClient().get_watch_list()
|
||||||
|
new_list = {}
|
||||||
|
if wl:
|
||||||
|
for call in wl.get_all():
|
||||||
|
# call_date = datetime.datetime.strptime(
|
||||||
|
# str(wl.last_seen(call)),
|
||||||
|
# "%Y-%m-%d %H:%M:%S.%f",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# We have to convert the RingBuffer to a real list
|
||||||
|
# so that json.dumps works.
|
||||||
|
# pkts = []
|
||||||
|
# for pkt in wl.get(call)["packets"].get():
|
||||||
|
# pkts.append(pkt)
|
||||||
|
|
||||||
|
new_list[call] = {
|
||||||
|
"last": wl.age(call),
|
||||||
|
# "packets": pkts
|
||||||
|
}
|
||||||
|
|
||||||
stats_dict["aprsd"]["watch_list"] = new_list
|
stats_dict["aprsd"]["watch_list"] = new_list
|
||||||
packet_list = packets.PacketList()
|
packet_list = RPCClient().get_packet_list()
|
||||||
rx = packet_list.total_rx()
|
rx = tx = 0
|
||||||
tx = packet_list.total_tx()
|
if packet_list:
|
||||||
|
rx = packet_list.total_rx()
|
||||||
|
tx = packet_list.total_tx()
|
||||||
stats_dict["packets"] = {
|
stats_dict["packets"] = {
|
||||||
"sent": tx,
|
"sent": tx,
|
||||||
"received": rx,
|
"received": rx,
|
||||||
}
|
}
|
||||||
|
if track:
|
||||||
|
size_tracker = len(track)
|
||||||
|
else:
|
||||||
|
size_tracker = 0
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"time": now.strftime(time_format),
|
"time": now.strftime(time_format),
|
||||||
"size_tracker": len(track),
|
"size_tracker": size_tracker,
|
||||||
"stats": stats_dict,
|
"stats": stats_dict,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,139 +367,58 @@ class APRSDFlask(flask_classful.FlaskView):
|
||||||
return json.dumps(self._stats())
|
return json.dumps(self._stats())
|
||||||
|
|
||||||
|
|
||||||
class SendMessageNamespace(Namespace):
|
class LogUpdateThread(threads.APRSDThread):
|
||||||
_config = None
|
|
||||||
got_ack = False
|
|
||||||
reply_sent = False
|
|
||||||
packet = None
|
|
||||||
request = None
|
|
||||||
|
|
||||||
def __init__(self, namespace=None, config=None):
|
|
||||||
self._config = config
|
|
||||||
super().__init__(namespace)
|
|
||||||
|
|
||||||
def on_connect(self):
|
|
||||||
global socketio
|
|
||||||
LOG.debug("Web socket connected")
|
|
||||||
socketio.emit(
|
|
||||||
"connected", {"data": "/sendmsg Connected"},
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_disconnect(self):
|
|
||||||
LOG.debug("WS Disconnected")
|
|
||||||
|
|
||||||
def on_send(self, data):
|
|
||||||
global socketio
|
|
||||||
LOG.debug(f"WS: on_send {data}")
|
|
||||||
self.request = data
|
|
||||||
self.packet = packets.MessagePacket(
|
|
||||||
from_call=data["from"],
|
|
||||||
to_call=data["to"],
|
|
||||||
message_text=data["message"],
|
|
||||||
)
|
|
||||||
msgs = SentMessages()
|
|
||||||
msgs.add(self.packet)
|
|
||||||
msgs.set_status(self.packet.msgNo, "Sending")
|
|
||||||
socketio.emit(
|
|
||||||
"sent", SentMessages().get(self.packet.msgNo),
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
|
|
||||||
socketio.start_background_task(
|
|
||||||
self._start, self._config, data,
|
|
||||||
self.packet, self,
|
|
||||||
)
|
|
||||||
LOG.warning("WS: on_send: exit")
|
|
||||||
|
|
||||||
def _start(self, config, data, packet, namespace):
|
|
||||||
msg_thread = SendMessageThread(self._config, data, packet, self)
|
|
||||||
msg_thread.start()
|
|
||||||
|
|
||||||
def handle_message(self, data):
|
|
||||||
LOG.debug(f"WS Data {data}")
|
|
||||||
|
|
||||||
def handle_json(self, data):
|
|
||||||
LOG.debug(f"WS json {data}")
|
|
||||||
|
|
||||||
|
|
||||||
class LogMonitorThread(threads.APRSDThread):
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("LogMonitorThread")
|
super().__init__("LogUpdate")
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
global socketio
|
global socketio
|
||||||
try:
|
|
||||||
record = log.logging_queue.get(block=True, timeout=5)
|
|
||||||
json_record = self.json_record(record)
|
|
||||||
socketio.emit(
|
|
||||||
"log_entry", json_record,
|
|
||||||
namespace="/logs",
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
# Just ignore thi
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
if socketio:
|
||||||
|
log_entries = RPCClient().get_log_entries()
|
||||||
|
|
||||||
|
if log_entries:
|
||||||
|
for entry in log_entries:
|
||||||
|
socketio.emit(
|
||||||
|
"log_entry", entry,
|
||||||
|
namespace="/logs",
|
||||||
|
)
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def json_record(self, record):
|
|
||||||
entry = {}
|
|
||||||
entry["filename"] = record.filename
|
|
||||||
entry["funcName"] = record.funcName
|
|
||||||
entry["levelname"] = record.levelname
|
|
||||||
entry["lineno"] = record.lineno
|
|
||||||
entry["module"] = record.module
|
|
||||||
entry["name"] = record.name
|
|
||||||
entry["pathname"] = record.pathname
|
|
||||||
entry["process"] = record.process
|
|
||||||
entry["processName"] = record.processName
|
|
||||||
if hasattr(record, "stack_info"):
|
|
||||||
entry["stack_info"] = record.stack_info
|
|
||||||
else:
|
|
||||||
entry["stack_info"] = None
|
|
||||||
entry["thread"] = record.thread
|
|
||||||
entry["threadName"] = record.threadName
|
|
||||||
entry["message"] = record.getMessage()
|
|
||||||
return entry
|
|
||||||
|
|
||||||
|
|
||||||
class LoggingNamespace(Namespace):
|
class LoggingNamespace(Namespace):
|
||||||
|
log_thread = None
|
||||||
|
|
||||||
def on_connect(self):
|
def on_connect(self):
|
||||||
global socketio
|
global socketio
|
||||||
LOG.debug("Web socket connected")
|
|
||||||
socketio.emit(
|
socketio.emit(
|
||||||
"connected", {"data": "/logs Connected"},
|
"connected", {"data": "/logs Connected"},
|
||||||
namespace="/logs",
|
namespace="/logs",
|
||||||
)
|
)
|
||||||
self.log_thread = LogMonitorThread()
|
self.log_thread = LogUpdateThread()
|
||||||
self.log_thread.start()
|
self.log_thread.start()
|
||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
LOG.debug("WS Disconnected")
|
LOG.debug("LOG Disconnected")
|
||||||
self.log_thread.stop()
|
if self.log_thread:
|
||||||
|
self.log_thread.stop()
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(config, flask_app, loglevel, quiet):
|
def setup_logging(flask_app, loglevel, quiet):
|
||||||
flask_log = logging.getLogger("werkzeug")
|
flask_log = logging.getLogger("werkzeug")
|
||||||
flask_app.logger.removeHandler(default_handler)
|
flask_app.logger.removeHandler(default_handler)
|
||||||
flask_log.removeHandler(default_handler)
|
flask_log.removeHandler(default_handler)
|
||||||
|
|
||||||
log_level = aprsd_config.LOG_LEVELS[loglevel]
|
log_level = conf.log.LOG_LEVELS[loglevel]
|
||||||
flask_log.setLevel(log_level)
|
flask_log.setLevel(log_level)
|
||||||
date_format = config["aprsd"].get(
|
date_format = CONF.logging.date_format
|
||||||
"dateformat",
|
flask_log.disabled = True
|
||||||
aprsd_config.DEFAULT_DATE_FORMAT,
|
flask_app.logger.disabled = True
|
||||||
)
|
|
||||||
|
|
||||||
if not config["aprsd"]["web"].get("logging_enabled", False):
|
if CONF.logging.rich_logging:
|
||||||
# disable web logging
|
|
||||||
flask_log.disabled = True
|
|
||||||
flask_app.logger.disabled = True
|
|
||||||
return
|
|
||||||
|
|
||||||
if config["aprsd"].get("rich_logging", False) and not quiet:
|
|
||||||
log_format = "%(message)s"
|
log_format = "%(message)s"
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
rh = aprsd_logging.APRSDRichHandler(
|
rh = aprsd_logging.APRSDRichHandler(
|
||||||
|
@ -594,13 +428,10 @@ def setup_logging(config, flask_app, loglevel, quiet):
|
||||||
rh.setFormatter(log_formatter)
|
rh.setFormatter(log_formatter)
|
||||||
flask_log.addHandler(rh)
|
flask_log.addHandler(rh)
|
||||||
|
|
||||||
log_file = config["aprsd"].get("logfile", None)
|
log_file = CONF.logging.logfile
|
||||||
|
|
||||||
if log_file:
|
if log_file:
|
||||||
log_format = config["aprsd"].get(
|
log_format = CONF.logging.logformat
|
||||||
"logformat",
|
|
||||||
aprsd_config.DEFAULT_LOG_FORMAT,
|
|
||||||
)
|
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
fh = RotatingFileHandler(
|
fh = RotatingFileHandler(
|
||||||
log_file, maxBytes=(10248576 * 5),
|
log_file, maxBytes=(10248576 * 5),
|
||||||
|
@ -610,7 +441,7 @@ def setup_logging(config, flask_app, loglevel, quiet):
|
||||||
flask_log.addHandler(fh)
|
flask_log.addHandler(fh)
|
||||||
|
|
||||||
|
|
||||||
def init_flask(config, loglevel, quiet):
|
def init_flask(loglevel, quiet):
|
||||||
global socketio
|
global socketio
|
||||||
|
|
||||||
flask_app = flask.Flask(
|
flask_app = flask.Flask(
|
||||||
|
@ -619,15 +450,13 @@ def init_flask(config, loglevel, quiet):
|
||||||
static_folder="web/admin/static",
|
static_folder="web/admin/static",
|
||||||
template_folder="web/admin/templates",
|
template_folder="web/admin/templates",
|
||||||
)
|
)
|
||||||
setup_logging(config, flask_app, loglevel, quiet)
|
setup_logging(flask_app, loglevel, quiet)
|
||||||
server = APRSDFlask()
|
server = APRSDFlask()
|
||||||
server.set_config(config)
|
server.set_config()
|
||||||
flask_app.route("/", methods=["GET"])(server.index)
|
flask_app.route("/", methods=["GET"])(server.index)
|
||||||
flask_app.route("/stats", methods=["GET"])(server.stats)
|
flask_app.route("/stats", methods=["GET"])(server.stats)
|
||||||
flask_app.route("/messages", methods=["GET"])(server.messages)
|
flask_app.route("/messages", methods=["GET"])(server.messages)
|
||||||
flask_app.route("/packets", methods=["GET"])(server.packets)
|
flask_app.route("/packets", methods=["GET"])(server.packets)
|
||||||
flask_app.route("/send-message", methods=["GET"])(server.send_message)
|
|
||||||
flask_app.route("/send-message-status", methods=["GET"])(server.send_message_status)
|
|
||||||
flask_app.route("/save", methods=["GET"])(server.save)
|
flask_app.route("/save", methods=["GET"])(server.save)
|
||||||
flask_app.route("/plugins", methods=["GET"])(server.plugins)
|
flask_app.route("/plugins", methods=["GET"])(server.plugins)
|
||||||
|
|
||||||
|
@ -637,7 +466,21 @@ def init_flask(config, loglevel, quiet):
|
||||||
)
|
)
|
||||||
# import eventlet
|
# import eventlet
|
||||||
# eventlet.monkey_patch()
|
# eventlet.monkey_patch()
|
||||||
|
gunicorn_logger = logging.getLogger("gunicorn.error")
|
||||||
|
flask_app.logger.handlers = gunicorn_logger.handlers
|
||||||
|
flask_app.logger.setLevel(gunicorn_logger.level)
|
||||||
|
|
||||||
socketio.on_namespace(SendMessageNamespace("/sendmsg", config=config))
|
|
||||||
socketio.on_namespace(LoggingNamespace("/logs"))
|
socketio.on_namespace(LoggingNamespace("/logs"))
|
||||||
return socketio, flask_app
|
return socketio, flask_app
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "aprsd.flask":
|
||||||
|
try:
|
||||||
|
default_config_file = cli_helper.DEFAULT_CONFIG_FILE
|
||||||
|
CONF(
|
||||||
|
[], project="aprsd", version=aprsd.__version__,
|
||||||
|
default_config_files=[default_config_file],
|
||||||
|
)
|
||||||
|
except cfg.ConfigFilesNotFoundError:
|
||||||
|
pass
|
||||||
|
sio, app = init_flask("DEBUG", False)
|
||||||
|
|
|
@ -4,10 +4,13 @@ from logging.handlers import RotatingFileHandler
|
||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import conf
|
||||||
from aprsd.logging import rich as aprsd_logging
|
from aprsd.logging import rich as aprsd_logging
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
logging_queue = queue.Queue()
|
logging_queue = queue.Queue()
|
||||||
|
|
||||||
|
@ -15,13 +18,15 @@ logging_queue = queue.Queue()
|
||||||
# Setup the logging faciility
|
# Setup the logging faciility
|
||||||
# to disable logging to stdout, but still log to file
|
# to disable logging to stdout, but still log to file
|
||||||
# use the --quiet option on the cmdln
|
# use the --quiet option on the cmdln
|
||||||
def setup_logging(config, loglevel, quiet):
|
def setup_logging(loglevel, quiet):
|
||||||
log_level = aprsd_config.LOG_LEVELS[loglevel]
|
log_level = conf.log.LOG_LEVELS[loglevel]
|
||||||
LOG.setLevel(log_level)
|
LOG.setLevel(log_level)
|
||||||
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
|
date_format = CONF.logging.date_format
|
||||||
|
rh = None
|
||||||
|
fh = None
|
||||||
|
|
||||||
rich_logging = False
|
rich_logging = False
|
||||||
if config["aprsd"].get("rich_logging", False) and not quiet:
|
if CONF.logging.get("rich_logging", False) and not quiet:
|
||||||
log_format = "%(message)s"
|
log_format = "%(message)s"
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
rh = aprsd_logging.APRSDRichHandler(
|
rh = aprsd_logging.APRSDRichHandler(
|
||||||
|
@ -32,8 +37,8 @@ def setup_logging(config, loglevel, quiet):
|
||||||
LOG.addHandler(rh)
|
LOG.addHandler(rh)
|
||||||
rich_logging = True
|
rich_logging = True
|
||||||
|
|
||||||
log_file = config["aprsd"].get("logfile", None)
|
log_file = CONF.logging.logfile
|
||||||
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
|
log_format = CONF.logging.logformat
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
|
|
||||||
if log_file:
|
if log_file:
|
||||||
|
@ -42,16 +47,19 @@ def setup_logging(config, loglevel, quiet):
|
||||||
LOG.addHandler(fh)
|
LOG.addHandler(fh)
|
||||||
|
|
||||||
imap_logger = None
|
imap_logger = None
|
||||||
if config.get("aprsd.email.enabled", default=False) and config.get("aprsd.email.imap.debug", default=False):
|
if CONF.email_plugin.enabled and CONF.email_plugin.debug:
|
||||||
imap_logger = logging.getLogger("imapclient.imaplib")
|
imap_logger = logging.getLogger("imapclient.imaplib")
|
||||||
imap_logger.setLevel(log_level)
|
imap_logger.setLevel(log_level)
|
||||||
imap_logger.addHandler(fh)
|
if rh:
|
||||||
|
imap_logger.addHandler(rh)
|
||||||
|
if fh:
|
||||||
|
imap_logger.addHandler(fh)
|
||||||
|
|
||||||
if config.get("aprsd.web.enabled", default=False):
|
if CONF.admin.web_enabled:
|
||||||
qh = logging.handlers.QueueHandler(logging_queue)
|
qh = logging.handlers.QueueHandler(logging_queue)
|
||||||
q_log_formatter = logging.Formatter(
|
q_log_formatter = logging.Formatter(
|
||||||
fmt=aprsd_config.QUEUE_LOG_FORMAT,
|
fmt=CONF.logging.logformat,
|
||||||
datefmt=aprsd_config.QUEUE_DATE_FORMAT,
|
datefmt=CONF.logging.date_format,
|
||||||
)
|
)
|
||||||
qh.setFormatter(q_log_formatter)
|
qh.setFormatter(q_log_formatter)
|
||||||
LOG.addHandler(qh)
|
LOG.addHandler(qh)
|
||||||
|
@ -65,10 +73,10 @@ def setup_logging(config, loglevel, quiet):
|
||||||
|
|
||||||
|
|
||||||
def setup_logging_no_config(loglevel, quiet):
|
def setup_logging_no_config(loglevel, quiet):
|
||||||
log_level = aprsd_config.LOG_LEVELS[loglevel]
|
log_level = conf.log.LOG_LEVELS[loglevel]
|
||||||
LOG.setLevel(log_level)
|
LOG.setLevel(log_level)
|
||||||
log_format = aprsd_config.DEFAULT_LOG_FORMAT
|
log_format = CONF.logging.logformat
|
||||||
date_format = aprsd_config.DEFAULT_DATE_FORMAT
|
date_format = CONF.logging.date_format
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
fh = NullHandler()
|
fh = NullHandler()
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,9 @@ class Packet(metaclass=abc.ABCMeta):
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
def update_timestamp(self):
|
||||||
|
self.timestamp = _int_timestamp()
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
"""Do stuff here that is needed prior to sending over the air."""
|
"""Do stuff here that is needed prior to sending over the air."""
|
||||||
# now build the raw message for sending
|
# now build the raw message for sending
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
from aprsd import stats, utils
|
from aprsd import stats, utils
|
||||||
from aprsd.packets import seen_list
|
from aprsd.packets import seen_list
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +17,6 @@ class PacketList:
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
config = None
|
|
||||||
|
|
||||||
packet_list: utils.RingBuffer = utils.RingBuffer(1000)
|
packet_list: utils.RingBuffer = utils.RingBuffer(1000)
|
||||||
|
|
||||||
|
@ -25,17 +26,8 @@ class PacketList:
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
if "config" in kwargs:
|
|
||||||
cls._instance.config = kwargs["config"]
|
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, config=None):
|
|
||||||
if config:
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
def _is_initialized(self):
|
|
||||||
return self.config is not None
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
@wrapt.synchronized(lock)
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self.packet_list)
|
return iter(self.packet_list)
|
||||||
|
|
|
@ -2,11 +2,13 @@ import datetime
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
from aprsd.utils import objectstore
|
from aprsd.utils import objectstore
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,21 +18,14 @@ class SeenList(objectstore.ObjectStoreMixin):
|
||||||
_instance = None
|
_instance = None
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
data: dict = {}
|
data: dict = {}
|
||||||
config = None
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
if "config" in kwargs:
|
cls._instance._init_store()
|
||||||
if "config" in kwargs:
|
|
||||||
cls._instance.config = kwargs["config"]
|
|
||||||
cls._instance._init_store()
|
|
||||||
cls._instance.data = {}
|
cls._instance.data = {}
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def is_initialized(self):
|
|
||||||
return self.config is not None
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
@wrapt.synchronized(lock)
|
||||||
def update_seen(self, packet):
|
def update_seen(self, packet):
|
||||||
callsign = None
|
callsign = None
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import datetime
|
import datetime
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
from aprsd.threads import tx
|
from aprsd.threads import tx
|
||||||
from aprsd.utils import objectstore
|
from aprsd.utils import objectstore
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class PacketTrack(objectstore.ObjectStoreMixin):
|
class PacketTrack(objectstore.ObjectStoreMixin):
|
||||||
"""Class to keep track of outstanding text messages.
|
"""Class to keep track of outstanding text messages.
|
||||||
|
|
||||||
|
@ -23,7 +27,6 @@ class PacketTrack(objectstore.ObjectStoreMixin):
|
||||||
_instance = None
|
_instance = None
|
||||||
_start_time = None
|
_start_time = None
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
config = None
|
|
||||||
|
|
||||||
data: dict = {}
|
data: dict = {}
|
||||||
total_tracked: int = 0
|
total_tracked: int = 0
|
||||||
|
@ -32,14 +35,9 @@ class PacketTrack(objectstore.ObjectStoreMixin):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
cls._instance._start_time = datetime.datetime.now()
|
cls._instance._start_time = datetime.datetime.now()
|
||||||
if "config" in kwargs:
|
|
||||||
cls._instance.config = kwargs["config"]
|
|
||||||
cls._instance._init_store()
|
cls._instance._init_store()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def is_initialized(self):
|
|
||||||
return self.config is not None
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
@wrapt.synchronized(lock)
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
return self.data[name]
|
return self.data[name]
|
||||||
|
|
|
@ -2,12 +2,14 @@ import datetime
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
from aprsd import utils
|
from aprsd import utils
|
||||||
from aprsd.utils import objectstore
|
from aprsd.utils import objectstore
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,24 +19,19 @@ class WatchList(objectstore.ObjectStoreMixin):
|
||||||
_instance = None
|
_instance = None
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
data = {}
|
data = {}
|
||||||
config = None
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
if "config" in kwargs:
|
cls._instance._init_store()
|
||||||
cls._instance.config = kwargs["config"]
|
|
||||||
cls._instance._init_store()
|
|
||||||
cls._instance.data = {}
|
cls._instance.data = {}
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config=None):
|
||||||
if config:
|
ring_size = CONF.watch_list.packet_keep_count
|
||||||
self.config = config
|
|
||||||
|
|
||||||
ring_size = config["aprsd"]["watch_list"].get("packet_keep_count", 10)
|
if CONF.watch_list.callsigns:
|
||||||
|
for callsign in CONF.watch_list.callsigns:
|
||||||
for callsign in config["aprsd"]["watch_list"].get("callsigns", []):
|
|
||||||
call = callsign.replace("*", "")
|
call = callsign.replace("*", "")
|
||||||
# FIXME(waboring) - we should fetch the last time we saw
|
# FIXME(waboring) - we should fetch the last time we saw
|
||||||
# a beacon from a callsign or some other mechanism to find
|
# a beacon from a callsign or some other mechanism to find
|
||||||
|
@ -47,14 +44,8 @@ class WatchList(objectstore.ObjectStoreMixin):
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def is_initialized(self):
|
|
||||||
return self.config is not None
|
|
||||||
|
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
if self.config and "watch_list" in self.config["aprsd"]:
|
return CONF.watch_list.enabled
|
||||||
return self.config["aprsd"]["watch_list"].get("enabled", False)
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def callsign_in_watchlist(self, callsign):
|
def callsign_in_watchlist(self, callsign):
|
||||||
return callsign in self.data
|
return callsign in self.data
|
||||||
|
@ -78,9 +69,8 @@ class WatchList(objectstore.ObjectStoreMixin):
|
||||||
return str(now - self.last_seen(callsign))
|
return str(now - self.last_seen(callsign))
|
||||||
|
|
||||||
def max_delta(self, seconds=None):
|
def max_delta(self, seconds=None):
|
||||||
watch_list_conf = self.config["aprsd"]["watch_list"]
|
|
||||||
if not seconds:
|
if not seconds:
|
||||||
seconds = watch_list_conf["alert_time_seconds"]
|
seconds = CONF.watch_list.alert_time_seconds
|
||||||
max_timeout = {"seconds": seconds}
|
max_timeout = {"seconds": seconds}
|
||||||
return datetime.timedelta(**max_timeout)
|
return datetime.timedelta(**max_timeout)
|
||||||
|
|
||||||
|
|
116
aprsd/plugin.py
116
aprsd/plugin.py
|
@ -7,6 +7,7 @@ import re
|
||||||
import textwrap
|
import textwrap
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import pluggy
|
import pluggy
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
|
@ -15,6 +16,7 @@ from aprsd.packets import watch_list
|
||||||
|
|
||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
CORE_MESSAGE_PLUGINS = [
|
CORE_MESSAGE_PLUGINS = [
|
||||||
|
@ -57,8 +59,7 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
|
||||||
# Set this in setup()
|
# Set this in setup()
|
||||||
enabled = False
|
enabled = False
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self):
|
||||||
self.config = config
|
|
||||||
self.message_counter = 0
|
self.message_counter = 0
|
||||||
self.setup()
|
self.setup()
|
||||||
self.threads = self.create_threads() or []
|
self.threads = self.create_threads() or []
|
||||||
|
@ -140,15 +141,10 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
# if we have a watch list enabled, we need to add filtering
|
# if we have a watch list enabled, we need to add filtering
|
||||||
# to enable seeing packets from the watch list.
|
# to enable seeing packets from the watch list.
|
||||||
if "watch_list" in self.config["aprsd"] and self.config["aprsd"][
|
if CONF.watch_list.enabled:
|
||||||
"watch_list"
|
|
||||||
].get("enabled", False):
|
|
||||||
# watch list is enabled
|
# watch list is enabled
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
watch_list = self.config["aprsd"]["watch_list"].get(
|
watch_list = CONF.watch_list.callsigns
|
||||||
"callsigns",
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
# make sure the timeout is set or this doesn't work
|
# make sure the timeout is set or this doesn't work
|
||||||
if watch_list:
|
if watch_list:
|
||||||
aprs_client = client.factory.create().client
|
aprs_client = client.factory.create().client
|
||||||
|
@ -211,36 +207,40 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def filter(self, packet: packets.core.MessagePacket):
|
def filter(self, packet: packets.core.MessagePacket):
|
||||||
|
if not self.enabled:
|
||||||
|
result = f"{self.__class__.__name__} isn't enabled"
|
||||||
|
LOG.warning(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
if not isinstance(packet, packets.core.MessagePacket):
|
||||||
|
LOG.warning(f"Got a {packet.__class__.__name__} ignoring")
|
||||||
|
return packets.NULL_MESSAGE
|
||||||
|
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
message = packet.get("message_text", None)
|
message = packet.message_text
|
||||||
msg_format = packet.get("format", None)
|
tocall = packet.to_call
|
||||||
tocall = packet.get("addresse", None)
|
|
||||||
|
|
||||||
# Only process messages destined for us
|
# Only process messages destined for us
|
||||||
# and is an APRS message format and has a message.
|
# and is an APRS message format and has a message.
|
||||||
if (
|
if (
|
||||||
tocall == self.config["aprs"]["login"]
|
tocall == CONF.callsign
|
||||||
and msg_format == "message"
|
and isinstance(packet, packets.core.MessagePacket)
|
||||||
and message
|
and message
|
||||||
):
|
):
|
||||||
if re.search(self.command_regex, message):
|
if re.search(self.command_regex, message):
|
||||||
self.rx_inc()
|
self.rx_inc()
|
||||||
if self.enabled:
|
try:
|
||||||
try:
|
result = self.process(packet)
|
||||||
result = self.process(packet)
|
except Exception as ex:
|
||||||
except Exception as ex:
|
LOG.error(
|
||||||
LOG.error(
|
"Plugin {} failed to process packet {}".format(
|
||||||
"Plugin {} failed to process packet {}".format(
|
self.__class__, ex,
|
||||||
self.__class__, ex,
|
),
|
||||||
),
|
)
|
||||||
)
|
LOG.exception(ex)
|
||||||
LOG.exception(ex)
|
if result:
|
||||||
if result:
|
self.tx_inc()
|
||||||
self.tx_inc()
|
|
||||||
else:
|
|
||||||
result = f"{self.__class__.__name__} isn't enabled"
|
|
||||||
LOG.warning(result)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -249,12 +249,11 @@ class APRSFIKEYMixin:
|
||||||
"""Mixin class to enable checking the existence of the aprs.fi apiKey."""
|
"""Mixin class to enable checking the existence of the aprs.fi apiKey."""
|
||||||
|
|
||||||
def ensure_aprs_fi_key(self):
|
def ensure_aprs_fi_key(self):
|
||||||
try:
|
if not CONF.aprs_fi.apiKey:
|
||||||
self.config.check_option(["services", "aprs.fi", "apiKey"])
|
LOG.error("Config aprs_fi.apiKey is not set")
|
||||||
self.enabled = True
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
|
||||||
self.enabled = False
|
self.enabled = False
|
||||||
|
else:
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
|
|
||||||
class HelpPlugin(APRSDRegexCommandPluginBase):
|
class HelpPlugin(APRSDRegexCommandPluginBase):
|
||||||
|
@ -371,12 +370,17 @@ class PluginManager:
|
||||||
:param kwargs: parameters to pass
|
:param kwargs: parameters to pass
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
module_name, class_name = module_class_string.rsplit(".", 1)
|
module_name = None
|
||||||
|
class_name = None
|
||||||
try:
|
try:
|
||||||
|
module_name, class_name = module_class_string.rsplit(".", 1)
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
module = importlib.reload(module)
|
module = importlib.reload(module)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to load Plugin '{module_name}' : '{ex}'")
|
if not module_name:
|
||||||
|
LOG.error(f"Failed to load Plugin {module_class_string}")
|
||||||
|
else:
|
||||||
|
LOG.error(f"Failed to load Plugin '{module_name}' : '{ex}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
assert hasattr(module, class_name), "class {} is not in {}".format(
|
assert hasattr(module, class_name), "class {} is not in {}".format(
|
||||||
|
@ -406,25 +410,31 @@ class PluginManager:
|
||||||
plugin_obj = self._create_class(
|
plugin_obj = self._create_class(
|
||||||
plugin_name,
|
plugin_name,
|
||||||
APRSDPluginBase,
|
APRSDPluginBase,
|
||||||
config=self.config,
|
|
||||||
)
|
)
|
||||||
if plugin_obj:
|
if plugin_obj:
|
||||||
if isinstance(plugin_obj, APRSDWatchListPluginBase):
|
if isinstance(plugin_obj, APRSDWatchListPluginBase):
|
||||||
LOG.info(
|
if plugin_obj.enabled:
|
||||||
"Registering WatchList plugin '{}'({})".format(
|
LOG.info(
|
||||||
plugin_name,
|
"Registering WatchList plugin '{}'({})".format(
|
||||||
plugin_obj.version,
|
plugin_name,
|
||||||
),
|
plugin_obj.version,
|
||||||
)
|
),
|
||||||
self._watchlist_pm.register(plugin_obj)
|
)
|
||||||
|
self._watchlist_pm.register(plugin_obj)
|
||||||
|
else:
|
||||||
|
LOG.warning(f"Plugin {plugin_obj.__class__.__name__} is disabled")
|
||||||
else:
|
else:
|
||||||
LOG.info(
|
if plugin_obj.enabled:
|
||||||
"Registering plugin '{}'({})".format(
|
LOG.info(
|
||||||
plugin_name,
|
"Registering plugin '{}'({}) -- {}".format(
|
||||||
plugin_obj.version,
|
plugin_name,
|
||||||
),
|
plugin_obj.version,
|
||||||
)
|
plugin_obj.command_regex,
|
||||||
self._pluggy_pm.register(plugin_obj)
|
),
|
||||||
|
)
|
||||||
|
self._pluggy_pm.register(plugin_obj)
|
||||||
|
else:
|
||||||
|
LOG.warning(f"Plugin {plugin_obj.__class__.__name__} is disabled")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Couldn't load plugin '{plugin_name}'")
|
LOG.error(f"Couldn't load plugin '{plugin_name}'")
|
||||||
LOG.exception(ex)
|
LOG.exception(ex)
|
||||||
|
@ -440,10 +450,10 @@ class PluginManager:
|
||||||
LOG.info("Loading APRSD Plugins")
|
LOG.info("Loading APRSD Plugins")
|
||||||
self._init()
|
self._init()
|
||||||
# Help plugin is always enabled.
|
# Help plugin is always enabled.
|
||||||
_help = HelpPlugin(self.config)
|
_help = HelpPlugin()
|
||||||
self._pluggy_pm.register(_help)
|
self._pluggy_pm.register(_help)
|
||||||
|
|
||||||
enabled_plugins = self.config["aprsd"].get("enabled_plugins", None)
|
enabled_plugins = CONF.enabled_plugins
|
||||||
if enabled_plugins:
|
if enabled_plugins:
|
||||||
for p_name in enabled_plugins:
|
for p_name in enabled_plugins:
|
||||||
self._load_plugin(p_name)
|
self._load_plugin(p_name)
|
||||||
|
|
|
@ -25,14 +25,20 @@ def get_aprs_fi(api_key, callsign):
|
||||||
|
|
||||||
|
|
||||||
def get_weather_gov_for_gps(lat, lon):
|
def get_weather_gov_for_gps(lat, lon):
|
||||||
|
# FIXME(hemna) This is currently BROKEN
|
||||||
LOG.debug(f"Fetch station at {lat}, {lon}")
|
LOG.debug(f"Fetch station at {lat}, {lon}")
|
||||||
|
headers = requests.utils.default_headers()
|
||||||
|
headers.update(
|
||||||
|
{"User-Agent": "(aprsd, waboring@hemna.com)"},
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
url2 = (
|
url2 = (
|
||||||
"https://forecast.weather.gov/MapClick.php?lat=%s"
|
# "https://forecast.weather.gov/MapClick.php?lat=%s"
|
||||||
"&lon=%s&FcstType=json" % (lat, lon)
|
# "&lon=%s&FcstType=json" % (lat, lon)
|
||||||
|
f"https://api.weather.gov/points/{lat},{lon}"
|
||||||
)
|
)
|
||||||
LOG.debug(f"Fetching weather '{url2}'")
|
LOG.debug(f"Fetching weather '{url2}'")
|
||||||
response = requests.get(url2)
|
response = requests.get(url2, headers=headers)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
raise Exception("Failed to get weather")
|
raise Exception("Failed to get weather")
|
||||||
|
|
|
@ -9,13 +9,16 @@ import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import imapclient
|
import imapclient
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import packets, plugin, stats, threads
|
from aprsd import packets, plugin, stats, threads
|
||||||
from aprsd.threads import tx
|
from aprsd.threads import tx
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
shortcuts_dict = None
|
||||||
|
|
||||||
|
|
||||||
class EmailInfo:
|
class EmailInfo:
|
||||||
|
@ -71,18 +74,18 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""Ensure that email is enabled and start the thread."""
|
"""Ensure that email is enabled and start the thread."""
|
||||||
|
if CONF.email_plugin.enabled:
|
||||||
email_enabled = self.config["aprsd"]["email"].get("enabled", False)
|
|
||||||
if email_enabled:
|
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
|
shortcuts = _build_shortcuts_dict()
|
||||||
|
LOG.info(f"Email shortcuts {shortcuts}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
LOG.info("Email services not enabled.")
|
LOG.info("Email services not enabled.")
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
def create_threads(self):
|
def create_threads(self):
|
||||||
if self.enabled:
|
if self.enabled:
|
||||||
return APRSDEmailThread(
|
return APRSDEmailThread()
|
||||||
config=self.config,
|
|
||||||
)
|
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet: packets.MessagePacket):
|
def process(self, packet: packets.MessagePacket):
|
||||||
|
@ -97,18 +100,18 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
ack = packet.get("msgNo", "0")
|
ack = packet.get("msgNo", "0")
|
||||||
|
|
||||||
reply = None
|
reply = None
|
||||||
if not self.config["aprsd"]["email"].get("enabled", False):
|
if not CONF.email_plugin.enabled:
|
||||||
LOG.debug("Email is not enabled in config file ignoring.")
|
LOG.debug("Email is not enabled in config file ignoring.")
|
||||||
return "Email not enabled."
|
return "Email not enabled."
|
||||||
|
|
||||||
searchstring = "^" + self.config["ham"]["callsign"] + ".*"
|
searchstring = "^" + CONF.email_plugin.callsign + ".*"
|
||||||
# only I can do email
|
# only I can do email
|
||||||
if re.search(searchstring, fromcall):
|
if re.search(searchstring, fromcall):
|
||||||
# digits only, first one is number of emails to resend
|
# digits only, first one is number of emails to resend
|
||||||
r = re.search("^-([0-9])[0-9]*$", message)
|
r = re.search("^-([0-9])[0-9]*$", message)
|
||||||
if r is not None:
|
if r is not None:
|
||||||
LOG.debug("RESEND EMAIL")
|
LOG.debug("RESEND EMAIL")
|
||||||
resend_email(self.config, r.group(1), fromcall)
|
resend_email(r.group(1), fromcall)
|
||||||
reply = packets.NULL_MESSAGE
|
reply = packets.NULL_MESSAGE
|
||||||
# -user@address.com body of email
|
# -user@address.com body of email
|
||||||
elif re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message):
|
elif re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message):
|
||||||
|
@ -118,7 +121,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
to_addr = a.group(1)
|
to_addr = a.group(1)
|
||||||
content = a.group(2)
|
content = a.group(2)
|
||||||
|
|
||||||
email_address = get_email_from_shortcut(self.config, to_addr)
|
email_address = get_email_from_shortcut(to_addr)
|
||||||
if not email_address:
|
if not email_address:
|
||||||
reply = "Bad email address"
|
reply = "Bad email address"
|
||||||
return reply
|
return reply
|
||||||
|
@ -128,7 +131,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
content = (
|
content = (
|
||||||
"Click for my location: http://aprs.fi/{}" ""
|
"Click for my location: http://aprs.fi/{}" ""
|
||||||
).format(
|
).format(
|
||||||
self.config["ham"]["callsign"],
|
CONF.email_plugin.callsign,
|
||||||
)
|
)
|
||||||
too_soon = 0
|
too_soon = 0
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
@ -141,7 +144,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
too_soon = 1
|
too_soon = 1
|
||||||
if not too_soon or ack == 0:
|
if not too_soon or ack == 0:
|
||||||
LOG.info(f"Send email '{content}'")
|
LOG.info(f"Send email '{content}'")
|
||||||
send_result = send_email(self.config, to_addr, content)
|
send_result = send_email(to_addr, content)
|
||||||
reply = packets.NULL_MESSAGE
|
reply = packets.NULL_MESSAGE
|
||||||
if send_result != 0:
|
if send_result != 0:
|
||||||
reply = f"-{to_addr} failed"
|
reply = f"-{to_addr} failed"
|
||||||
|
@ -169,9 +172,9 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
def _imap_connect(config):
|
def _imap_connect():
|
||||||
imap_port = config["aprsd"]["email"]["imap"].get("port", 143)
|
imap_port = CONF.email_plugin.imap_port
|
||||||
use_ssl = config["aprsd"]["email"]["imap"].get("use_ssl", False)
|
use_ssl = CONF.email_plugin.imap_use_ssl
|
||||||
# host = CONFIG["aprsd"]["email"]["imap"]["host"]
|
# host = CONFIG["aprsd"]["email"]["imap"]["host"]
|
||||||
# msg = "{}{}:{}".format("TLS " if use_ssl else "", host, imap_port)
|
# msg = "{}{}:{}".format("TLS " if use_ssl else "", host, imap_port)
|
||||||
# LOG.debug("Connect to IMAP host {} with user '{}'".
|
# LOG.debug("Connect to IMAP host {} with user '{}'".
|
||||||
|
@ -179,7 +182,7 @@ def _imap_connect(config):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server = imapclient.IMAPClient(
|
server = imapclient.IMAPClient(
|
||||||
config["aprsd"]["email"]["imap"]["host"],
|
CONF.email_plugin.imap_host,
|
||||||
port=imap_port,
|
port=imap_port,
|
||||||
use_uid=True,
|
use_uid=True,
|
||||||
ssl=use_ssl,
|
ssl=use_ssl,
|
||||||
|
@ -191,8 +194,8 @@ def _imap_connect(config):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server.login(
|
server.login(
|
||||||
config["aprsd"]["email"]["imap"]["login"],
|
CONF.email_plugin.imap_login,
|
||||||
config["aprsd"]["email"]["imap"]["password"],
|
CONF.email_plugin.imap_password,
|
||||||
)
|
)
|
||||||
except (imaplib.IMAP4.error, Exception) as e:
|
except (imaplib.IMAP4.error, Exception) as e:
|
||||||
msg = getattr(e, "message", repr(e))
|
msg = getattr(e, "message", repr(e))
|
||||||
|
@ -208,15 +211,15 @@ def _imap_connect(config):
|
||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
def _smtp_connect(config):
|
def _smtp_connect():
|
||||||
host = config["aprsd"]["email"]["smtp"]["host"]
|
host = CONF.email_plugin.smtp_host
|
||||||
smtp_port = config["aprsd"]["email"]["smtp"]["port"]
|
smtp_port = CONF.email_plugin.smtp_port
|
||||||
use_ssl = config["aprsd"]["email"]["smtp"].get("use_ssl", False)
|
use_ssl = CONF.email_plugin.smtp_use_ssl
|
||||||
msg = "{}{}:{}".format("SSL " if use_ssl else "", host, smtp_port)
|
msg = "{}{}:{}".format("SSL " if use_ssl else "", host, smtp_port)
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Connect to SMTP host {} with user '{}'".format(
|
"Connect to SMTP host {} with user '{}'".format(
|
||||||
msg,
|
msg,
|
||||||
config["aprsd"]["email"]["imap"]["login"],
|
CONF.email_plugin.smtp_login,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -239,15 +242,15 @@ def _smtp_connect(config):
|
||||||
|
|
||||||
LOG.debug(f"Connected to smtp host {msg}")
|
LOG.debug(f"Connected to smtp host {msg}")
|
||||||
|
|
||||||
debug = config["aprsd"]["email"]["smtp"].get("debug", False)
|
debug = CONF.email_plugin.debug
|
||||||
if debug:
|
if debug:
|
||||||
server.set_debuglevel(5)
|
server.set_debuglevel(5)
|
||||||
server.sendmail = trace.trace(server.sendmail)
|
server.sendmail = trace.trace(server.sendmail)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server.login(
|
server.login(
|
||||||
config["aprsd"]["email"]["smtp"]["login"],
|
CONF.email_plugin.smtp_login,
|
||||||
config["aprsd"]["email"]["smtp"]["password"],
|
CONF.email_plugin.smtp_password,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.error("Couldn't connect to SMTP Server")
|
LOG.error("Couldn't connect to SMTP Server")
|
||||||
|
@ -257,22 +260,39 @@ def _smtp_connect(config):
|
||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
def get_email_from_shortcut(config, addr):
|
def _build_shortcuts_dict():
|
||||||
if config["aprsd"]["email"].get("shortcuts", False):
|
global shortcuts_dict
|
||||||
return config["aprsd"]["email"]["shortcuts"].get(addr, addr)
|
if not shortcuts_dict:
|
||||||
|
if CONF.email_plugin.email_shortcuts:
|
||||||
|
shortcuts_dict = {}
|
||||||
|
tmp = CONF.email_plugin.email_shortcuts
|
||||||
|
for combo in tmp:
|
||||||
|
entry = combo.split("=")
|
||||||
|
shortcuts_dict[entry[0]] = entry[1]
|
||||||
|
else:
|
||||||
|
shortcuts_dict = {}
|
||||||
|
|
||||||
|
return shortcuts_dict
|
||||||
|
|
||||||
|
|
||||||
|
def get_email_from_shortcut(addr):
|
||||||
|
if CONF.email_plugin.email_shortcuts:
|
||||||
|
shortcuts = _build_shortcuts_dict()
|
||||||
|
LOG.info(f"Shortcut lookup {addr} returns {shortcuts.get(addr, addr)}")
|
||||||
|
return shortcuts.get(addr, addr)
|
||||||
else:
|
else:
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
|
||||||
def validate_email_config(config, disable_validation=False):
|
def validate_email_config(disable_validation=False):
|
||||||
"""function to simply ensure we can connect to email services.
|
"""function to simply ensure we can connect to email services.
|
||||||
|
|
||||||
This helps with failing early during startup.
|
This helps with failing early during startup.
|
||||||
"""
|
"""
|
||||||
LOG.info("Checking IMAP configuration")
|
LOG.info("Checking IMAP configuration")
|
||||||
imap_server = _imap_connect(config)
|
imap_server = _imap_connect()
|
||||||
LOG.info("Checking SMTP configuration")
|
LOG.info("Checking SMTP configuration")
|
||||||
smtp_server = _smtp_connect(config)
|
smtp_server = _smtp_connect()
|
||||||
|
|
||||||
if imap_server and smtp_server:
|
if imap_server and smtp_server:
|
||||||
return True
|
return True
|
||||||
|
@ -376,16 +396,16 @@ def parse_email(msgid, data, server):
|
||||||
|
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def send_email(config, to_addr, content):
|
def send_email(to_addr, content):
|
||||||
shortcuts = config["aprsd"]["email"]["shortcuts"]
|
shortcuts = _build_shortcuts_dict()
|
||||||
email_address = get_email_from_shortcut(config, to_addr)
|
email_address = get_email_from_shortcut(to_addr)
|
||||||
LOG.info("Sending Email_________________")
|
LOG.info("Sending Email_________________")
|
||||||
|
|
||||||
if to_addr in shortcuts:
|
if to_addr in shortcuts:
|
||||||
LOG.info(f"To : {to_addr}")
|
LOG.info(f"To : {to_addr}")
|
||||||
to_addr = email_address
|
to_addr = email_address
|
||||||
LOG.info(f" ({to_addr})")
|
LOG.info(f" ({to_addr})")
|
||||||
subject = config["ham"]["callsign"]
|
subject = CONF.email_plugin.callsign
|
||||||
# content = content + "\n\n(NOTE: reply with one line)"
|
# content = content + "\n\n(NOTE: reply with one line)"
|
||||||
LOG.info(f"Subject : {subject}")
|
LOG.info(f"Subject : {subject}")
|
||||||
LOG.info(f"Body : {content}")
|
LOG.info(f"Body : {content}")
|
||||||
|
@ -395,13 +415,13 @@ def send_email(config, to_addr, content):
|
||||||
|
|
||||||
msg = MIMEText(content)
|
msg = MIMEText(content)
|
||||||
msg["Subject"] = subject
|
msg["Subject"] = subject
|
||||||
msg["From"] = config["aprsd"]["email"]["smtp"]["login"]
|
msg["From"] = CONF.email_plugin.smtp_login
|
||||||
msg["To"] = to_addr
|
msg["To"] = to_addr
|
||||||
server = _smtp_connect(config)
|
server = _smtp_connect()
|
||||||
if server:
|
if server:
|
||||||
try:
|
try:
|
||||||
server.sendmail(
|
server.sendmail(
|
||||||
config["aprsd"]["email"]["smtp"]["login"],
|
CONF.email_plugin.smtp_login,
|
||||||
[to_addr],
|
[to_addr],
|
||||||
msg.as_string(),
|
msg.as_string(),
|
||||||
)
|
)
|
||||||
|
@ -415,19 +435,19 @@ def send_email(config, to_addr, content):
|
||||||
|
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def resend_email(config, count, fromcall):
|
def resend_email(count, fromcall):
|
||||||
date = datetime.datetime.now()
|
date = datetime.datetime.now()
|
||||||
month = date.strftime("%B")[:3] # Nov, Mar, Apr
|
month = date.strftime("%B")[:3] # Nov, Mar, Apr
|
||||||
day = date.day
|
day = date.day
|
||||||
year = date.year
|
year = date.year
|
||||||
today = f"{day}-{month}-{year}"
|
today = f"{day}-{month}-{year}"
|
||||||
|
|
||||||
shortcuts = config["aprsd"]["email"]["shortcuts"]
|
shortcuts = _build_shortcuts_dict()
|
||||||
# swap key/value
|
# swap key/value
|
||||||
shortcuts_inverted = {v: k for k, v in shortcuts.items()}
|
shortcuts_inverted = {v: k for k, v in shortcuts.items()}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server = _imap_connect(config)
|
server = _imap_connect()
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.exception("Failed to Connect to IMAP. Cannot resend email ")
|
LOG.exception("Failed to Connect to IMAP. Cannot resend email ")
|
||||||
return
|
return
|
||||||
|
@ -467,7 +487,7 @@ def resend_email(config, count, fromcall):
|
||||||
reply = "-" + from_addr + " * " + body.decode(errors="ignore")
|
reply = "-" + from_addr + " * " + body.decode(errors="ignore")
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=fromcall,
|
to_call=fromcall,
|
||||||
message_text=reply,
|
message_text=reply,
|
||||||
),
|
),
|
||||||
|
@ -490,7 +510,7 @@ def resend_email(config, count, fromcall):
|
||||||
)
|
)
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=fromcall,
|
to_call=fromcall,
|
||||||
message_text=reply,
|
message_text=reply,
|
||||||
),
|
),
|
||||||
|
@ -504,9 +524,8 @@ def resend_email(config, count, fromcall):
|
||||||
|
|
||||||
|
|
||||||
class APRSDEmailThread(threads.APRSDThread):
|
class APRSDEmailThread(threads.APRSDThread):
|
||||||
def __init__(self, config):
|
def __init__(self):
|
||||||
super().__init__("EmailThread")
|
super().__init__("EmailThread")
|
||||||
self.config = config
|
|
||||||
self.past = datetime.datetime.now()
|
self.past = datetime.datetime.now()
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
|
@ -527,7 +546,7 @@ class APRSDEmailThread(threads.APRSDThread):
|
||||||
f"check_email_delay is {EmailInfo().delay} seconds ",
|
f"check_email_delay is {EmailInfo().delay} seconds ",
|
||||||
)
|
)
|
||||||
|
|
||||||
shortcuts = self.config["aprsd"]["email"]["shortcuts"]
|
shortcuts = _build_shortcuts_dict()
|
||||||
# swap key/value
|
# swap key/value
|
||||||
shortcuts_inverted = {v: k for k, v in shortcuts.items()}
|
shortcuts_inverted = {v: k for k, v in shortcuts.items()}
|
||||||
|
|
||||||
|
@ -538,7 +557,7 @@ class APRSDEmailThread(threads.APRSDThread):
|
||||||
today = f"{day}-{month}-{year}"
|
today = f"{day}-{month}-{year}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server = _imap_connect(self.config)
|
server = _imap_connect()
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.exception("IMAP Failed to connect")
|
LOG.exception("IMAP Failed to connect")
|
||||||
return True
|
return True
|
||||||
|
@ -611,8 +630,8 @@ class APRSDEmailThread(threads.APRSDThread):
|
||||||
# config ham.callsign
|
# config ham.callsign
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=self.config["ham"]["callsign"],
|
to_call=CONF.email_plugin.callsign,
|
||||||
message_text=reply,
|
message_text=reply,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,10 +2,13 @@ import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import packets, plugin, plugin_utils
|
from aprsd import packets, plugin, plugin_utils
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,9 +27,8 @@ class LocationPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
||||||
LOG.info("Location Plugin")
|
LOG.info("Location Plugin")
|
||||||
fromcall = packet.from_call
|
fromcall = packet.from_call
|
||||||
message = packet.get("message_text", None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
|
||||||
|
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
api_key = CONF.aprs_fi.apiKey
|
||||||
|
|
||||||
# optional second argument is a callsign to search
|
# optional second argument is a callsign to search
|
||||||
a = re.search(r"^.*\s+(.*)", message)
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import packets, plugin
|
from aprsd import packets, plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +23,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
|
||||||
def process(self, packet: packets.MessagePacket):
|
def process(self, packet: packets.MessagePacket):
|
||||||
LOG.info("NotifySeenPlugin")
|
LOG.info("NotifySeenPlugin")
|
||||||
|
|
||||||
notify_callsign = self.config["aprsd"]["watch_list"]["alert_callsign"]
|
notify_callsign = CONF.watch_list.alert_callsign
|
||||||
fromcall = packet.from_call
|
fromcall = packet.from_call
|
||||||
|
|
||||||
wl = packets.WatchList()
|
wl = packets.WatchList()
|
||||||
|
@ -38,7 +41,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
|
||||||
packet_type = packet.__class__.__name__
|
packet_type = packet.__class__.__name__
|
||||||
# we shouldn't notify the alert user that they are online.
|
# we shouldn't notify the alert user that they are online.
|
||||||
pkt = packets.MessagePacket(
|
pkt = packets.MessagePacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=notify_callsign,
|
to_call=notify_callsign,
|
||||||
message_text=(
|
message_text=(
|
||||||
f"{fromcall} was just seen by type:'{packet_type}'"
|
f"{fromcall} was just seen by type:'{packet_type}'"
|
||||||
|
|
|
@ -2,11 +2,14 @@ import datetime
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import packets, plugin
|
from aprsd import packets, plugin
|
||||||
from aprsd.packets import tracker
|
from aprsd.packets import tracker
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,13 +20,19 @@ class QueryPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
command_name = "query"
|
command_name = "query"
|
||||||
short_description = "APRSD Owner command to query messages in the MsgTrack"
|
short_description = "APRSD Owner command to query messages in the MsgTrack"
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""Do any plugin setup here."""
|
||||||
|
if not CONF.query_plugin.callsign:
|
||||||
|
LOG.error("Config query_plugin.callsign not set. Disabling plugin")
|
||||||
|
self.enabled = False
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet: packets.MessagePacket):
|
def process(self, packet: packets.MessagePacket):
|
||||||
LOG.info("Query COMMAND")
|
LOG.info("Query COMMAND")
|
||||||
|
|
||||||
fromcall = packet.from_call
|
fromcall = packet.from_call
|
||||||
message = packet.get("message_text", None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
|
||||||
|
|
||||||
pkt_tracker = tracker.PacketTrack()
|
pkt_tracker = tracker.PacketTrack()
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
@ -32,7 +41,7 @@ class QueryPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
now.strftime("%H:%M:%S"),
|
now.strftime("%H:%M:%S"),
|
||||||
)
|
)
|
||||||
|
|
||||||
searchstring = "^" + self.config["ham"]["callsign"] + ".*"
|
searchstring = "^" + CONF.query_plugin.callsign + ".*"
|
||||||
# only I can do admin commands
|
# only I can do admin commands
|
||||||
if re.search(searchstring, fromcall):
|
if re.search(searchstring, fromcall):
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,14 @@ import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from aprsd import packets, plugin, plugin_utils
|
from aprsd import packets, plugin, plugin_utils
|
||||||
from aprsd.utils import fuzzy, trace
|
from aprsd.utils import fuzzy, trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +76,7 @@ class TimeOWMPlugin(TimePlugin, plugin.APRSFIKEYMixin):
|
||||||
# if no second argument, search for calling station
|
# if no second argument, search for calling station
|
||||||
searchcall = fromcall
|
searchcall = fromcall
|
||||||
|
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
api_key = CONF.aprs_fi.apiKey
|
||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
|
|
@ -2,7 +2,6 @@ import logging
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import plugin, stats
|
from aprsd import plugin, stats
|
||||||
from aprsd.utils import trace
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
@ -19,7 +18,6 @@ class VersionPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
# five mins {int:int}
|
# five mins {int:int}
|
||||||
email_sent_dict = {}
|
email_sent_dict = {}
|
||||||
|
|
||||||
@trace.trace
|
|
||||||
def process(self, packet):
|
def process(self, packet):
|
||||||
LOG.info("Version COMMAND")
|
LOG.info("Version COMMAND")
|
||||||
# fromcall = packet.get("from")
|
# fromcall = packet.get("from")
|
||||||
|
@ -27,6 +25,7 @@ class VersionPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
stats_obj = stats.APRSDStats()
|
stats_obj = stats.APRSDStats()
|
||||||
s = stats_obj.stats()
|
s = stats_obj.stats()
|
||||||
|
print(s)
|
||||||
return "APRSD ver:{} uptime:{}".format(
|
return "APRSD ver:{} uptime:{}".format(
|
||||||
aprsd.__version__,
|
aprsd.__version__,
|
||||||
s["aprsd"]["uptime"],
|
s["aprsd"]["uptime"],
|
||||||
|
|
|
@ -2,12 +2,14 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from aprsd import plugin, plugin_utils
|
from aprsd import plugin, plugin_utils
|
||||||
from aprsd.utils import trace
|
from aprsd.utils import trace
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,10 +36,10 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet):
|
def process(self, packet):
|
||||||
LOG.info("Weather Plugin")
|
LOG.info("Weather Plugin")
|
||||||
fromcall = packet.get("from")
|
fromcall = packet.from_call
|
||||||
# message = packet.get("message_text", None)
|
# message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
api_key = CONF.aprs_fi.apiKey
|
||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
@ -58,6 +60,8 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
|
||||||
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
||||||
return "Unable to get weather"
|
return "Unable to get weather"
|
||||||
|
|
||||||
|
LOG.info(f"WX data {wx_data}")
|
||||||
|
|
||||||
reply = (
|
reply = (
|
||||||
"%sF(%sF/%sF) %s. %s, %s."
|
"%sF(%sF/%sF) %s. %s, %s."
|
||||||
% (
|
% (
|
||||||
|
@ -97,6 +101,7 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def process(self, packet):
|
def process(self, packet):
|
||||||
|
print("FISTY")
|
||||||
fromcall = packet.get("from")
|
fromcall = packet.get("from")
|
||||||
message = packet.get("message_text", None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
|
@ -119,13 +124,7 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
||||||
# if no second argument, search for calling station
|
# if no second argument, search for calling station
|
||||||
fromcall = fromcall
|
fromcall = fromcall
|
||||||
|
|
||||||
try:
|
api_key = CONF.aprs_fi.apiKey
|
||||||
self.config.exists(["services", "aprs.fi", "apiKey"])
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
|
||||||
return "No aprs.fi apikey found"
|
|
||||||
|
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
||||||
|
@ -187,6 +186,13 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
command_name = "OpenWeatherMap"
|
command_name = "OpenWeatherMap"
|
||||||
short_description = "OpenWeatherMap weather of GPS Beacon location"
|
short_description = "OpenWeatherMap weather of GPS Beacon location"
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
if not CONF.owm_weather_plugin.apiKey:
|
||||||
|
LOG.error("Config.owm_weather_plugin.apiKey is not set. Disabling")
|
||||||
|
self.enabled = False
|
||||||
|
else:
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
def help(self):
|
def help(self):
|
||||||
_help = [
|
_help = [
|
||||||
"openweathermap: Send {} to get weather "
|
"openweathermap: Send {} to get weather "
|
||||||
|
@ -209,13 +215,8 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
else:
|
else:
|
||||||
searchcall = fromcall
|
searchcall = fromcall
|
||||||
|
|
||||||
try:
|
api_key = CONF.aprs_fi.apiKey
|
||||||
self.config.exists(["services", "aprs.fi", "apiKey"])
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
|
||||||
return "No aprs.fi apikey found"
|
|
||||||
|
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
|
||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
@ -230,21 +231,8 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
lat = aprs_data["entries"][0]["lat"]
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
units = CONF.units
|
||||||
self.config.exists(["services", "openweathermap", "apiKey"])
|
api_key = CONF.owm_weather_plugin.apiKey
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(f"Failed to find config openweathermap:apiKey {ex}")
|
|
||||||
return "No openweathermap apiKey found"
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.config.exists(["aprsd", "units"])
|
|
||||||
except Exception:
|
|
||||||
LOG.debug("Couldn't find untis in aprsd:services:units")
|
|
||||||
units = "metric"
|
|
||||||
else:
|
|
||||||
units = self.config["aprsd"]["units"]
|
|
||||||
|
|
||||||
api_key = self.config["services"]["openweathermap"]["apiKey"]
|
|
||||||
try:
|
try:
|
||||||
wx_data = plugin_utils.fetch_openweathermap(
|
wx_data = plugin_utils.fetch_openweathermap(
|
||||||
api_key,
|
api_key,
|
||||||
|
@ -317,6 +305,16 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
command_name = "AVWXWeather"
|
command_name = "AVWXWeather"
|
||||||
short_description = "AVWX weather of GPS Beacon location"
|
short_description = "AVWX weather of GPS Beacon location"
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
if not CONF.avwx_plugin.base_url:
|
||||||
|
LOG.error("Config avwx_plugin.base_url not specified. Disabling")
|
||||||
|
return False
|
||||||
|
elif not CONF.avwx_plugin.apiKey:
|
||||||
|
LOG.error("Config avwx_plugin.apiKey not specified. Disabling")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
def help(self):
|
def help(self):
|
||||||
_help = [
|
_help = [
|
||||||
"avwxweather: Send {} to get weather "
|
"avwxweather: Send {} to get weather "
|
||||||
|
@ -339,13 +337,7 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
else:
|
else:
|
||||||
searchcall = fromcall
|
searchcall = fromcall
|
||||||
|
|
||||||
try:
|
api_key = CONF.aprs_fi.apiKey
|
||||||
self.config.exists(["services", "aprs.fi", "apiKey"])
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
|
||||||
return "No aprs.fi apikey found"
|
|
||||||
|
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
|
||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
@ -360,21 +352,8 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
lat = aprs_data["entries"][0]["lat"]
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
api_key = CONF.avwx_plugin.apiKey
|
||||||
self.config.exists(["services", "avwx", "apiKey"])
|
base_url = CONF.avwx_plugin.base_url
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(f"Failed to find config avwx:apiKey {ex}")
|
|
||||||
return "No avwx apiKey found"
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.config.exists(self.config, ["services", "avwx", "base_url"])
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.debug(f"Didn't find avwx:base_url {ex}")
|
|
||||||
base_url = "https://avwx.rest"
|
|
||||||
else:
|
|
||||||
base_url = self.config["services"]["avwx"]["base_url"]
|
|
||||||
|
|
||||||
api_key = self.config["services"]["avwx"]["apiKey"]
|
|
||||||
token = f"TOKEN {api_key}"
|
token = f"TOKEN {api_key}"
|
||||||
headers = {"Authorization": token}
|
headers = {"Authorization": token}
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
import rpyc
|
||||||
|
from rpyc.utils.authenticators import AuthenticationError
|
||||||
|
from rpyc.utils.server import ThreadPoolServer
|
||||||
|
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
|
from aprsd import packets, stats, threads
|
||||||
|
from aprsd.threads import log_monitor
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
def magic_word_authenticator(sock):
|
||||||
|
magic = sock.recv(len(CONF.rpc_settings.magic_word)).decode()
|
||||||
|
if magic != CONF.rpc_settings.magic_word:
|
||||||
|
raise AuthenticationError(f"wrong magic word {magic}")
|
||||||
|
return sock, None
|
||||||
|
|
||||||
|
|
||||||
|
class APRSDRPCThread(threads.APRSDThread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(name="RPCThread")
|
||||||
|
self.thread = ThreadPoolServer(
|
||||||
|
APRSDService,
|
||||||
|
port=CONF.rpc_settings.port,
|
||||||
|
protocol_config={"allow_public_attrs": True},
|
||||||
|
authenticator=magic_word_authenticator,
|
||||||
|
)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.thread:
|
||||||
|
self.thread.close()
|
||||||
|
self.thread_stop = True
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
# there is no loop as run is blocked
|
||||||
|
if self.thread and not self.thread_stop:
|
||||||
|
# This is a blocking call
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
@rpyc.service
|
||||||
|
class APRSDService(rpyc.Service):
|
||||||
|
def on_connect(self, conn):
|
||||||
|
# code that runs when a connection is created
|
||||||
|
# (to init the service, if needed)
|
||||||
|
LOG.info("Connected")
|
||||||
|
self._conn = conn
|
||||||
|
|
||||||
|
def on_disconnect(self, conn):
|
||||||
|
# code that runs after the connection has already closed
|
||||||
|
# (to finalize the service, if needed)
|
||||||
|
LOG.info("Disconnected")
|
||||||
|
self._conn = None
|
||||||
|
|
||||||
|
@rpyc.exposed
|
||||||
|
def get_stats(self):
|
||||||
|
stat = stats.APRSDStats()
|
||||||
|
stats_dict = stat.stats()
|
||||||
|
return json.dumps(stats_dict, indent=4, sort_keys=True, default=str)
|
||||||
|
|
||||||
|
@rpyc.exposed
|
||||||
|
def get_stats_obj(self):
|
||||||
|
return stats.APRSDStats()
|
||||||
|
|
||||||
|
@rpyc.exposed
|
||||||
|
def get_packet_list(self):
|
||||||
|
return packets.PacketList()
|
||||||
|
|
||||||
|
@rpyc.exposed
|
||||||
|
def get_packet_track(self):
|
||||||
|
return packets.PacketTrack()
|
||||||
|
|
||||||
|
@rpyc.exposed
|
||||||
|
def get_watch_list(self):
|
||||||
|
return packets.WatchList()
|
||||||
|
|
||||||
|
@rpyc.exposed
|
||||||
|
def get_seen_list(self):
|
||||||
|
return packets.SeenList()
|
||||||
|
|
||||||
|
@rpyc.exposed
|
||||||
|
def get_log_entries(self):
|
||||||
|
entries = log_monitor.LogEntries().get_all_and_purge()
|
||||||
|
return json.dumps(entries, default=str)
|
|
@ -2,12 +2,14 @@ import datetime
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import packets, plugin, utils
|
from aprsd import packets, plugin, utils
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +17,6 @@ class APRSDStats:
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
config = None
|
|
||||||
|
|
||||||
start_time = None
|
start_time = None
|
||||||
_aprsis_server = None
|
_aprsis_server = None
|
||||||
|
@ -62,15 +63,11 @@ class APRSDStats:
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
# any initializetion here
|
# any init here
|
||||||
cls._instance.start_time = datetime.datetime.now()
|
cls._instance.start_time = datetime.datetime.now()
|
||||||
cls._instance._aprsis_keepalive = datetime.datetime.now()
|
cls._instance._aprsis_keepalive = datetime.datetime.now()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, config=None):
|
|
||||||
if config:
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
@wrapt.synchronized(lock)
|
||||||
@property
|
@property
|
||||||
def uptime(self):
|
def uptime(self):
|
||||||
|
@ -191,7 +188,7 @@ class APRSDStats:
|
||||||
"aprsd": {
|
"aprsd": {
|
||||||
"version": aprsd.__version__,
|
"version": aprsd.__version__,
|
||||||
"uptime": utils.strfdelta(self.uptime),
|
"uptime": utils.strfdelta(self.uptime),
|
||||||
"callsign": self.config["aprsd"]["callsign"],
|
"callsign": CONF.callsign,
|
||||||
"memory_current": int(self.memory),
|
"memory_current": int(self.memory),
|
||||||
"memory_current_str": utils.human_size(self.memory),
|
"memory_current_str": utils.human_size(self.memory),
|
||||||
"memory_peak": int(self.memory_peak),
|
"memory_peak": int(self.memory_peak),
|
||||||
|
@ -201,7 +198,7 @@ class APRSDStats:
|
||||||
},
|
},
|
||||||
"aprs-is": {
|
"aprs-is": {
|
||||||
"server": str(self.aprsis_server),
|
"server": str(self.aprsis_server),
|
||||||
"callsign": self.config["aprs"]["login"],
|
"callsign": CONF.aprs_network.login,
|
||||||
"last_update": last_aprsis_keepalive,
|
"last_update": last_aprsis_keepalive,
|
||||||
},
|
},
|
||||||
"packets": {
|
"packets": {
|
||||||
|
@ -215,7 +212,7 @@ class APRSDStats:
|
||||||
"ack_sent": self._pkt_cnt["AckPacket"]["tx"],
|
"ack_sent": self._pkt_cnt["AckPacket"]["tx"],
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"enabled": self.config["aprsd"]["email"]["enabled"],
|
"enabled": CONF.email_plugin.enabled,
|
||||||
"sent": int(self._email_tx),
|
"sent": int(self._email_tx),
|
||||||
"received": int(self._email_rx),
|
"received": int(self._email_rx),
|
||||||
"thread_last_update": last_update,
|
"thread_last_update": last_update,
|
||||||
|
|
|
@ -3,10 +3,13 @@ import logging
|
||||||
import time
|
import time
|
||||||
import tracemalloc
|
import tracemalloc
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import client, packets, stats, utils
|
from aprsd import client, packets, stats, utils
|
||||||
from aprsd.threads import APRSDThread, APRSDThreadList
|
from aprsd.threads import APRSDThread, APRSDThreadList
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,10 +17,9 @@ class KeepAliveThread(APRSDThread):
|
||||||
cntr = 0
|
cntr = 0
|
||||||
checker_time = datetime.datetime.now()
|
checker_time = datetime.datetime.now()
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self):
|
||||||
tracemalloc.start()
|
tracemalloc.start()
|
||||||
super().__init__("KeepAlive")
|
super().__init__("KeepAlive")
|
||||||
self.config = config
|
|
||||||
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
|
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
|
||||||
self.max_delta = datetime.timedelta(**max_timeout)
|
self.max_delta = datetime.timedelta(**max_timeout)
|
||||||
|
|
||||||
|
@ -40,15 +42,9 @@ class KeepAliveThread(APRSDThread):
|
||||||
stats_obj.set_memory(current)
|
stats_obj.set_memory(current)
|
||||||
stats_obj.set_memory_peak(peak)
|
stats_obj.set_memory_peak(peak)
|
||||||
|
|
||||||
try:
|
login = CONF.callsign
|
||||||
login = self.config["aprsd"]["callsign"]
|
|
||||||
except KeyError:
|
|
||||||
login = self.config["ham"]["callsign"]
|
|
||||||
|
|
||||||
if pkt_tracker.is_initialized():
|
tracked_packets = len(pkt_tracker)
|
||||||
tracked_packets = len(pkt_tracker)
|
|
||||||
else:
|
|
||||||
tracked_packets = 0
|
|
||||||
|
|
||||||
keepalive = (
|
keepalive = (
|
||||||
"{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
|
"{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
|
||||||
|
@ -77,7 +73,7 @@ class KeepAliveThread(APRSDThread):
|
||||||
if delta > self.max_delta:
|
if delta > self.max_delta:
|
||||||
# We haven't gotten a keepalive from aprs-is in a while
|
# We haven't gotten a keepalive from aprs-is in a while
|
||||||
# reset the connection.a
|
# reset the connection.a
|
||||||
if not client.KISSClient.is_enabled(self.config):
|
if not client.KISSClient.is_enabled():
|
||||||
LOG.warning(f"Resetting connection to APRS-IS {delta}")
|
LOG.warning(f"Resetting connection to APRS-IS {delta}")
|
||||||
client.factory.create().reset()
|
client.factory.create().reset()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import wrapt
|
||||||
|
|
||||||
|
from aprsd import threads
|
||||||
|
from aprsd.logging import log
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class LogEntries:
|
||||||
|
entries = []
|
||||||
|
lock = threading.Lock()
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
@wrapt.synchronized(lock)
|
||||||
|
def add(self, entry):
|
||||||
|
self.entries.append(entry)
|
||||||
|
|
||||||
|
@wrapt.synchronized(lock)
|
||||||
|
def get_all_and_purge(self):
|
||||||
|
entries = self.entries.copy()
|
||||||
|
self.entries = []
|
||||||
|
return entries
|
||||||
|
|
||||||
|
@wrapt.synchronized(lock)
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.entries)
|
||||||
|
|
||||||
|
|
||||||
|
class LogMonitorThread(threads.APRSDThread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("LogMonitorThread")
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
try:
|
||||||
|
record = log.logging_queue.get(block=True, timeout=2)
|
||||||
|
if isinstance(record, list):
|
||||||
|
for item in record:
|
||||||
|
entry = self.json_record(item)
|
||||||
|
LogEntries().add(entry)
|
||||||
|
else:
|
||||||
|
entry = self.json_record(record)
|
||||||
|
LogEntries().add(entry)
|
||||||
|
except Exception:
|
||||||
|
# Just ignore thi
|
||||||
|
pass
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def json_record(self, record):
|
||||||
|
entry = {}
|
||||||
|
entry["filename"] = record.filename
|
||||||
|
entry["funcName"] = record.funcName
|
||||||
|
entry["levelname"] = record.levelname
|
||||||
|
entry["lineno"] = record.lineno
|
||||||
|
entry["module"] = record.module
|
||||||
|
entry["name"] = record.name
|
||||||
|
entry["pathname"] = record.pathname
|
||||||
|
entry["process"] = record.process
|
||||||
|
entry["processName"] = record.processName
|
||||||
|
if hasattr(record, "stack_info"):
|
||||||
|
entry["stack_info"] = record.stack_info
|
||||||
|
else:
|
||||||
|
entry["stack_info"] = None
|
||||||
|
entry["thread"] = record.thread
|
||||||
|
entry["threadName"] = record.threadName
|
||||||
|
entry["message"] = record.getMessage()
|
||||||
|
return entry
|
|
@ -4,18 +4,19 @@ import queue
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import aprslib
|
import aprslib
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import client, packets, plugin
|
from aprsd import client, packets, plugin
|
||||||
from aprsd.threads import APRSDThread, tx
|
from aprsd.threads import APRSDThread, tx
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
class APRSDRXThread(APRSDThread):
|
class APRSDRXThread(APRSDThread):
|
||||||
def __init__(self, config, packet_queue):
|
def __init__(self, packet_queue):
|
||||||
super().__init__("RX_MSG")
|
super().__init__("RX_MSG")
|
||||||
self.config = config
|
|
||||||
self.packet_queue = packet_queue
|
self.packet_queue = packet_queue
|
||||||
self._client = client.factory.create()
|
self._client = client.factory.create()
|
||||||
|
|
||||||
|
@ -80,8 +81,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
||||||
will ack a message before sending the packet to the subclass
|
will ack a message before sending the packet to the subclass
|
||||||
for processing."""
|
for processing."""
|
||||||
|
|
||||||
def __init__(self, config, packet_queue):
|
def __init__(self, packet_queue):
|
||||||
self.config = config
|
|
||||||
self.packet_queue = packet_queue
|
self.packet_queue = packet_queue
|
||||||
super().__init__("ProcessPKT")
|
super().__init__("ProcessPKT")
|
||||||
self._loop_cnt = 1
|
self._loop_cnt = 1
|
||||||
|
@ -106,7 +106,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
||||||
def process_packet(self, packet):
|
def process_packet(self, packet):
|
||||||
"""Process a packet received from aprs-is server."""
|
"""Process a packet received from aprs-is server."""
|
||||||
LOG.debug(f"RXPKT-LOOP {self._loop_cnt}")
|
LOG.debug(f"RXPKT-LOOP {self._loop_cnt}")
|
||||||
our_call = self.config["aprsd"]["callsign"].lower()
|
our_call = CONF.callsign.lower()
|
||||||
|
|
||||||
from_call = packet.from_call
|
from_call = packet.from_call
|
||||||
if packet.addresse:
|
if packet.addresse:
|
||||||
|
@ -133,7 +133,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
||||||
# send an ack last
|
# send an ack last
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.AckPacket(
|
packets.AckPacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=from_call,
|
to_call=from_call,
|
||||||
msgNo=msg_id,
|
msgNo=msg_id,
|
||||||
),
|
),
|
||||||
|
@ -178,11 +178,11 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
||||||
if isinstance(subreply, packets.Packet):
|
if isinstance(subreply, packets.Packet):
|
||||||
tx.send(subreply)
|
tx.send(subreply)
|
||||||
else:
|
else:
|
||||||
wl = self.config["aprsd"]["watch_list"]
|
wl = CONF.watch_list
|
||||||
to_call = wl["alert_callsign"]
|
to_call = wl["alert_callsign"]
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=to_call,
|
to_call=to_call,
|
||||||
message_text=subreply,
|
message_text=subreply,
|
||||||
),
|
),
|
||||||
|
@ -219,7 +219,7 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
||||||
else:
|
else:
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=from_call,
|
to_call=from_call,
|
||||||
message_text=subreply,
|
message_text=subreply,
|
||||||
),
|
),
|
||||||
|
@ -238,7 +238,7 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
||||||
LOG.debug(f"Sending '{reply}'")
|
LOG.debug(f"Sending '{reply}'")
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=from_call,
|
to_call=from_call,
|
||||||
message_text=reply,
|
message_text=reply,
|
||||||
),
|
),
|
||||||
|
@ -246,12 +246,12 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
||||||
|
|
||||||
# If the message was for us and we didn't have a
|
# If the message was for us and we didn't have a
|
||||||
# response, then we send a usage statement.
|
# response, then we send a usage statement.
|
||||||
if to_call == self.config["aprsd"]["callsign"] and not replied:
|
if to_call == CONF.callsign and not replied:
|
||||||
LOG.warning("Sending help!")
|
LOG.warning("Sending help!")
|
||||||
message_text = "Unknown command! Send 'help' message for help"
|
message_text = "Unknown command! Send 'help' message for help"
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=from_call,
|
to_call=from_call,
|
||||||
message_text=message_text,
|
message_text=message_text,
|
||||||
),
|
),
|
||||||
|
@ -260,11 +260,11 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
||||||
LOG.error("Plugin failed!!!")
|
LOG.error("Plugin failed!!!")
|
||||||
LOG.exception(ex)
|
LOG.exception(ex)
|
||||||
# Do we need to send a reply?
|
# Do we need to send a reply?
|
||||||
if to_call == self.config["aprsd"]["callsign"]:
|
if to_call == CONF.callsign:
|
||||||
reply = "A Plugin failed! try again?"
|
reply = "A Plugin failed! try again?"
|
||||||
tx.send(
|
tx.send(
|
||||||
packets.MessagePacket(
|
packets.MessagePacket(
|
||||||
from_call=self.config["aprsd"]["callsign"],
|
from_call=CONF.callsign,
|
||||||
to_call=from_call,
|
to_call=from_call,
|
||||||
message_text=reply,
|
message_text=reply,
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,7 +4,7 @@ import time
|
||||||
|
|
||||||
from aprsd import client
|
from aprsd import client
|
||||||
from aprsd import threads as aprsd_threads
|
from aprsd import threads as aprsd_threads
|
||||||
from aprsd.packets import core, packet_list, tracker
|
from aprsd.packets import core, tracker
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
@ -27,9 +27,9 @@ def send(packet: core.Packet, direct=False, aprs_client=None):
|
||||||
else:
|
else:
|
||||||
cl = client.factory.create()
|
cl = client.factory.create()
|
||||||
|
|
||||||
|
packet.update_timestamp()
|
||||||
packet.log(header="TX")
|
packet.log(header="TX")
|
||||||
cl.send(packet)
|
cl.send(packet)
|
||||||
packet_list.PacketList().tx(packet)
|
|
||||||
|
|
||||||
|
|
||||||
class SendPacketThread(aprsd_threads.APRSDThread):
|
class SendPacketThread(aprsd_threads.APRSDThread):
|
||||||
|
@ -94,8 +94,8 @@ class SendPacketThread(aprsd_threads.APRSDThread):
|
||||||
if send_now:
|
if send_now:
|
||||||
# no attempt time, so lets send it, and start
|
# no attempt time, so lets send it, and start
|
||||||
# tracking the time.
|
# tracking the time.
|
||||||
send(packet, direct=True)
|
|
||||||
packet.last_send_time = datetime.datetime.now()
|
packet.last_send_time = datetime.datetime.now()
|
||||||
|
send(packet, direct=True)
|
||||||
packet.send_count += 1
|
packet.send_count += 1
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import abc
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
class ObjectStoreMixin(metaclass=abc.ABCMeta):
|
class ObjectStoreMixin:
|
||||||
"""Class 'MIXIN' intended to save/load object data.
|
"""Class 'MIXIN' intended to save/load object data.
|
||||||
|
|
||||||
The asumption of how this mixin is used:
|
The asumption of how this mixin is used:
|
||||||
|
@ -24,13 +24,6 @@ class ObjectStoreMixin(metaclass=abc.ABCMeta):
|
||||||
When APRSD Starts, it calls load()
|
When APRSD Starts, it calls load()
|
||||||
aprsd server -f (flush) will wipe all saved objects.
|
aprsd server -f (flush) will wipe all saved objects.
|
||||||
"""
|
"""
|
||||||
@abc.abstractmethod
|
|
||||||
def is_initialized(self):
|
|
||||||
"""Return True if the class has been setup correctly.
|
|
||||||
|
|
||||||
If this returns False, the ObjectStore doesn't save anything.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.data)
|
return len(self.data)
|
||||||
|
@ -44,25 +37,18 @@ class ObjectStoreMixin(metaclass=abc.ABCMeta):
|
||||||
return self.data[id]
|
return self.data[id]
|
||||||
|
|
||||||
def _init_store(self):
|
def _init_store(self):
|
||||||
if self.is_initialized():
|
if not CONF.enable_save:
|
||||||
sl = self._save_location()
|
return
|
||||||
if not os.path.exists(sl):
|
sl = CONF.save_location
|
||||||
LOG.warning(f"Save location {sl} doesn't exist")
|
if not os.path.exists(sl):
|
||||||
try:
|
LOG.warning(f"Save location {sl} doesn't exist")
|
||||||
os.makedirs(sl)
|
try:
|
||||||
except Exception as ex:
|
os.makedirs(sl)
|
||||||
LOG.exception(ex)
|
except Exception as ex:
|
||||||
else:
|
LOG.exception(ex)
|
||||||
LOG.warning(f"{self.__class__.__name__} is not initialized")
|
|
||||||
|
|
||||||
def _save_location(self):
|
|
||||||
save_location = self.config.get("aprsd.save_location", None)
|
|
||||||
if not save_location:
|
|
||||||
save_location = aprsd_config.DEFAULT_CONFIG_DIR
|
|
||||||
return save_location
|
|
||||||
|
|
||||||
def _save_filename(self):
|
def _save_filename(self):
|
||||||
save_location = self._save_location()
|
save_location = CONF.save_location
|
||||||
|
|
||||||
return "{}/{}.p".format(
|
return "{}/{}.p".format(
|
||||||
save_location,
|
save_location,
|
||||||
|
@ -79,45 +65,48 @@ class ObjectStoreMixin(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save any queued to disk?"""
|
"""Save any queued to disk?"""
|
||||||
if self.is_initialized():
|
if not CONF.enable_save:
|
||||||
if len(self) > 0:
|
return
|
||||||
LOG.info(
|
if len(self) > 0:
|
||||||
f"{self.__class__.__name__}::Saving"
|
LOG.info(
|
||||||
f" {len(self)} entries to disk at"
|
f"{self.__class__.__name__}::Saving"
|
||||||
f"{self._save_location()}",
|
f" {len(self)} entries to disk at"
|
||||||
)
|
f"{CONF.save_location}",
|
||||||
with open(self._save_filename(), "wb+") as fp:
|
)
|
||||||
pickle.dump(self._dump(), fp)
|
with open(self._save_filename(), "wb+") as fp:
|
||||||
else:
|
pickle.dump(self._dump(), fp)
|
||||||
LOG.debug(
|
else:
|
||||||
"{} Nothing to save, flushing old save file '{}'".format(
|
LOG.debug(
|
||||||
self.__class__.__name__,
|
"{} Nothing to save, flushing old save file '{}'".format(
|
||||||
self._save_filename(),
|
self.__class__.__name__,
|
||||||
),
|
self._save_filename(),
|
||||||
)
|
),
|
||||||
self.flush()
|
)
|
||||||
|
self.flush()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if self.is_initialized():
|
if not CONF.enable_save:
|
||||||
if os.path.exists(self._save_filename()):
|
return
|
||||||
try:
|
if os.path.exists(self._save_filename()):
|
||||||
with open(self._save_filename(), "rb") as fp:
|
try:
|
||||||
raw = pickle.load(fp)
|
with open(self._save_filename(), "rb") as fp:
|
||||||
if raw:
|
raw = pickle.load(fp)
|
||||||
self.data = raw
|
if raw:
|
||||||
LOG.debug(
|
self.data = raw
|
||||||
f"{self.__class__.__name__}::Loaded {len(self)} entries from disk.",
|
LOG.debug(
|
||||||
)
|
f"{self.__class__.__name__}::Loaded {len(self)} entries from disk.",
|
||||||
LOG.debug(f"{self.data}")
|
)
|
||||||
except (pickle.UnpicklingError, Exception) as ex:
|
LOG.debug(f"{self.data}")
|
||||||
LOG.error(f"Failed to UnPickle {self._save_filename()}")
|
except (pickle.UnpicklingError, Exception) as ex:
|
||||||
LOG.error(ex)
|
LOG.error(f"Failed to UnPickle {self._save_filename()}")
|
||||||
self.data = {}
|
LOG.error(ex)
|
||||||
|
self.data = {}
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""Nuke the old pickle file that stored the old results from last aprsd run."""
|
"""Nuke the old pickle file that stored the old results from last aprsd run."""
|
||||||
if self.is_initialized():
|
if not CONF.enable_save:
|
||||||
if os.path.exists(self._save_filename()):
|
return
|
||||||
pathlib.Path(self._save_filename()).unlink()
|
if os.path.exists(self._save_filename()):
|
||||||
with self.lock:
|
pathlib.Path(self._save_filename()).unlink()
|
||||||
self.data = {}
|
with self.lock:
|
||||||
|
self.data = {}
|
||||||
|
|
|
@ -109,16 +109,17 @@ function update_packets( data ) {
|
||||||
}
|
}
|
||||||
jQuery.each(data, function(i, val) {
|
jQuery.each(data, function(i, val) {
|
||||||
pkt = JSON.parse(val);
|
pkt = JSON.parse(val);
|
||||||
|
|
||||||
update_watchlist_from_packet(pkt['from_call'], pkt);
|
update_watchlist_from_packet(pkt['from_call'], pkt);
|
||||||
if ( packet_list.hasOwnProperty(val["timestamp"]) == false ) {
|
if ( packet_list.hasOwnProperty(pkt.timestamp) == false ) {
|
||||||
// Store the packet
|
// Store the packet
|
||||||
packet_list[pkt["timestamp"]] = pkt;
|
packet_list[pkt.timestamp] = pkt;
|
||||||
//ts_str = val["timestamp"].toString();
|
//ts_str = val["timestamp"].toString();
|
||||||
//ts = ts_str.split(".")[0]*1000;
|
//ts = ts_str.split(".")[0]*1000;
|
||||||
ts = pkt["timestamp"]
|
ts = pkt.timestamp
|
||||||
var d = new Date(ts).toLocaleDateString("en-US");
|
var d = new Date(ts).toLocaleDateString("en-US");
|
||||||
var t = new Date(ts).toLocaleTimeString("en-US");
|
var t = new Date(ts).toLocaleTimeString("en-US");
|
||||||
var from_call = pkt['from_call'];
|
var from_call = pkt.from_call;
|
||||||
if (from_call == our_callsign) {
|
if (from_call == our_callsign) {
|
||||||
title_id = 'title_tx';
|
title_id = 'title_tx';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -28,36 +28,6 @@ function init_messages() {
|
||||||
update_msg(msg);
|
update_msg(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#sendform").submit(function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
var $checkboxes = $(this).find('input[type=checkbox]');
|
|
||||||
|
|
||||||
//loop through the checkboxes and change to hidden fields
|
|
||||||
$checkboxes.each(function() {
|
|
||||||
if ($(this)[0].checked) {
|
|
||||||
$(this).attr('type', 'hidden');
|
|
||||||
$(this).val(1);
|
|
||||||
} else {
|
|
||||||
$(this).attr('type', 'hidden');
|
|
||||||
$(this).val(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
msg = {'from': $('#from').val(),
|
|
||||||
'password': $('#password').val(),
|
|
||||||
'to': $('#to').val(),
|
|
||||||
'message': $('#message').val(),
|
|
||||||
'wait_reply': $('#wait_reply').val(),
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.emit("send", msg);
|
|
||||||
|
|
||||||
//loop through the checkboxes and change to hidden fields
|
|
||||||
$checkboxes.each(function() {
|
|
||||||
$(this).attr('type', 'checkbox');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_msg(msg) {
|
function add_msg(msg) {
|
||||||
|
|
|
@ -82,7 +82,6 @@
|
||||||
<div class="item" data-tab="watch-tab">Watch List</div>
|
<div class="item" data-tab="watch-tab">Watch List</div>
|
||||||
<div class="item" data-tab="plugin-tab">Plugins</div>
|
<div class="item" data-tab="plugin-tab">Plugins</div>
|
||||||
<div class="item" data-tab="config-tab">Config</div>
|
<div class="item" data-tab="config-tab">Config</div>
|
||||||
<div class="item" data-tab="send-tab">Send Message</div>
|
|
||||||
<div class="item" data-tab="log-tab">LogFile</div>
|
<div class="item" data-tab="log-tab">LogFile</div>
|
||||||
<div class="item" data-tab="raw-tab">Raw JSON</div>
|
<div class="item" data-tab="raw-tab">Raw JSON</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -160,29 +159,6 @@
|
||||||
<pre id="configjson" class="language-json">{{ config_json|safe }}</pre>
|
<pre id="configjson" class="language-json">{{ config_json|safe }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="send-tab">
|
|
||||||
<h3 class="ui dividing header">Send Message</h3>
|
|
||||||
<div id="sendMsgDiv" class="ui mini text">
|
|
||||||
<form id="sendform" name="sendmsg" action="">
|
|
||||||
<p><label for="from_call">From Callsign:</label>
|
|
||||||
<input type="text" name="from_call" id="from"></p>
|
|
||||||
<p><label for="from_call_password">Password:</label>
|
|
||||||
<input type="password" name="from_call_password" id='password'></p>
|
|
||||||
<p><label for="to_call">To Callsign:</label>
|
|
||||||
<input type="text" name="to_call" id="to" ></p>
|
|
||||||
<p><label for="message">Message:</label>
|
|
||||||
<input type="text" name="message" id="message" ></p>
|
|
||||||
<p><label for="wait">Wait for Reply?</label>
|
|
||||||
<input type="checkbox" name="wait_reply" id="wait_reply" value="off" checked>
|
|
||||||
</p>
|
|
||||||
<input type="submit" name="submit" class="button" id="send_msg" value="Send" />
|
|
||||||
</form>
|
|
||||||
<div class="ui styled fluid accordion" id="accordion">
|
|
||||||
<div id="sendMsgsDiv" class="ui mini text">Messages</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="log-tab">
|
<div class="ui bottom attached tab segment" data-tab="log-tab">
|
||||||
<h3 class="ui dividing header">LOGFILE</h3>
|
<h3 class="ui dividing header">LOGFILE</h3>
|
||||||
<pre id="logContainer" style="height: 600px;overflow-y:auto;overflow-x:auto;"><code id="logtext" class="language-log" ></code></pre>
|
<pre id="logContainer" style="height: 600px;overflow-y:auto;overflow-x:auto;"><code id="logtext" class="language-log" ></code></pre>
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
|
|
||||||
<script src="json-viewer/jquery.json-viewer.js"></script>
|
|
||||||
<link href="json-viewer/jquery.json-viewer.css" type="text/css" rel="stylesheet" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<pre id="json-viewer"></pre>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var data = {{ messages | safe }}
|
|
||||||
$('#json-viewer').jsonViewer(data)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,74 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery-simple-websocket@1.1.4/src/jquery.simple.websocket.min.js"></script>
|
|
||||||
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/prism.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-json.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/themes/prism-tomorrow.css">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/index.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/tabs.css">
|
|
||||||
<script src="/static/js/send-message.js"></script>
|
|
||||||
|
|
||||||
<script language="JavaScript">
|
|
||||||
$(document).ready(function() {
|
|
||||||
init_messages();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='ui text container'>
|
|
||||||
<h1 class='ui dividing header'>APRSD {{ version }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='ui grid text container'>
|
|
||||||
<div class='left floated ten wide column'>
|
|
||||||
<span style='color: green'>{{ callsign }}</span>
|
|
||||||
connected to
|
|
||||||
<span style='color: blue' id='aprsis'>NONE</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='right floated four wide column'>
|
|
||||||
<span id='uptime'>NONE</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="ui dividing header">Send Message Form</h3>
|
|
||||||
<form id="sendform" name="sendmsg" action="">
|
|
||||||
<p><label for="from_call">From Callsign:</label>
|
|
||||||
<input type="text" name="from_call" id="from" value="WB4BOR"></p>
|
|
||||||
<p><label for="from_call_password">Password:</label>
|
|
||||||
<input type="password" name="from_call_password" id='password' value="24496"></p>
|
|
||||||
|
|
||||||
<p><label for="to_call">To Callsign:</label>
|
|
||||||
<input type="text" name="to_call" id="to" value="WB4BOR-11"></p>
|
|
||||||
|
|
||||||
<p><label for="message">Message:</label>
|
|
||||||
<input type="text" name="message" id="message" value="ping"></p>
|
|
||||||
|
|
||||||
<p><label for="wait">Wait for Reply?</label>
|
|
||||||
<input type="checkbox" name="wait_reply" id="wait_reply" value="off" checked>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<input type="submit" name="submit" class="button" id="send_msg" value="Send" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h3 class="ui dividing header">Messages (<span id="msgs_count">0</span>)</h3>
|
|
||||||
<div class="ui styled fluid accordion" id="accordion">
|
|
||||||
<div id="msgsDiv" class="ui mini text">Messages</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,12 +1,12 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.9
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile --annotation-style=line --resolver=backtracking dev-requirements.in
|
# pip-compile --annotation-style=line --resolver=backtracking dev-requirements.in
|
||||||
#
|
#
|
||||||
add-trailing-comma==2.4.0 # via gray
|
add-trailing-comma==2.4.0 # via gray
|
||||||
alabaster==0.7.12 # via sphinx
|
alabaster==0.7.12 # via sphinx
|
||||||
attrs==22.1.0 # via jsonschema, pytest
|
attrs==22.2.0 # via jsonschema, pytest
|
||||||
autoflake==1.5.3 # via gray
|
autoflake==1.5.3 # via gray
|
||||||
babel==2.11.0 # via sphinx
|
babel==2.11.0 # via sphinx
|
||||||
black==22.12.0 # via gray
|
black==22.12.0 # via gray
|
||||||
|
@ -20,21 +20,20 @@ click==8.1.3 # via black, pip-tools
|
||||||
colorama==0.4.6 # via tox
|
colorama==0.4.6 # via tox
|
||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
configargparse==1.5.3 # via gray
|
configargparse==1.5.3 # via gray
|
||||||
coverage[toml]==6.5.0 # via pytest-cov
|
coverage[toml]==7.0.1 # via pytest-cov
|
||||||
distlib==0.3.6 # via virtualenv
|
distlib==0.3.6 # via virtualenv
|
||||||
docutils==0.19 # via sphinx
|
docutils==0.19 # via sphinx
|
||||||
exceptiongroup==1.0.4 # via pytest
|
exceptiongroup==1.1.0 # via pytest
|
||||||
filelock==3.8.2 # via tox, virtualenv
|
filelock==3.8.2 # via tox, virtualenv
|
||||||
fixit==0.1.4 # via gray
|
fixit==0.1.4 # via gray
|
||||||
flake8==6.0.0 # via -r dev-requirements.in, fixit, pep8-naming
|
flake8==6.0.0 # via -r dev-requirements.in, fixit, pep8-naming
|
||||||
gray==0.13.0 # via -r dev-requirements.in
|
gray==0.13.0 # via -r dev-requirements.in
|
||||||
identify==2.5.9 # via pre-commit
|
identify==2.5.11 # via pre-commit
|
||||||
idna==3.4 # via requests
|
idna==3.4 # via requests
|
||||||
imagesize==1.4.1 # via sphinx
|
imagesize==1.4.1 # via sphinx
|
||||||
importlib-metadata==5.1.0 # via sphinx
|
|
||||||
importlib-resources==5.10.1 # via fixit
|
importlib-resources==5.10.1 # via fixit
|
||||||
iniconfig==1.1.1 # via pytest
|
iniconfig==1.1.1 # via pytest
|
||||||
isort==5.11.2 # via -r dev-requirements.in, gray
|
isort==5.11.4 # via -r dev-requirements.in, gray
|
||||||
jinja2==3.1.2 # via sphinx
|
jinja2==3.1.2 # via sphinx
|
||||||
jsonschema==4.17.3 # via fixit
|
jsonschema==4.17.3 # via fixit
|
||||||
libcst==0.4.9 # via fixit
|
libcst==0.4.9 # via fixit
|
||||||
|
@ -46,11 +45,11 @@ nodeenv==1.7.0 # via pre-commit
|
||||||
packaging==22.0 # via build, pyproject-api, pytest, sphinx, tox
|
packaging==22.0 # via build, pyproject-api, pytest, sphinx, tox
|
||||||
pathspec==0.10.3 # via black
|
pathspec==0.10.3 # via black
|
||||||
pep517==0.13.0 # via build
|
pep517==0.13.0 # via build
|
||||||
pep8-naming==0.13.2 # via -r dev-requirements.in
|
pep8-naming==0.13.3 # via -r dev-requirements.in
|
||||||
pip-tools==6.12.0 # via -r dev-requirements.in
|
pip-tools==6.12.1 # via -r dev-requirements.in
|
||||||
platformdirs==2.6.0 # via black, tox, virtualenv
|
platformdirs==2.6.0 # via black, tox, virtualenv
|
||||||
pluggy==1.0.0 # via pytest, tox
|
pluggy==1.0.0 # via pytest, tox
|
||||||
pre-commit==2.20.0 # via -r dev-requirements.in
|
pre-commit==2.21.0 # via -r dev-requirements.in
|
||||||
pycodestyle==2.10.0 # via flake8
|
pycodestyle==2.10.0 # via flake8
|
||||||
pyflakes==3.0.1 # via autoflake, flake8
|
pyflakes==3.0.1 # via autoflake, flake8
|
||||||
pygments==2.13.0 # via rich, sphinx
|
pygments==2.13.0 # via rich, sphinx
|
||||||
|
@ -58,7 +57,7 @@ pyproject-api==1.2.1 # via tox
|
||||||
pyrsistent==0.19.2 # via jsonschema
|
pyrsistent==0.19.2 # via jsonschema
|
||||||
pytest==7.2.0 # via -r dev-requirements.in, pytest-cov
|
pytest==7.2.0 # via -r dev-requirements.in, pytest-cov
|
||||||
pytest-cov==4.0.0 # via -r dev-requirements.in
|
pytest-cov==4.0.0 # via -r dev-requirements.in
|
||||||
pytz==2022.6 # via babel
|
pytz==2022.7 # via babel
|
||||||
pyupgrade==3.3.1 # via gray
|
pyupgrade==3.3.1 # via gray
|
||||||
pyyaml==6.0 # via fixit, libcst, pre-commit
|
pyyaml==6.0 # via fixit, libcst, pre-commit
|
||||||
requests==2.28.1 # via sphinx
|
requests==2.28.1 # via sphinx
|
||||||
|
@ -72,17 +71,16 @@ sphinxcontrib-jsmath==1.0.1 # via sphinx
|
||||||
sphinxcontrib-qthelp==1.0.3 # via sphinx
|
sphinxcontrib-qthelp==1.0.3 # via sphinx
|
||||||
sphinxcontrib-serializinghtml==1.1.5 # via sphinx
|
sphinxcontrib-serializinghtml==1.1.5 # via sphinx
|
||||||
tokenize-rt==5.0.0 # via add-trailing-comma, pyupgrade
|
tokenize-rt==5.0.0 # via add-trailing-comma, pyupgrade
|
||||||
toml==0.10.2 # via autoflake, pre-commit
|
toml==0.10.2 # via autoflake
|
||||||
tomli==2.0.1 # via black, build, coverage, mypy, pep517, pyproject-api, pytest, tox
|
tomli==2.0.1 # via black, build, coverage, mypy, pep517, pyproject-api, pytest, tox
|
||||||
tox==4.0.9 # via -r dev-requirements.in
|
tox==4.0.18 # via -r dev-requirements.in
|
||||||
typing-extensions==4.4.0 # via black, libcst, mypy, typing-inspect
|
typing-extensions==4.4.0 # via libcst, mypy, typing-inspect
|
||||||
typing-inspect==0.8.0 # via libcst
|
typing-inspect==0.8.0 # via libcst
|
||||||
unify==0.5 # via gray
|
unify==0.5 # via gray
|
||||||
untokenize==0.1.1 # via unify
|
untokenize==0.1.1 # via unify
|
||||||
urllib3==1.26.13 # via requests
|
urllib3==1.26.13 # via requests
|
||||||
virtualenv==20.17.1 # via pre-commit, tox
|
virtualenv==20.17.1 # via pre-commit, tox
|
||||||
wheel==0.38.4 # via pip-tools
|
wheel==0.38.4 # via pip-tools
|
||||||
zipp==3.11.0 # via importlib-metadata, importlib-resources
|
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
# pip
|
# pip
|
||||||
|
|
|
@ -41,7 +41,7 @@ RUN pip install aprsd==$APRSD_PIP_VERSION
|
||||||
# Ensure /config is there with a default config file
|
# Ensure /config is there with a default config file
|
||||||
USER root
|
USER root
|
||||||
RUN mkdir -p /config
|
RUN mkdir -p /config
|
||||||
RUN aprsd sample-config > /config/aprsd.yml
|
RUN aprsd sample-config > /config/aprsd.conf
|
||||||
RUN chown -R $APRS_USER:$APRS_USER /config
|
RUN chown -R $APRS_USER:$APRS_USER /config
|
||||||
|
|
||||||
# override this to run another configuration
|
# override this to run another configuration
|
||||||
|
@ -53,4 +53,4 @@ ADD bin/run.sh /usr/local/bin
|
||||||
ENTRYPOINT ["/usr/local/bin/run.sh"]
|
ENTRYPOINT ["/usr/local/bin/run.sh"]
|
||||||
|
|
||||||
HEALTHCHECK --interval=5m --timeout=12s --start-period=30s \
|
HEALTHCHECK --interval=5m --timeout=12s --start-period=30s \
|
||||||
CMD aprsd healthcheck --config /config/aprsd.yml --url http://localhost:8001/stats
|
CMD aprsd healthcheck --config /config/aprsd.conf --url http://localhost:8001/stats
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM ubuntu:focal as aprsd
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
# Dockerfile for building a container during aprsd development.
|
# Dockerfile for building a container during aprsd development.
|
||||||
ARG BRANCH=master
|
ARG BRANCH=master
|
||||||
|
@ -55,7 +55,7 @@ RUN ls -al /usr/local/bin
|
||||||
RUN ls -al /usr/bin
|
RUN ls -al /usr/bin
|
||||||
RUN which aprsd
|
RUN which aprsd
|
||||||
RUN mkdir -p /config
|
RUN mkdir -p /config
|
||||||
RUN aprsd sample-config > /config/aprsd.yml
|
RUN aprsd sample-config > /config/aprsd.conf
|
||||||
RUN chown -R $APRS_USER:$APRS_USER /config
|
RUN chown -R $APRS_USER:$APRS_USER /config
|
||||||
|
|
||||||
# override this to run another configuration
|
# override this to run another configuration
|
||||||
|
@ -67,4 +67,4 @@ ADD bin/run.sh $HOME/
|
||||||
ENTRYPOINT ["/home/aprs/run.sh"]
|
ENTRYPOINT ["/home/aprs/run.sh"]
|
||||||
|
|
||||||
HEALTHCHECK --interval=5m --timeout=12s --start-period=30s \
|
HEALTHCHECK --interval=5m --timeout=12s --start-period=30s \
|
||||||
CMD aprsd healthcheck --config /config/aprsd.yml --url http://localhost:8001/stats
|
CMD aprsd healthcheck --config /config/aprsd.conf --url http://localhost:8001/stats
|
||||||
|
|
|
@ -52,14 +52,6 @@ aprsd.plugins.query module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.plugins.stock module
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.plugins.stock
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.plugins.time module
|
aprsd.plugins.time module
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,35 @@ Subpackages
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
|
aprsd.clients
|
||||||
|
aprsd.cmds
|
||||||
|
aprsd.conf
|
||||||
|
aprsd.logging
|
||||||
|
aprsd.packets
|
||||||
aprsd.plugins
|
aprsd.plugins
|
||||||
|
aprsd.threads
|
||||||
|
aprsd.utils
|
||||||
|
aprsd.web
|
||||||
|
|
||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
aprsd.aprsd module
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.aprsd
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.cli\_helper module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.cli_helper
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.client module
|
aprsd.client module
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -20,18 +44,10 @@ aprsd.client module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.dev module
|
aprsd.exception module
|
||||||
----------------
|
----------------------
|
||||||
|
|
||||||
.. automodule:: aprsd.dev
|
.. automodule:: aprsd.exception
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.fake\_aprs module
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.fake_aprs
|
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
@ -44,46 +60,6 @@ aprsd.flask module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.fuzzyclock module
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.fuzzyclock
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.healthcheck module
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.healthcheck
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.kissclient module
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.kissclient
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.listen module
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.listen
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.main module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.main
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.messaging module
|
aprsd.messaging module
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -92,14 +68,6 @@ aprsd.messaging module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.packets module
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.packets
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.plugin module
|
aprsd.plugin module
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -116,6 +84,14 @@ aprsd.plugin\_utils module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.rpc\_server module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.rpc_server
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.stats module
|
aprsd.stats module
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -124,30 +100,6 @@ aprsd.stats module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.threads module
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.threads
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.trace module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.trace
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
aprsd.utils module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: aprsd.utils
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,339 @@
|
||||||
CHANGES
|
CHANGES
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
* Decouple admin web interface from server command
|
||||||
|
* Dockerfile now produces aprsd.conf
|
||||||
|
* Fix some unit tests and loading of CONF w/o file
|
||||||
|
* Added missing conf
|
||||||
|
* Removed references to old custom config
|
||||||
|
* Convert config to oslo\_config
|
||||||
|
* Added rain formatting unit tests to WeatherPacket
|
||||||
|
* Fix Rain reporting in WeatherPacket send
|
||||||
|
* Removed Packet.send()
|
||||||
|
* Removed watchlist plugins
|
||||||
|
* Fix PluginManager.get\_plugins
|
||||||
|
* Cleaned up PluginManager
|
||||||
|
* Cleaned up PluginManager
|
||||||
|
* Update routing for weatherpacket
|
||||||
|
* Fix some WeatherPacket formatting
|
||||||
|
* Fix pep8 violation
|
||||||
|
* Add packet filtering for aprsd listen
|
||||||
|
* Added WeatherPacket encoding
|
||||||
|
* Updated webchat and listen for queue based RX
|
||||||
|
* reworked collecting and reporting stats
|
||||||
|
* Removed unused threading code
|
||||||
|
* Change RX packet processing to enqueu
|
||||||
|
* Make tracking objectstores work w/o initializing
|
||||||
|
* Cleaned up packet transmit class attributes
|
||||||
|
* Fix packets timestamp to int
|
||||||
|
* More messaging -> packets cleanup
|
||||||
|
* Cleaned out all references to messaging
|
||||||
|
* Added contructing a GPSPacket for sending
|
||||||
|
* cleanup webchat
|
||||||
|
* Reworked all packet processing
|
||||||
|
* Updated plugins and plugin interfaces for Packet
|
||||||
|
* Started using dataclasses to describe packets
|
||||||
|
|
||||||
|
v2.6.1
|
||||||
|
------
|
||||||
|
|
||||||
|
* v2.6.1
|
||||||
|
* Fixed position report for webchat beacon
|
||||||
|
* Try and fix broken 32bit qemu builds on 64bit system
|
||||||
|
* Add unit tests for webchat
|
||||||
|
* remove armv7 build RUST sucks
|
||||||
|
* Fix for Collections change in 3.10
|
||||||
|
|
||||||
|
v2.6.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Update workflow again
|
||||||
|
* Update Dockerfile to 22.04
|
||||||
|
* Update Dockerfile and build.sh
|
||||||
|
* Update workflow
|
||||||
|
* Prep for 2.6.0 release
|
||||||
|
* Update requirements
|
||||||
|
* Removed Makefile comment
|
||||||
|
* Update Makefile for dev vs. run environments
|
||||||
|
* Added pyopenssl for https for webchat
|
||||||
|
* change from device-detector to user-agents
|
||||||
|
* Remove twine from dev-requirements
|
||||||
|
* Update to latest Makefile.venv
|
||||||
|
* Refactored threads a bit
|
||||||
|
* Mark packets as acked in MsgTracker
|
||||||
|
* remove dev setting for template
|
||||||
|
* Add GPS beacon to mobile page
|
||||||
|
* Allow werkzeug for admin interface
|
||||||
|
* Allow werkzeug for admin interface
|
||||||
|
* Add support for mobile browsers for webchat
|
||||||
|
* Ignore callsign case while processing packets
|
||||||
|
* remove linux/arm/v7 for official builds for now
|
||||||
|
* added workflow for building specific version
|
||||||
|
* Allow passing in version to the Dockerfile
|
||||||
|
* Send GPS Beacon from webchat interface
|
||||||
|
* specify Dockerfile-dev
|
||||||
|
* Fixed build.sh
|
||||||
|
* Build on the source not released aprsd
|
||||||
|
* Remove email validation
|
||||||
|
* Add support for building linux/arm/v7
|
||||||
|
* Remove python 3.7 from docker build github
|
||||||
|
* Fixed failing unit tests
|
||||||
|
* change github workflow
|
||||||
|
* Removed TimeOpenCageDataPlugin
|
||||||
|
* Dump config with aprsd dev test-plugin
|
||||||
|
* Updated requirements
|
||||||
|
* Got webchat working with KISS tcp
|
||||||
|
* Added click auto\_envvar\_prefix
|
||||||
|
* Update aprsd thread base class to use queue
|
||||||
|
* Update packets to use wrapt
|
||||||
|
* Add remving existing requirements
|
||||||
|
* Try sending raw APRSFrames to aioax25
|
||||||
|
* Use new aprsd.callsign as the main callsign
|
||||||
|
* Fixed access to threads refactor
|
||||||
|
* Added webchat command
|
||||||
|
* Moved log.py to logging
|
||||||
|
* Moved trace.py to utils
|
||||||
|
* Fixed pep8 errors
|
||||||
|
* Refactored threads.py
|
||||||
|
* Refactor utils to directory
|
||||||
|
* remove arm build for now
|
||||||
|
* Added rustc and cargo to Dockerfile
|
||||||
|
* remove linux/arm/v6 from docker platform build
|
||||||
|
* Only tag master build as master
|
||||||
|
* Remove docker build from test
|
||||||
|
* create master-build.yml
|
||||||
|
* Added container build action
|
||||||
|
* Update docs on using Docker
|
||||||
|
* Update dev-requirements pip-tools
|
||||||
|
* Fix typo in docker-compose.yml
|
||||||
|
* Fix PyPI scraping
|
||||||
|
* Allow web interface when running in Docker
|
||||||
|
* Fix typo on exception
|
||||||
|
* README formatting fixes
|
||||||
|
* Bump dependencies to fix python 3.10
|
||||||
|
* Fixed up config option checking for KISS
|
||||||
|
* Fix logging issue with log messages
|
||||||
|
* for 2.5.9
|
||||||
|
|
||||||
|
v2.5.9
|
||||||
|
------
|
||||||
|
|
||||||
|
* FIX: logging exceptions
|
||||||
|
* Updated build and run for rich lib
|
||||||
|
* update build for 2.5.8
|
||||||
|
|
||||||
|
v2.5.8
|
||||||
|
------
|
||||||
|
|
||||||
|
* For 2.5.8
|
||||||
|
* Removed debug code
|
||||||
|
* Updated list-plugins
|
||||||
|
* Renamed virtualenv dir to .aprsd-venv
|
||||||
|
* Added unit tests for dev test-plugin
|
||||||
|
* Send Message command defaults to config
|
||||||
|
|
||||||
|
v2.5.7
|
||||||
|
------
|
||||||
|
|
||||||
|
* Updated Changelog
|
||||||
|
* Fixed an KISS config disabled issue
|
||||||
|
* Fixed a bug with multiple notify plugins enabled
|
||||||
|
* Unify the logging to file and stdout
|
||||||
|
* Added new feature to list-plugins command
|
||||||
|
* more README.rst cleanup
|
||||||
|
* Updated README examples
|
||||||
|
|
||||||
|
v2.5.6
|
||||||
|
------
|
||||||
|
|
||||||
|
* Changelog
|
||||||
|
* Tightened up the packet logging
|
||||||
|
* Added unit tests for USWeatherPlugin, USMetarPlugin
|
||||||
|
* Added test\_location to test LocationPlugin
|
||||||
|
* Updated pytest output
|
||||||
|
* Added py39 to tox for tests
|
||||||
|
* Added NotifyPlugin unit tests and more
|
||||||
|
* Small cleanup on packet logging
|
||||||
|
* Reduced the APRSIS connection reset to 2 minutes
|
||||||
|
* Fixed the NotifyPlugin
|
||||||
|
* Fixed some pep8 errors
|
||||||
|
* Add tracing for dev command
|
||||||
|
* Added python rich library based logging
|
||||||
|
* Added LOG\_LEVEL env variable for the docker
|
||||||
|
|
||||||
|
v2.5.5
|
||||||
|
------
|
||||||
|
|
||||||
|
* Update requirements to use aprslib 0.7.0
|
||||||
|
* fixed the failure during loading for objectstore
|
||||||
|
* updated docker build
|
||||||
|
|
||||||
|
v2.5.4
|
||||||
|
------
|
||||||
|
|
||||||
|
* Updated Changelog
|
||||||
|
* Fixed dev command missing initialization
|
||||||
|
|
||||||
|
v2.5.3
|
||||||
|
------
|
||||||
|
|
||||||
|
* Fix admin logging tab
|
||||||
|
|
||||||
|
v2.5.2
|
||||||
|
------
|
||||||
|
|
||||||
|
* Added new list-plugins command
|
||||||
|
* Don't require check-version command to have a config
|
||||||
|
* Healthcheck command doesn't need the aprsd.yml config
|
||||||
|
* Fix test failures
|
||||||
|
* Removed requirement for aprs.fi key
|
||||||
|
* Updated Changelog
|
||||||
|
|
||||||
|
v2.5.1
|
||||||
|
------
|
||||||
|
|
||||||
|
* Removed stock plugin
|
||||||
|
* Removed the stock plugin
|
||||||
|
|
||||||
|
v2.5.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Updated for v2.5.0
|
||||||
|
* Updated Dockerfile's and build script for docker
|
||||||
|
* Cleaned up some verbose output & colorized output
|
||||||
|
* Reworked all the common arguments
|
||||||
|
* Fixed test-plugin
|
||||||
|
* Ensure common params are honored
|
||||||
|
* pep8
|
||||||
|
* Added healthcheck to the cmds
|
||||||
|
* Removed the need for FROMCALL in dev test-plugin
|
||||||
|
* Pep8 failures
|
||||||
|
* Refactor the cli
|
||||||
|
* Updated Changelog for 4.2.3
|
||||||
|
* Fixed a problem with send-message command
|
||||||
|
|
||||||
|
v2.4.2
|
||||||
|
------
|
||||||
|
|
||||||
|
* Updated Changelog
|
||||||
|
* Be more careful picking data to/from disk
|
||||||
|
* Updated Changelog
|
||||||
|
|
||||||
|
v2.4.1
|
||||||
|
------
|
||||||
|
|
||||||
|
* Ensure plugins are last to be loaded
|
||||||
|
* Fixed email connecting to smtp server
|
||||||
|
|
||||||
|
v2.4.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Updated Changelog for 2.4.0 release
|
||||||
|
* Converted MsgTrack to ObjectStoreMixin
|
||||||
|
* Fixed unit tests
|
||||||
|
* Make sure SeenList update has a from in packet
|
||||||
|
* Ensure PacketList is initialized
|
||||||
|
* Added SIGTERM to signal\_handler
|
||||||
|
* Enable configuring where to save the objectstore data
|
||||||
|
* PEP8 cleanup
|
||||||
|
* Added objectstore Mixin
|
||||||
|
* Added -num option to aprsd-dev test-plugin
|
||||||
|
* Only call stop\_threads if it exists
|
||||||
|
* Added new SeenList
|
||||||
|
* Added plugin version to stats reporting
|
||||||
|
* Added new HelpPlugin
|
||||||
|
* Updated aprsd-dev to use config for logfile format
|
||||||
|
* Updated build.sh
|
||||||
|
* removed usage of config.check\_config\_option
|
||||||
|
* Fixed send-message after config/client rework
|
||||||
|
* Fixed issue with flask config
|
||||||
|
* Added some server startup info logs
|
||||||
|
* Increase email delay to +10
|
||||||
|
* Updated dev to use plugin manager
|
||||||
|
* Fixed notify plugins
|
||||||
|
* Added new Config object
|
||||||
|
* Fixed email plugin's use of globals
|
||||||
|
* Refactored client classes
|
||||||
|
* Refactor utils usage
|
||||||
|
* 2.3.1 Changelog
|
||||||
|
|
||||||
|
v2.3.1
|
||||||
|
------
|
||||||
|
|
||||||
|
* Fixed issue of aprs-is missing keepalive
|
||||||
|
* Fixed packet processing issue with aprsd send-message
|
||||||
|
|
||||||
|
v2.3.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Prep 2.3.0
|
||||||
|
* Enable plugins to return message object
|
||||||
|
* Added enabled flag for every plugin object
|
||||||
|
* Ensure plugin threads are valid
|
||||||
|
* Updated Dockerfile to use v2.3.0
|
||||||
|
* Removed fixed size on logging queue
|
||||||
|
* Added Logfile tab in Admin ui
|
||||||
|
* Updated Makefile clean target
|
||||||
|
* Added self creating Makefile help target
|
||||||
|
* Update dev.py
|
||||||
|
* Allow passing in aprsis\_client
|
||||||
|
* Fixed a problem with the AVWX plugin not working
|
||||||
|
* Remove some noisy trace in email plugin
|
||||||
|
* Fixed issue at startup with notify plugin
|
||||||
|
* Fixed email validation
|
||||||
|
* Removed values from forms
|
||||||
|
* Added send-message to the main admin UI
|
||||||
|
* Updated requirements
|
||||||
|
* Cleaned up some pep8 failures
|
||||||
|
* Upgraded the send-message POC to use websockets
|
||||||
|
* New Admin ui send message page working
|
||||||
|
* Send Message via admin Web interface
|
||||||
|
* Updated Admin UI to show KISS connections
|
||||||
|
* Got TX/RX working with aioax25+direwolf over TCP
|
||||||
|
* Rebased from master
|
||||||
|
* Added the ability to use direwolf KISS socket
|
||||||
|
* Update Dockerfile to use 2.2.1
|
||||||
|
|
||||||
|
v2.2.1
|
||||||
|
------
|
||||||
|
|
||||||
|
* Update Changelog for 2.2.1
|
||||||
|
* Silence some log noise
|
||||||
|
|
||||||
|
v2.2.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Updated Changelog for v2.2.0
|
||||||
|
* Updated overview image
|
||||||
|
* Removed Black code style reference
|
||||||
|
* Removed TXThread
|
||||||
|
* Added days to uptime string formatting
|
||||||
|
* Updated select timeouts
|
||||||
|
* Rebase from master and run gray
|
||||||
|
* Added tracking plugin processing
|
||||||
|
* Added threads functions to APRSDPluginBase
|
||||||
|
* Refactor Message processing and MORE
|
||||||
|
* Use Gray instead of Black for code formatting
|
||||||
|
* Updated tox.ini
|
||||||
|
* Fixed LOG.debug issue in weather plugin
|
||||||
|
* Updated slack channel link
|
||||||
|
* Cleanup of the README.rst
|
||||||
|
* Fixed aprsd-dev
|
||||||
|
|
||||||
|
v2.1.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Prep for v2.1.0
|
||||||
|
* Enable multiple replies for plugins
|
||||||
|
* Put in a fix for aprslib parse exceptions
|
||||||
|
* Fixed time plugin
|
||||||
|
* Updated the charts Added the packets chart
|
||||||
|
* Added showing symbol images to watch list
|
||||||
|
|
||||||
v2.0.0
|
v2.0.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
* Updated docs for 2.0.0
|
||||||
* Reworked the notification threads and admin ui
|
* Reworked the notification threads and admin ui
|
||||||
* Fixed small bug with packets get\_packet\_type
|
* Fixed small bug with packets get\_packet\_type
|
||||||
* Updated overview images
|
* Updated overview images
|
||||||
|
|
|
@ -64,7 +64,7 @@ master_doc = "index"
|
||||||
#
|
#
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
# Usually you set "language" from the command line for these cases.
|
# Usually you set "language" from the command line for these cases.
|
||||||
language = None
|
language = "en"
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
|
|
|
@ -15,10 +15,11 @@ a sample config file in the default location of ~/.config/aprsd/aprsd.yml
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
└─[$] -> aprsd server
|
└─> aprsd server
|
||||||
Load config
|
12/28/2022 04:26:31 PM MainThread ERROR No config file found!! run 'aprsd sample-config' cli_helper.py:90
|
||||||
/home/aprsd/.config/aprsd/aprsd.yml is missing, creating config file
|
12/28/2022 04:26:31 PM MainThread ERROR Config aprs_network.password not set. client.py:105
|
||||||
Default config file created at /home/aprsd/.config/aprsd/aprsd.yml. Please edit with your settings.
|
12/28/2022 04:26:31 PM MainThread ERROR Option 'aprs_network.password is not set.' was not in config file client.py:268
|
||||||
|
12/28/2022 04:26:31 PM MainThread ERROR APRS client is not properly configured in config file. server.py:58
|
||||||
|
|
||||||
You can see the sample config file output
|
You can see the sample config file output
|
||||||
|
|
||||||
|
@ -27,43 +28,310 @@ Sample config file
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
└─[$] -> cat ~/.config/aprsd/aprsd.yml
|
└─> aprsd sample-config
|
||||||
aprs:
|
[DEFAULT]
|
||||||
host: rotate.aprs.net
|
|
||||||
logfile: /tmp/arsd.log
|
|
||||||
login: someusername
|
|
||||||
password: somepassword
|
|
||||||
port: 14580
|
|
||||||
aprsd:
|
|
||||||
enabled_plugins:
|
|
||||||
- aprsd.plugins.email.EmailPlugin
|
|
||||||
- aprsd.plugins.fortune.FortunePlugin
|
|
||||||
- aprsd.plugins.location.LocationPlugin
|
|
||||||
- aprsd.plugins.ping.PingPlugin
|
|
||||||
- aprsd.plugins.query.QueryPlugin
|
|
||||||
- aprsd.plugins.time.TimePlugin
|
|
||||||
- aprsd.plugins.weather.WeatherPlugin
|
|
||||||
- aprsd.plugins.version.VersionPlugin
|
|
||||||
plugin_dir: ~/.config/aprsd/plugins
|
|
||||||
ham:
|
|
||||||
callsign: KFART
|
|
||||||
imap:
|
|
||||||
host: imap.gmail.com
|
|
||||||
login: imapuser
|
|
||||||
password: something here too
|
|
||||||
port: 993
|
|
||||||
use_ssl: true
|
|
||||||
shortcuts:
|
|
||||||
aa: 5551239999@vtext.com
|
|
||||||
cl: craiglamparter@somedomain.org
|
|
||||||
wb: 555309@vtext.com
|
|
||||||
smtp:
|
|
||||||
host: imap.gmail.com
|
|
||||||
login: something
|
|
||||||
password: some lame password
|
|
||||||
port: 465
|
|
||||||
use_ssl: false
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Callsign to use for messages sent by APRSD (string value)
|
||||||
|
#callsign = <None>
|
||||||
|
|
||||||
|
# Enable saving of watch list, packet tracker between restarts.
|
||||||
|
# (boolean value)
|
||||||
|
#enable_save = true
|
||||||
|
|
||||||
|
# Save location for packet tracking files. (string value)
|
||||||
|
#save_location = ~/.config/aprsd
|
||||||
|
|
||||||
|
# Enable code tracing (boolean value)
|
||||||
|
#trace_enabled = false
|
||||||
|
|
||||||
|
# Units for display, imperial or metric (string value)
|
||||||
|
#units = imperial
|
||||||
|
|
||||||
|
# Comma separated list of enabled plugins for APRSD.To enable
|
||||||
|
# installed external plugins add them here.The full python path to the
|
||||||
|
# class name must be used (list value)
|
||||||
|
#enabled_plugins = aprsd.plugins.email.EmailPlugin,aprsd.plugins.fortune.FortunePlugin,aprsd.plugins.location.LocationPlugin,aprsd.plugins.ping.PingPlugin,aprsd.plugins.query.QueryPlugin,aprsd.plugins.time.TimePlugin,aprsd.plugins.weather.OWMWeatherPlugin,aprsd.plugins.version.VersionPlugin
|
||||||
|
|
||||||
|
|
||||||
|
[admin]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Enable the Admin Web Interface (boolean value)
|
||||||
|
#web_enabled = false
|
||||||
|
|
||||||
|
# The ip address to listen on (IP address value)
|
||||||
|
#web_ip = 0.0.0.0
|
||||||
|
|
||||||
|
# The port to listen on (port value)
|
||||||
|
# Minimum value: 0
|
||||||
|
# Maximum value: 65535
|
||||||
|
#web_port = 8001
|
||||||
|
|
||||||
|
# The admin user for the admin web interface (string value)
|
||||||
|
#user = admin
|
||||||
|
|
||||||
|
# Admin interface password (string value)
|
||||||
|
#password = <None>
|
||||||
|
|
||||||
|
|
||||||
|
[aprs_fi]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Get the apiKey from your aprs.fi account here:http://aprs.fi/account
|
||||||
|
# (string value)
|
||||||
|
#apiKey = <None>
|
||||||
|
|
||||||
|
|
||||||
|
[aprs_network]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Set enabled to False if there is no internet connectivity.This is
|
||||||
|
# useful for a direwolf KISS aprs connection only. (boolean value)
|
||||||
|
#enabled = true
|
||||||
|
|
||||||
|
# APRS Username (string value)
|
||||||
|
#login = NOCALL
|
||||||
|
|
||||||
|
# APRS Password Get the passcode for your callsign here:
|
||||||
|
# https://apps.magicbug.co.uk/passcode (string value)
|
||||||
|
#password = <None>
|
||||||
|
|
||||||
|
# The APRS-IS hostname (hostname value)
|
||||||
|
#host = noam.aprs2.net
|
||||||
|
|
||||||
|
# APRS-IS port (port value)
|
||||||
|
# Minimum value: 0
|
||||||
|
# Maximum value: 65535
|
||||||
|
#port = 14580
|
||||||
|
|
||||||
|
|
||||||
|
[aprsd_weewx_plugin]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd_weewx_plugin.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Latitude of the station you want to report as (floating point value)
|
||||||
|
#latitude = <None>
|
||||||
|
|
||||||
|
# Longitude of the station you want to report as (floating point
|
||||||
|
# value)
|
||||||
|
#longitude = <None>
|
||||||
|
|
||||||
|
# How long (in seconds) in between weather reports (integer value)
|
||||||
|
#report_interval = 60
|
||||||
|
|
||||||
|
|
||||||
|
[avwx_plugin]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# avwx-api is an opensource project that hasa hosted service here:
|
||||||
|
# https://avwx.rest/You can launch your own avwx-api in a containerby
|
||||||
|
# cloning the githug repo here:https://github.com/avwx-rest/AVWX-API
|
||||||
|
# (string value)
|
||||||
|
#apiKey = <None>
|
||||||
|
|
||||||
|
# The base url for the avwx API. If you are hosting your ownHere is
|
||||||
|
# where you change the url to point to yours. (string value)
|
||||||
|
#base_url = https://avwx.rest
|
||||||
|
|
||||||
|
|
||||||
|
[email_plugin]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# (Required) Callsign to validate for doing email commands.Only this
|
||||||
|
# callsign can check email. This is also where the email notifications
|
||||||
|
# for new emails will be sent. (string value)
|
||||||
|
#callsign = <None>
|
||||||
|
|
||||||
|
# Enable the Email plugin? (boolean value)
|
||||||
|
#enabled = false
|
||||||
|
|
||||||
|
# Enable the Email plugin Debugging? (boolean value)
|
||||||
|
#debug = false
|
||||||
|
|
||||||
|
# Login username/email for IMAP server (string value)
|
||||||
|
#imap_login = <None>
|
||||||
|
|
||||||
|
# Login password for IMAP server (string value)
|
||||||
|
#imap_password = <None>
|
||||||
|
|
||||||
|
# Hostname/IP of the IMAP server (hostname value)
|
||||||
|
#imap_host = <None>
|
||||||
|
|
||||||
|
# Port to use for IMAP server (port value)
|
||||||
|
# Minimum value: 0
|
||||||
|
# Maximum value: 65535
|
||||||
|
#imap_port = 993
|
||||||
|
|
||||||
|
# Use SSL for connection to IMAP Server (boolean value)
|
||||||
|
#imap_use_ssl = true
|
||||||
|
|
||||||
|
# Login username/email for SMTP server (string value)
|
||||||
|
#smtp_login = <None>
|
||||||
|
|
||||||
|
# Login password for SMTP server (string value)
|
||||||
|
#smtp_password = <None>
|
||||||
|
|
||||||
|
# Hostname/IP of the SMTP server (hostname value)
|
||||||
|
#smtp_host = <None>
|
||||||
|
|
||||||
|
# Port to use for SMTP server (port value)
|
||||||
|
# Minimum value: 0
|
||||||
|
# Maximum value: 65535
|
||||||
|
#smtp_port = 465
|
||||||
|
|
||||||
|
# Use SSL for connection to SMTP Server (boolean value)
|
||||||
|
#smtp_use_ssl = true
|
||||||
|
|
||||||
|
# List of email shortcuts for checking/sending email For Exmaple:
|
||||||
|
# wb=walt@walt.com,cl=cl@cl.com
|
||||||
|
# Means use 'wb' to send an email to walt@walt.com (list value)
|
||||||
|
#email_shortcuts = <None>
|
||||||
|
|
||||||
|
|
||||||
|
[kiss_serial]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Enable Serial KISS interface connection. (boolean value)
|
||||||
|
#enabled = false
|
||||||
|
|
||||||
|
# Serial Device file to use. /dev/ttyS0 (string value)
|
||||||
|
#device = <None>
|
||||||
|
|
||||||
|
# The Serial device baud rate for communication (integer value)
|
||||||
|
#baudrate = 9600
|
||||||
|
|
||||||
|
|
||||||
|
[kiss_tcp]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Enable Serial KISS interface connection. (boolean value)
|
||||||
|
#enabled = false
|
||||||
|
|
||||||
|
# The KISS TCP Host to connect to. (hostname value)
|
||||||
|
#host = <None>
|
||||||
|
|
||||||
|
# The KISS TCP/IP network port (port value)
|
||||||
|
# Minimum value: 0
|
||||||
|
# Maximum value: 65535
|
||||||
|
#port = 8001
|
||||||
|
|
||||||
|
|
||||||
|
[logging]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Date format for log entries (string value)
|
||||||
|
#date_format = %m/%d/%Y %I:%M:%S %p
|
||||||
|
|
||||||
|
# Enable Rich logging (boolean value)
|
||||||
|
#rich_logging = true
|
||||||
|
|
||||||
|
# File to log to (string value)
|
||||||
|
#logfile = <None>
|
||||||
|
|
||||||
|
# Log file format, unless rich_logging enabled. (string value)
|
||||||
|
#logformat = [%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s] %(message)s - [%(pathname)s:%(lineno)d]
|
||||||
|
|
||||||
|
|
||||||
|
[owm_weather_plugin]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# OWMWeatherPlugin api key to OpenWeatherMap's API.This plugin uses
|
||||||
|
# the openweathermap API to fetchlocation and weather information.To
|
||||||
|
# use this plugin you need to get an openweathermapaccount and
|
||||||
|
# apikey.https://home.openweathermap.org/api_keys (string value)
|
||||||
|
#apiKey = <None>
|
||||||
|
|
||||||
|
|
||||||
|
[query_plugin]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# The Ham callsign to allow access to the query plugin from RF.
|
||||||
|
# (string value)
|
||||||
|
#callsign = <None>
|
||||||
|
|
||||||
|
|
||||||
|
[rpc_settings]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Enable RPC calls (boolean value)
|
||||||
|
#enabled = true
|
||||||
|
|
||||||
|
# The ip address to listen on (string value)
|
||||||
|
#ip = localhost
|
||||||
|
|
||||||
|
# The port to listen on (port value)
|
||||||
|
# Minimum value: 0
|
||||||
|
# Maximum value: 65535
|
||||||
|
#port = 18861
|
||||||
|
|
||||||
|
# Magic word to authenticate requests between client/server (string
|
||||||
|
# value)
|
||||||
|
#magic_word = CHANGEME!!!
|
||||||
|
|
||||||
|
|
||||||
|
[watch_list]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From aprsd.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Enable the watch list feature. Still have to enable the correct
|
||||||
|
# plugin. Built-in plugin to use is aprsd.plugins.notify.NotifyPlugin
|
||||||
|
# (boolean value)
|
||||||
|
#enabled = false
|
||||||
|
|
||||||
|
# Callsigns to watch for messsages (list value)
|
||||||
|
#callsigns = <None>
|
||||||
|
|
||||||
|
# The Ham Callsign to send messages to for watch list alerts. (string
|
||||||
|
# value)
|
||||||
|
#alert_callsign = <None>
|
||||||
|
|
||||||
|
# The number of packets to store. (integer value)
|
||||||
|
#packet_keep_count = 10
|
||||||
|
|
||||||
|
# Time to wait before alert is sent on new message for users in
|
||||||
|
# callsigns. (integer value)
|
||||||
|
#alert_time_seconds = 3600
|
||||||
|
|
||||||
Note, You must edit the config file and change the ham callsign to your
|
Note, You must edit the config file and change the ham callsign to your
|
||||||
legal FCC HAM callsign, or aprsd server will not start.
|
legal FCC HAM callsign, or aprsd server will not start.
|
||||||
|
|
|
@ -41,7 +41,7 @@ aprsd/examples/plugins/example_plugin.py
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
class HelloPlugin(plugin.APRSDPluginBase):
|
class HelloPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
"""Hello World."""
|
"""Hello World."""
|
||||||
|
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
@ -49,7 +49,7 @@ aprsd/examples/plugins/example_plugin.py
|
||||||
command_regex = "^[hH]"
|
command_regex = "^[hH]"
|
||||||
command_name = "hello"
|
command_name = "hello"
|
||||||
|
|
||||||
def command(self, fromcall, message, ack):
|
def process(self, packet):
|
||||||
LOG.info("HelloPlugin")
|
LOG.info("HelloPlugin")
|
||||||
reply = "Hello '{}'".format(fromcall)
|
reply = "Hello '{}'".format(packet.from_call)
|
||||||
return reply
|
return reply
|
||||||
|
|
431
docs/readme.rst
431
docs/readme.rst
|
@ -1,28 +1,12 @@
|
||||||
=====
|
===============================================
|
||||||
APRSD
|
APRSD - Ham radio APRS-IS Message plugin server
|
||||||
=====
|
===============================================
|
||||||
by KM6LYW and WB4BOR
|
|
||||||
|
|
||||||
.. image:: https://badge.fury.io/py/aprsd.svg
|
KM6LYW and WB4BOR
|
||||||
:target: https://badge.fury.io/py/aprsd
|
____________________
|
||||||
|
|
||||||
.. image:: https://github.com/craigerl/aprsd/workflows/python/badge.svg
|
|pypi| |pytest| |versions| |slack| |issues| |commit| |imports| |down|
|
||||||
:target: https://github.com/craigerl/aprsd/actions
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
||||||
:target: https://black.readthedocs.io/en/stable/
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336
|
|
||||||
:target: https://timothycrosley.github.io/isort/
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/github/issues/craigerl/aprsd
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/github/last-commit/craigerl/aprsd
|
|
||||||
|
|
||||||
.. 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
|
|
||||||
|
|
||||||
.. contents:: :local:
|
|
||||||
|
|
||||||
`APRSD <http://github.com/craigerl/aprsd>`_ is a Ham radio `APRS <http://aprs.org>`_ message command gateway built on python.
|
`APRSD <http://github.com/craigerl/aprsd>`_ is a Ham radio `APRS <http://aprs.org>`_ message command gateway built on python.
|
||||||
|
|
||||||
|
@ -37,11 +21,14 @@ provide responding to messages to check email, get location, ping,
|
||||||
time of day, get weather, and fortune telling as well as version information
|
time of day, get weather, and fortune telling as well as version information
|
||||||
of aprsd itself.
|
of aprsd itself.
|
||||||
|
|
||||||
Documentation: https://aprsd.readthedocs.io
|
Please `read the docs`_ to learn more!
|
||||||
|
|
||||||
|
|
||||||
|
.. contents:: :local:
|
||||||
|
|
||||||
|
|
||||||
APRSD Overview Diagram
|
APRSD Overview Diagram
|
||||||
----------------------
|
======================
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/craigerl/aprsd/master/docs/_static/aprsd_overview.svg?sanitize=true
|
.. image:: https://raw.githubusercontent.com/craigerl/aprsd/master/docs/_static/aprsd_overview.svg?sanitize=true
|
||||||
|
|
||||||
|
@ -50,7 +37,7 @@ Typical use case
|
||||||
================
|
================
|
||||||
|
|
||||||
Ham radio operator using an APRS enabled HAM radio sends a message to check
|
Ham radio operator using an APRS enabled HAM radio sends a message to check
|
||||||
the weather. an APRS message is sent, and then picked up by APRSD. The
|
the weather. An APRS message is sent, and then picked up by APRSD. The
|
||||||
APRS packet is decoded, and the message is sent through the list of plugins
|
APRS packet is decoded, and the message is sent through the list of plugins
|
||||||
for processing. For example, the WeatherPlugin picks up the message, fetches the weather
|
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
|
for the area around the user who sent the request, and then responds with
|
||||||
|
@ -59,103 +46,91 @@ callsigns to look out for. The watch list can notify you when a HAM callsign
|
||||||
in the list is seen and now available to message on the APRS network.
|
in the list is seen and now available to message on the APRS network.
|
||||||
|
|
||||||
|
|
||||||
APRSD Capabilities
|
Current list of built-in plugins
|
||||||
==================
|
|
||||||
|
|
||||||
* server - The main aprsd server processor. Send/Rx APRS messages to HAM callsign
|
|
||||||
* send-message - use aprsd to send a command/message to aprsd server. Used for development testing
|
|
||||||
* sample-config - generate a sample aprsd.yml config file for use/editing
|
|
||||||
* bash completion generation. Uses python click bash completion to generate completion code for your .bashrc/.zshrc
|
|
||||||
|
|
||||||
|
|
||||||
List of core server plugins
|
|
||||||
===========================
|
|
||||||
|
|
||||||
Plugins function by specifying a regex that is searched for in the APRS message.
|
|
||||||
If it matches, the plugin runs. IF the regex doesn't match, the plugin is skipped.
|
|
||||||
|
|
||||||
* EmailPlugin - Check email and reply with contents. Have to configure IMAP and SMTP settings in aprs.yml
|
|
||||||
* FortunePlugin - Replies with old unix fortune random fortune!
|
|
||||||
* LocationPlugin - Checks location of ham operator
|
|
||||||
* PingPlugin - Sends pong with timestamp
|
|
||||||
* QueryPlugin - Allows querying the list of delayed messages that were not ACK'd by radio
|
|
||||||
* TimePlugin - Current time of day
|
|
||||||
* WeatherPlugin - Get weather conditions for current location of HAM callsign
|
|
||||||
* VersionPlugin - Reports the version information for aprsd
|
|
||||||
|
|
||||||
|
|
||||||
List of core notification plugins
|
|
||||||
=================================
|
|
||||||
|
|
||||||
These plugins see all APRS messages from ham callsigns in the config's watch
|
|
||||||
list.
|
|
||||||
|
|
||||||
* NotifySeenPlugin - Send a message when a message is seen from a callsign in
|
|
||||||
the watch list. This is helpful when you want to know
|
|
||||||
when a friend is online in the ARPS network, but haven't
|
|
||||||
been seen in a while.
|
|
||||||
|
|
||||||
|
|
||||||
Current messages this will respond to:
|
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
APRS messages:
|
└─> aprsd list-plugins
|
||||||
l(ocation) [callsign] = descriptive current location of your radio
|
🐍 APRSD Built-in Plugins 🐍
|
||||||
8 Miles E Auburn CA 1673' 39.92150,-120.93950 0.1h ago
|
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
w(eather) = weather forecast for your radio's current position
|
┃ Plugin Name ┃ Info ┃ Type ┃ Plugin Path ┃
|
||||||
58F(58F/46F) Partly Cloudy. Tonight, Heavy Rain.
|
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||||
t(ime) = respond with the current time
|
│ AVWXWeatherPlugin │ AVWX weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.AVWXWeatherPlugin │
|
||||||
f(ortune) = respond with a short fortune
|
│ EmailPlugin │ Send and Receive email │ RegexCommand │ aprsd.plugins.email.EmailPlugin │
|
||||||
-email_addr email text = send an email, say "mapme" to send a current position/map
|
│ FortunePlugin │ Give me a fortune │ RegexCommand │ aprsd.plugins.fortune.FortunePlugin │
|
||||||
-2 = resend the last 2 emails from your imap inbox to this radio
|
│ LocationPlugin │ Where in the world is a CALLSIGN's last GPS beacon? │ RegexCommand │ aprsd.plugins.location.LocationPlugin │
|
||||||
p(ing) = respond with Pong!/time
|
│ NotifySeenPlugin │ Notify me when a CALLSIGN is recently seen on APRS-IS │ WatchList │ aprsd.plugins.notify.NotifySeenPlugin │
|
||||||
v(ersion) = Respond with current APRSD Version string
|
│ OWMWeatherPlugin │ OpenWeatherMap weather of GPS Beacon location │ RegexCommand │ aprsd.plugins.weather.OWMWeatherPlugin │
|
||||||
anything else = respond with usage
|
│ 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 │
|
||||||
|
└───────────────────┴────────────────────────────────────────────────────────────┴──────────────┴─────────────────────────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
Meanwhile this code will monitor a single imap mailbox and forward email
|
Pypi.org APRSD Installable Plugin Packages
|
||||||
to your BASECALLSIGN over the air. Only radios using the BASECALLSIGN are allowed
|
|
||||||
to send email, so consider this security risk before using this (or Amatuer radio in
|
|
||||||
general). Email is single user at this time.
|
|
||||||
|
|
||||||
There are additional parameters in the code (sorry), so be sure to set your
|
Install any of the following plugins with 'pip install <Plugin Package Name>'
|
||||||
email server, and associated logins, passwords. search for "yourdomain",
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
||||||
"password". Search for "shortcuts" to setup email aliases as well.
|
┃ 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 │
|
||||||
|
└──────────────────────────────┴────────────────────────────────────────────────────────────────────┴─────────┴──────────────┴────────────┘
|
||||||
|
|
||||||
|
|
||||||
Installation:
|
🐍 APRSD Installed 3rd party Plugins 🐍
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ Package Name ┃ Plugin Name ┃ Version ┃ Type ┃ Plugin Path ┃
|
||||||
|
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||||
|
│ aprsd-weewx-plugin │ WeewxMQTTPlugin │ 1.0 │ RegexCommand │ aprsd_weewx_plugin.weewx.WeewxMQTTPlugin │
|
||||||
|
└────────────────────┴─────────────────┴─────────┴──────────────┴──────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Installation
|
||||||
=============
|
=============
|
||||||
|
|
||||||
pip install aprsd
|
To install ``aprsd``, use Pip:
|
||||||
|
|
||||||
Example usage:
|
``pip install aprsd``
|
||||||
|
|
||||||
|
Example usage
|
||||||
==============
|
==============
|
||||||
|
|
||||||
aprsd -h
|
``aprsd -h``
|
||||||
|
|
||||||
Help
|
Help
|
||||||
====
|
====
|
||||||
::
|
::
|
||||||
|
|
||||||
└─[$] > aprsd -h
|
└─> aprsd -h
|
||||||
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
Usage: aprsd [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
Shell completion for click-completion-command Available shell types:
|
|
||||||
bash Bourne again shell fish Friendly interactive shell
|
|
||||||
powershell Windows PowerShell zsh Z shell Default type: auto
|
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--version Show the version and exit.
|
--version Show the version and exit.
|
||||||
-h, --help Show this message and exit.
|
-h, --help Show this message and exit.
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
install Install the click-completion-command completion
|
check-version Check this version against the latest in pypi.org.
|
||||||
sample-config This dumps the config to stdout.
|
completion Click Completion subcommands
|
||||||
|
dev Development type subcommands
|
||||||
|
healthcheck Check the health of the running aprsd server.
|
||||||
|
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.
|
send-message Send a message to a callsign via APRS_IS.
|
||||||
server Start the aprsd server process.
|
server Start the aprsd server gateway process.
|
||||||
show Show the click-completion-command completion code
|
version Show the APRSD version.
|
||||||
|
webchat Web based HAM Radio chat program!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,90 +140,14 @@ Commands
|
||||||
Configuration
|
Configuration
|
||||||
=============
|
=============
|
||||||
This command outputs a sample config yml formatted block that you can edit
|
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
|
and use to pass in to ``aprsd`` with ``-c``. By default aprsd looks in ``~/.config/aprsd/aprsd.yml``
|
||||||
|
|
||||||
aprsd sample-config
|
``aprsd sample-config``
|
||||||
|
|
||||||
Output
|
|
||||||
======
|
|
||||||
::
|
::
|
||||||
|
|
||||||
└─> aprsd sample-config
|
└─> aprsd sample-config
|
||||||
aprs:
|
...
|
||||||
# Get the passcode for your callsign here:
|
|
||||||
# https://apps.magicbug.co.uk/passcode
|
|
||||||
host: rotate.aprs2.net
|
|
||||||
login: CALLSIGN
|
|
||||||
password: '00000'
|
|
||||||
port: 14580
|
|
||||||
aprsd:
|
|
||||||
dateformat: '%m/%d/%Y %I:%M:%S %p'
|
|
||||||
email:
|
|
||||||
enabled: true
|
|
||||||
imap:
|
|
||||||
debug: false
|
|
||||||
host: imap.gmail.com
|
|
||||||
login: IMAP_USERNAME
|
|
||||||
password: IMAP_PASSWORD
|
|
||||||
port: 993
|
|
||||||
use_ssl: true
|
|
||||||
shortcuts:
|
|
||||||
aa: 5551239999@vtext.com
|
|
||||||
cl: craiglamparter@somedomain.org
|
|
||||||
wb: 555309@vtext.com
|
|
||||||
smtp:
|
|
||||||
debug: false
|
|
||||||
host: smtp.gmail.com
|
|
||||||
login: SMTP_USERNAME
|
|
||||||
password: SMTP_PASSWORD
|
|
||||||
port: 465
|
|
||||||
use_ssl: false
|
|
||||||
enabled_plugins:
|
|
||||||
- aprsd.plugins.email.EmailPlugin
|
|
||||||
- aprsd.plugins.fortune.FortunePlugin
|
|
||||||
- aprsd.plugins.location.LocationPlugin
|
|
||||||
- aprsd.plugins.ping.PingPlugin
|
|
||||||
- aprsd.plugins.query.QueryPlugin
|
|
||||||
- aprsd.plugins.stock.StockPlugin
|
|
||||||
- aprsd.plugins.time.TimePlugin
|
|
||||||
- aprsd.plugins.weather.USWeatherPlugin
|
|
||||||
- aprsd.plugins.version.VersionPlugin
|
|
||||||
logfile: /tmp/aprsd.log
|
|
||||||
logformat: '[%(asctime)s] [%(threadName)-12s] [%(levelname)-5.5s] %(message)s - [%(pathname)s:%(lineno)d]'
|
|
||||||
trace: false
|
|
||||||
units: imperial
|
|
||||||
web:
|
|
||||||
enabled: true
|
|
||||||
host: 0.0.0.0
|
|
||||||
logging_enabled: true
|
|
||||||
port: 8001
|
|
||||||
users:
|
|
||||||
admin: aprsd
|
|
||||||
ham:
|
|
||||||
callsign: CALLSIGN
|
|
||||||
services:
|
|
||||||
aprs.fi:
|
|
||||||
# Get the apiKey from your aprs.fi account here:
|
|
||||||
# http://aprs.fi/account
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
avwx:
|
|
||||||
# (Optional for AVWXWeatherPlugin)
|
|
||||||
# Use hosted avwx-api here: https://avwx.rest
|
|
||||||
# or deploy your own from here:
|
|
||||||
# https://github.com/avwx-rest/avwx-api
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
base_url: http://host:port
|
|
||||||
opencagedata:
|
|
||||||
# (Optional for TimeOpenCageDataPlugin)
|
|
||||||
# Get the apiKey from your opencagedata account here:
|
|
||||||
# https://opencagedata.com/dashboard#api-keys
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
openweathermap:
|
|
||||||
# (Optional for OWMWeatherPlugin)
|
|
||||||
# Get the apiKey from your
|
|
||||||
# openweathermap account here:
|
|
||||||
# https://home.openweathermap.org/api_keys
|
|
||||||
apiKey: APIKEYVALUE
|
|
||||||
|
|
||||||
server
|
server
|
||||||
======
|
======
|
||||||
|
@ -259,35 +158,35 @@ look for incomming commands to the callsign configured in the config file
|
||||||
::
|
::
|
||||||
|
|
||||||
└─[$] > aprsd server --help
|
└─[$] > aprsd server --help
|
||||||
Usage: aprsd server [OPTIONS]
|
Usage: aprsd server [OPTIONS]
|
||||||
|
|
||||||
Start the aprsd server process.
|
Start the aprsd server gateway process.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
||||||
The log level to use for aprsd.log
|
The log level to use for aprsd.log
|
||||||
[default: INFO]
|
[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.
|
||||||
|
|
||||||
--quiet Don't log to stdout
|
└─> aprsd server
|
||||||
--disable-validation Disable email shortcut validation. Bad
|
|
||||||
email addresses can result in broken email
|
|
||||||
responses!!
|
|
||||||
|
|
||||||
-c, --config TEXT The aprsd config file to use for options.
|
|
||||||
[default:
|
|
||||||
/home/waboring/.config/aprsd/aprsd.yml]
|
|
||||||
|
|
||||||
-f, --flush Flush out all old aged messages on disk.
|
|
||||||
[default: False]
|
|
||||||
|
|
||||||
-h, --help Show this message and exit.
|
|
||||||
|
|
||||||
$ aprsd server
|
|
||||||
Load config
|
Load config
|
||||||
[02/13/2021 09:22:09 AM] [MainThread ] [INFO ] APRSD Started version: 1.6.0
|
12/07/2021 03:16:17 PM MainThread INFO APRSD is up to date server.py:51
|
||||||
[02/13/2021 09:22:09 AM] [MainThread ] [INFO ] Checking IMAP configuration
|
12/07/2021 03:16:17 PM MainThread INFO APRSD Started version: 2.5.6 server.py:52
|
||||||
[02/13/2021 09:22:09 AM] [MainThread ] [INFO ] Checking SMTP configuration
|
12/07/2021 03:16:17 PM MainThread INFO Using CONFIG values: server.py:55
|
||||||
[02/13/2021 09:22:10 AM] [MainThread ] [INFO ] Validating 2 Email shortcuts. This can take up to 10 seconds per shortcut
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
send-message
|
send-message
|
||||||
|
@ -299,32 +198,30 @@ test messages
|
||||||
::
|
::
|
||||||
|
|
||||||
└─[$] > aprsd send-message -h
|
└─[$] > aprsd send-message -h
|
||||||
Usage: aprsd send-message [OPTIONS] TOCALLSIGN [COMMAND]...
|
Usage: aprsd send-message [OPTIONS] TOCALLSIGN COMMAND...
|
||||||
|
|
||||||
Send a message to a callsign via APRS_IS.
|
Send a message to a callsign via APRS_IS.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
--loglevel [CRITICAL|ERROR|WARNING|INFO|DEBUG]
|
||||||
The log level to use for aprsd.log
|
The log level to use for aprsd.log
|
||||||
[default: DEBUG]
|
[default: INFO]
|
||||||
|
|
||||||
--quiet Don't log to stdout
|
|
||||||
-c, --config TEXT The aprsd config file to use for options.
|
-c, --config TEXT The aprsd config file to use for options.
|
||||||
[default: ~/.config/aprsd/aprsd.yml]
|
[default:
|
||||||
|
/Users/i530566/.config/aprsd/aprsd.yml]
|
||||||
|
--quiet Don't log to stdout
|
||||||
--aprs-login TEXT What callsign to send the message from.
|
--aprs-login TEXT What callsign to send the message from.
|
||||||
[env var: APRS_LOGIN]
|
[env var: APRS_LOGIN]
|
||||||
|
|
||||||
--aprs-password TEXT the APRS-IS password for APRS_LOGIN [env
|
--aprs-password TEXT the APRS-IS password for APRS_LOGIN [env
|
||||||
var: APRS_PASSWORD]
|
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.
|
-h, --help Show this message and exit.
|
||||||
|
|
||||||
|
|
||||||
Example output:
|
|
||||||
===============
|
|
||||||
|
|
||||||
|
|
||||||
SEND EMAIL (radio to smtp server)
|
SEND EMAIL (radio to smtp server)
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
|
@ -395,25 +292,35 @@ AND... ping, fortune, time.....
|
||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
|
|
||||||
* git clone git@github.com:craigerl/aprsd.git
|
* ``git clone git@github.com:craigerl/aprsd.git``
|
||||||
* cd aprsd
|
* ``cd aprsd``
|
||||||
* make
|
* ``make``
|
||||||
|
|
||||||
Workflow
|
Workflow
|
||||||
========
|
========
|
||||||
|
|
||||||
While working aprsd, The workflow is as follows
|
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``
|
||||||
|
|
||||||
* checkout a new branch to work on
|
|
||||||
* git checkout -b mybranch
|
|
||||||
* Edit code
|
|
||||||
* run tox -epep8
|
|
||||||
* run tox -efmt
|
|
||||||
* run tox -p
|
|
||||||
* git commit ( This will run the pre-commit hooks which does checks too )
|
|
||||||
* Once you are done with all of your commits, then push up the branch to
|
* Once you are done with all of your commits, then push up the branch to
|
||||||
github
|
github with:
|
||||||
* git push -u origin mybranch
|
|
||||||
|
``git push -u origin mybranch``
|
||||||
|
|
||||||
* Create a pull request from your branch so github tests can run and we can do
|
* Create a pull request from your branch so github tests can run and we can do
|
||||||
a code review.
|
a code review.
|
||||||
|
|
||||||
|
@ -423,21 +330,21 @@ Release
|
||||||
|
|
||||||
To do release to pypi:
|
To do release to pypi:
|
||||||
|
|
||||||
* Tag release with
|
* Tag release with:
|
||||||
|
|
||||||
git tag -v1.XX -m "New release"
|
``git tag -v1.XX -m "New release"``
|
||||||
|
|
||||||
* push release tag up
|
* Push release tag:
|
||||||
|
|
||||||
git push origin master --tags
|
``git push origin master --tags``
|
||||||
|
|
||||||
* Do a test build and verify build is valid
|
* Do a test build and verify build is valid by running:
|
||||||
|
|
||||||
make build
|
``make build``
|
||||||
|
|
||||||
* Once twine is happy, upload release to pypi
|
* Once twine is happy, upload release to pypi:
|
||||||
|
|
||||||
make upload
|
``make upload``
|
||||||
|
|
||||||
|
|
||||||
Docker Container
|
Docker Container
|
||||||
|
@ -455,24 +362,62 @@ the repo.
|
||||||
Official Build
|
Official Build
|
||||||
==============
|
==============
|
||||||
|
|
||||||
docker build -t hemna6969/aprsd:latest .
|
``docker build -t hemna6969/aprsd:latest .``
|
||||||
|
|
||||||
Development Build
|
Development Build
|
||||||
=================
|
=================
|
||||||
|
|
||||||
docker build -t hemna6969/aprsd:latest -f Dockerfile-dev .
|
``docker build -t hemna6969/aprsd:latest -f Dockerfile-dev .``
|
||||||
|
|
||||||
|
|
||||||
Running the container
|
Running the container
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
There is a docker-compose.yml file that can be used to run your container.
|
There is a ``docker-compose.yml`` file in the ``docker/`` directory
|
||||||
There are 2 volumes defined that can be used to store your configuration
|
that can be used to run your container. To provide the container
|
||||||
and the plugins directory: /config and /plugins
|
an ``aprsd.conf`` configuration file, change your
|
||||||
|
``docker-compose.yml`` as shown below:
|
||||||
|
|
||||||
If you want to install plugins at container start time, then use the
|
::
|
||||||
environment var in docker-compose.yml specified as APRS_PLUGINS
|
|
||||||
Provide a csv list of pypi installable plugins. Then make sure the plugin
|
volumes:
|
||||||
python file is in your /plugins volume and the plugin will be installed at
|
- $HOME/.config/aprsd:/config
|
||||||
container startup. The plugin may have dependencies that are required.
|
|
||||||
The plugin file should be copied to /plugins for loading by aprsd
|
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
|
||||||
|
|
|
@ -23,9 +23,11 @@ beautifulsoup4
|
||||||
wrapt
|
wrapt
|
||||||
# kiss3 uses attrs
|
# kiss3 uses attrs
|
||||||
kiss3
|
kiss3
|
||||||
attrs==22.1.0
|
attrs
|
||||||
# for mobile checking
|
# for mobile checking
|
||||||
user-agents
|
user-agents
|
||||||
pyopenssl
|
pyopenssl
|
||||||
dataclasses
|
dataclasses
|
||||||
dacite2
|
dacite2
|
||||||
|
oslo.config
|
||||||
|
rpyc
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.9
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile --annotation-style=line --resolver=backtracking requirements.in
|
# pip-compile --annotation-style=line --resolver=backtracking requirements.in
|
||||||
#
|
#
|
||||||
aprslib==0.7.2 # via -r requirements.in
|
aprslib==0.7.2 # via -r requirements.in
|
||||||
attrs==22.1.0 # via -r requirements.in, ax253, kiss3
|
attrs==22.2.0 # via -r requirements.in, ax253, kiss3
|
||||||
ax253==0.1.5.post1 # via kiss3
|
ax253==0.1.5.post1 # via kiss3
|
||||||
beautifulsoup4==4.11.1 # via -r requirements.in
|
beautifulsoup4==4.11.1 # via -r requirements.in
|
||||||
bidict==0.22.0 # via python-socketio
|
bidict==0.22.0 # via python-socketio
|
||||||
bitarray==2.6.0 # via ax253, kiss3
|
bitarray==2.6.1 # via ax253, kiss3
|
||||||
certifi==2022.12.7 # via requests
|
certifi==2022.12.7 # via requests
|
||||||
cffi==1.15.1 # via cryptography
|
cffi==1.15.1 # via cryptography
|
||||||
charset-normalizer==2.1.1 # via requests
|
charset-normalizer==2.1.1 # via requests
|
||||||
|
@ -19,6 +19,7 @@ commonmark==0.9.1 # via rich
|
||||||
cryptography==38.0.4 # via pyopenssl
|
cryptography==38.0.4 # via pyopenssl
|
||||||
dacite2==2.0.0 # via -r requirements.in
|
dacite2==2.0.0 # via -r requirements.in
|
||||||
dataclasses==0.6 # via -r requirements.in
|
dataclasses==0.6 # via -r requirements.in
|
||||||
|
debtcollector==2.5.0 # via oslo-config
|
||||||
dnspython==2.2.1 # via eventlet
|
dnspython==2.2.1 # via eventlet
|
||||||
eventlet==0.33.2 # via -r requirements.in
|
eventlet==0.33.2 # via -r requirements.in
|
||||||
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
|
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
|
||||||
|
@ -28,13 +29,17 @@ flask-socketio==5.3.2 # via -r requirements.in
|
||||||
greenlet==2.0.1 # via eventlet
|
greenlet==2.0.1 # via eventlet
|
||||||
idna==3.4 # via requests
|
idna==3.4 # via requests
|
||||||
imapclient==2.3.1 # via -r requirements.in
|
imapclient==2.3.1 # via -r requirements.in
|
||||||
importlib-metadata==5.1.0 # via ax253, flask, kiss3
|
importlib-metadata==5.2.0 # via ax253, kiss3
|
||||||
itsdangerous==2.1.2 # via flask
|
itsdangerous==2.1.2 # via flask
|
||||||
jinja2==3.1.2 # via click-completion, flask
|
jinja2==3.1.2 # via click-completion, flask
|
||||||
kiss3==8.0.0 # via -r requirements.in
|
kiss3==8.0.0 # via -r requirements.in
|
||||||
markupsafe==2.1.1 # via jinja2
|
markupsafe==2.1.1 # via jinja2
|
||||||
pbr==5.11.0 # via -r requirements.in
|
netaddr==0.8.0 # via oslo-config
|
||||||
|
oslo-config==9.0.0 # via -r requirements.in
|
||||||
|
oslo-i18n==5.1.0 # via oslo-config
|
||||||
|
pbr==5.11.0 # via -r requirements.in, oslo-i18n, stevedore
|
||||||
pluggy==1.0.0 # via -r requirements.in
|
pluggy==1.0.0 # via -r requirements.in
|
||||||
|
plumbum==1.8.0 # via rpyc
|
||||||
pycparser==2.21 # via cffi
|
pycparser==2.21 # via cffi
|
||||||
pygments==2.13.0 # via rich
|
pygments==2.13.0 # via rich
|
||||||
pyopenssl==22.1.0 # via -r requirements.in
|
pyopenssl==22.1.0 # via -r requirements.in
|
||||||
|
@ -42,13 +47,16 @@ pyserial==3.5 # via pyserial-asyncio
|
||||||
pyserial-asyncio==0.6 # via kiss3
|
pyserial-asyncio==0.6 # via kiss3
|
||||||
python-engineio==4.3.4 # via python-socketio
|
python-engineio==4.3.4 # via python-socketio
|
||||||
python-socketio==5.7.2 # via flask-socketio
|
python-socketio==5.7.2 # via flask-socketio
|
||||||
pytz==2022.6 # via -r requirements.in
|
pytz==2022.7 # via -r requirements.in
|
||||||
pyyaml==6.0 # via -r requirements.in
|
pyyaml==6.0 # via -r requirements.in, oslo-config
|
||||||
requests==2.28.1 # via -r requirements.in, update-checker
|
requests==2.28.1 # via -r requirements.in, oslo-config, update-checker
|
||||||
|
rfc3986==2.0.0 # via oslo-config
|
||||||
rich==12.6.0 # via -r requirements.in
|
rich==12.6.0 # via -r requirements.in
|
||||||
|
rpyc==5.3.0 # via -r requirements.in
|
||||||
shellingham==1.5.0 # via click-completion
|
shellingham==1.5.0 # via click-completion
|
||||||
six==1.16.0 # via -r requirements.in, click-completion, eventlet, imapclient
|
six==1.16.0 # via -r requirements.in, click-completion, eventlet, imapclient
|
||||||
soupsieve==2.3.2.post1 # via beautifulsoup4
|
soupsieve==2.3.2.post1 # via beautifulsoup4
|
||||||
|
stevedore==4.1.1 # via oslo-config
|
||||||
tabulate==0.9.0 # via -r requirements.in
|
tabulate==0.9.0 # via -r requirements.in
|
||||||
thesmuggler==1.0.1 # via -r requirements.in
|
thesmuggler==1.0.1 # via -r requirements.in
|
||||||
ua-parser==0.16.1 # via user-agents
|
ua-parser==0.16.1 # via user-agents
|
||||||
|
@ -56,5 +64,5 @@ update-checker==0.18.0 # via -r requirements.in
|
||||||
urllib3==1.26.13 # via requests
|
urllib3==1.26.13 # via requests
|
||||||
user-agents==2.2.0 # via -r requirements.in
|
user-agents==2.2.0 # via -r requirements.in
|
||||||
werkzeug==2.1.2 # via -r requirements.in, flask
|
werkzeug==2.1.2 # via -r requirements.in, flask
|
||||||
wrapt==1.14.1 # via -r requirements.in
|
wrapt==1.14.1 # via -r requirements.in, debtcollector
|
||||||
zipp==3.11.0 # via importlib-metadata
|
zipp==3.11.0 # via importlib-metadata
|
||||||
|
|
|
@ -34,6 +34,10 @@ packages =
|
||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
aprsd = aprsd.aprsd:main
|
aprsd = aprsd.aprsd:main
|
||||||
|
oslo.config.opts =
|
||||||
|
aprsd.conf = aprsd.conf.opts:list_opts
|
||||||
|
oslo.config.opts.defaults =
|
||||||
|
aprsd.conf = aprsd.conf:set_lib_defaults
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = docs
|
source-dir = docs
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import typing as t
|
|
||||||
import unittest
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from click.testing import CliRunner
|
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
|
||||||
from aprsd.aprsd import cli
|
|
||||||
from aprsd.cmds import dev # noqa
|
|
||||||
|
|
||||||
|
|
||||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
|
||||||
|
|
||||||
|
|
||||||
class TestDevTestPluginCommand(unittest.TestCase):
|
|
||||||
|
|
||||||
def _build_config(self, login=None, password=None):
|
|
||||||
config = {
|
|
||||||
"aprs": {},
|
|
||||||
"aprsd": {
|
|
||||||
"trace": False,
|
|
||||||
"watch_list": {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if login:
|
|
||||||
config["aprs"]["login"] = login
|
|
||||||
|
|
||||||
if password:
|
|
||||||
config["aprs"]["password"] = password
|
|
||||||
|
|
||||||
return aprsd_config.Config(config)
|
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.logging.log.setup_logging")
|
|
||||||
def test_no_login(self, mock_logging, mock_parse_config):
|
|
||||||
"""Make sure we get an error if there is no login and config."""
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
mock_parse_config.return_value = self._build_config()
|
|
||||||
|
|
||||||
result = runner.invoke(
|
|
||||||
cli, [
|
|
||||||
"dev", "test-plugin",
|
|
||||||
"-p", "aprsd.plugins.version.VersionPlugin",
|
|
||||||
"bogus command",
|
|
||||||
],
|
|
||||||
catch_exceptions=False,
|
|
||||||
)
|
|
||||||
# rich.print(f"EXIT CODE {result.exit_code}")
|
|
||||||
# rich.print(f"Exception {result.exception}")
|
|
||||||
# rich.print(f"OUTPUT {result.output}")
|
|
||||||
assert result.exit_code == -1
|
|
||||||
assert "Must set --aprs_login or APRS_LOGIN" in result.output
|
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.logging.log.setup_logging")
|
|
||||||
def test_no_plugin_arg(self, mock_logging, mock_parse_config):
|
|
||||||
"""Make sure we get an error if there is no login and config."""
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
mock_parse_config.return_value = self._build_config(login="something")
|
|
||||||
|
|
||||||
result = runner.invoke(
|
|
||||||
cli, ["dev", "test-plugin", "bogus command"],
|
|
||||||
catch_exceptions=False,
|
|
||||||
)
|
|
||||||
assert result.exit_code == 2
|
|
||||||
assert "Failed to provide -p option to test a plugin" in result.output
|
|
|
@ -3,78 +3,42 @@ import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
from aprsd import conf # noqa : F401
|
||||||
from aprsd.aprsd import cli
|
from aprsd.aprsd import cli
|
||||||
from aprsd.cmds import send_message # noqa
|
from aprsd.cmds import send_message # noqa
|
||||||
|
|
||||||
|
from .. import fake
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
|
|
||||||
class TestSendMessageCommand(unittest.TestCase):
|
class TestSendMessageCommand(unittest.TestCase):
|
||||||
|
|
||||||
def _build_config(self, login=None, password=None):
|
def config_and_init(self, login=None, password=None):
|
||||||
config = {
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
"aprs": {},
|
CONF.trace_enabled = False
|
||||||
"aprsd": {
|
CONF.watch_list.packet_keep_count = 1
|
||||||
"trace": False,
|
|
||||||
"watch_list": {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if login:
|
if login:
|
||||||
config["aprs"]["login"] = login
|
CONF.aprs_network.login = login
|
||||||
|
|
||||||
if password:
|
if password:
|
||||||
config["aprs"]["password"] = password
|
CONF.aprs_network.password = password
|
||||||
|
|
||||||
return aprsd_config.Config(config)
|
CONF.admin.user = "admin"
|
||||||
|
CONF.admin.password = "password"
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.logging.log.setup_logging")
|
@mock.patch("aprsd.logging.log.setup_logging")
|
||||||
def test_no_login(self, mock_logging, mock_parse_config):
|
def test_no_tocallsign(self, mock_logging):
|
||||||
"""Make sure we get an error if there is no login and config."""
|
|
||||||
return
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
mock_parse_config.return_value = self._build_config()
|
|
||||||
|
|
||||||
result = runner.invoke(
|
|
||||||
cli, ["send-message", "WB4BOR", "wx"],
|
|
||||||
catch_exceptions=False,
|
|
||||||
)
|
|
||||||
# rich.print(f"EXIT CODE {result.exit_code}")
|
|
||||||
# rich.print(f"Exception {result.exception}")
|
|
||||||
# rich.print(f"OUTPUT {result.output}")
|
|
||||||
assert result.exit_code == -1
|
|
||||||
assert "Must set --aprs_login or APRS_LOGIN" in result.output
|
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.logging.log.setup_logging")
|
|
||||||
def test_no_password(self, mock_logging, mock_parse_config):
|
|
||||||
"""Make sure we get an error if there is no password and config."""
|
|
||||||
|
|
||||||
return
|
|
||||||
runner = CliRunner()
|
|
||||||
mock_parse_config.return_value = self._build_config(login="something")
|
|
||||||
|
|
||||||
result = runner.invoke(
|
|
||||||
cli, ["send-message", "WB4BOR", "wx"],
|
|
||||||
catch_exceptions=False,
|
|
||||||
)
|
|
||||||
assert result.exit_code == -1
|
|
||||||
assert "Must set --aprs-password or APRS_PASSWORD" in result.output
|
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.logging.log.setup_logging")
|
|
||||||
def test_no_tocallsign(self, mock_logging, mock_parse_config):
|
|
||||||
"""Make sure we get an error if there is no tocallsign."""
|
"""Make sure we get an error if there is no tocallsign."""
|
||||||
|
|
||||||
runner = CliRunner()
|
self.config_and_init(
|
||||||
mock_parse_config.return_value = self._build_config(
|
|
||||||
login="something",
|
login="something",
|
||||||
password="another",
|
password="another",
|
||||||
)
|
)
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
cli, ["send-message"],
|
cli, ["send-message"],
|
||||||
|
@ -83,16 +47,15 @@ class TestSendMessageCommand(unittest.TestCase):
|
||||||
assert result.exit_code == 2
|
assert result.exit_code == 2
|
||||||
assert "Error: Missing argument 'TOCALLSIGN'" in result.output
|
assert "Error: Missing argument 'TOCALLSIGN'" in result.output
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.logging.log.setup_logging")
|
@mock.patch("aprsd.logging.log.setup_logging")
|
||||||
def test_no_command(self, mock_logging, mock_parse_config):
|
def test_no_command(self, mock_logging):
|
||||||
"""Make sure we get an error if there is no command."""
|
"""Make sure we get an error if there is no command."""
|
||||||
|
|
||||||
runner = CliRunner()
|
self.config_and_init(
|
||||||
mock_parse_config.return_value = self._build_config(
|
|
||||||
login="something",
|
login="something",
|
||||||
password="another",
|
password="another",
|
||||||
)
|
)
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
cli, ["send-message", "WB4BOR"],
|
cli, ["send-message", "WB4BOR"],
|
||||||
|
|
|
@ -5,112 +5,81 @@ from unittest import mock
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
import flask
|
import flask
|
||||||
import flask_socketio
|
import flask_socketio
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd import packets
|
|
||||||
from aprsd.cmds import webchat # noqa
|
from aprsd.cmds import webchat # noqa
|
||||||
from aprsd.packets import core
|
from aprsd.packets import core
|
||||||
|
|
||||||
from .. import fake
|
from .. import fake
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
|
|
||||||
class TestSendMessageCommand(unittest.TestCase):
|
class TestSendMessageCommand(unittest.TestCase):
|
||||||
|
|
||||||
def _build_config(self, login=None, password=None):
|
def config_and_init(self, login=None, password=None):
|
||||||
config = {
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
"aprs": {},
|
CONF.trace_enabled = False
|
||||||
"aprsd": {
|
CONF.watch_list.packet_keep_count = 1
|
||||||
"trace": False,
|
|
||||||
"web": {
|
|
||||||
"users": {"admin": "password"},
|
|
||||||
},
|
|
||||||
"watch_list": {"packet_keep_count": 1},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if login:
|
if login:
|
||||||
config["aprs"]["login"] = login
|
CONF.aprs_network.login = login
|
||||||
|
|
||||||
if password:
|
if password:
|
||||||
config["aprs"]["password"] = password
|
CONF.aprs_network.password = password
|
||||||
|
|
||||||
return aprsd_config.Config(config)
|
CONF.admin.user = "admin"
|
||||||
|
CONF.admin.password = "password"
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
def test_missing_config(self, mock_parse_config):
|
|
||||||
CliRunner()
|
|
||||||
cfg = self._build_config()
|
|
||||||
del cfg["aprsd"]["web"]["users"]
|
|
||||||
mock_parse_config.return_value = cfg
|
|
||||||
|
|
||||||
server = webchat.WebChatFlask()
|
|
||||||
self.assertRaises(
|
|
||||||
KeyError,
|
|
||||||
server.set_config, cfg,
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.logging.log.setup_logging")
|
@mock.patch("aprsd.logging.log.setup_logging")
|
||||||
def test_init_flask(self, mock_logging, mock_parse_config):
|
def test_init_flask(self, mock_logging):
|
||||||
"""Make sure we get an error if there is no login and config."""
|
"""Make sure we get an error if there is no login and config."""
|
||||||
|
|
||||||
CliRunner()
|
CliRunner()
|
||||||
cfg = self._build_config()
|
self.config_and_init()
|
||||||
mock_parse_config.return_value = cfg
|
|
||||||
|
|
||||||
socketio, flask_app = webchat.init_flask(cfg, "DEBUG", False)
|
socketio, flask_app = webchat.init_flask("DEBUG", False)
|
||||||
self.assertIsInstance(socketio, flask_socketio.SocketIO)
|
self.assertIsInstance(socketio, flask_socketio.SocketIO)
|
||||||
self.assertIsInstance(flask_app, flask.Flask)
|
self.assertIsInstance(flask_app, flask.Flask)
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.packets.tracker.PacketTrack.remove")
|
@mock.patch("aprsd.packets.tracker.PacketTrack.remove")
|
||||||
@mock.patch("aprsd.cmds.webchat.socketio.emit")
|
@mock.patch("aprsd.cmds.webchat.socketio")
|
||||||
def test_process_ack_packet(
|
def test_process_ack_packet(
|
||||||
self, mock_parse_config,
|
self,
|
||||||
mock_remove, mock_emit,
|
mock_remove, mock_socketio,
|
||||||
):
|
):
|
||||||
config = self._build_config()
|
self.config_and_init()
|
||||||
mock_parse_config.return_value = config
|
mock_socketio.emit = mock.MagicMock()
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="blah",
|
message="blah",
|
||||||
msg_number=1,
|
msg_number=1,
|
||||||
message_format=core.PACKET_TYPE_ACK,
|
message_format=core.PACKET_TYPE_ACK,
|
||||||
)
|
)
|
||||||
socketio = mock.MagicMock()
|
socketio = mock.MagicMock()
|
||||||
packets.PacketList(config=config)
|
wcp = webchat.WebChatProcessPacketThread(packet, socketio)
|
||||||
packets.PacketTrack(config=config)
|
|
||||||
packets.WatchList(config=config)
|
|
||||||
packets.SeenList(config=config)
|
|
||||||
wcp = webchat.WebChatProcessPacketThread(config, packet, socketio)
|
|
||||||
|
|
||||||
wcp.process_ack_packet(packet)
|
wcp.process_ack_packet(packet)
|
||||||
mock_remove.called_once()
|
mock_remove.called_once()
|
||||||
mock_emit.called_once()
|
mock_socketio.called_once()
|
||||||
|
|
||||||
@mock.patch("aprsd.config.parse_config")
|
|
||||||
@mock.patch("aprsd.packets.PacketList.rx")
|
@mock.patch("aprsd.packets.PacketList.rx")
|
||||||
@mock.patch("aprsd.cmds.webchat.socketio.emit")
|
@mock.patch("aprsd.cmds.webchat.socketio")
|
||||||
def test_process_our_message_packet(
|
def test_process_our_message_packet(
|
||||||
self, mock_parse_config,
|
self,
|
||||||
mock_packet_add,
|
mock_packet_add,
|
||||||
mock_emit,
|
mock_socketio,
|
||||||
):
|
):
|
||||||
config = self._build_config()
|
self.config_and_init()
|
||||||
mock_parse_config.return_value = config
|
mock_socketio.emit = mock.MagicMock()
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="blah",
|
message="blah",
|
||||||
msg_number=1,
|
msg_number=1,
|
||||||
message_format=core.PACKET_TYPE_MESSAGE,
|
message_format=core.PACKET_TYPE_MESSAGE,
|
||||||
)
|
)
|
||||||
socketio = mock.MagicMock()
|
socketio = mock.MagicMock()
|
||||||
packets.PacketList(config=config)
|
wcp = webchat.WebChatProcessPacketThread(packet, socketio)
|
||||||
packets.PacketTrack(config=config)
|
|
||||||
packets.WatchList(config=config)
|
|
||||||
packets.SeenList(config=config)
|
|
||||||
wcp = webchat.WebChatProcessPacketThread(config, packet, socketio)
|
|
||||||
|
|
||||||
wcp.process_our_message_packet(packet)
|
wcp.process_our_message_packet(packet)
|
||||||
mock_packet_add.called_once()
|
mock_packet_add.called_once()
|
||||||
mock_emit.called_once()
|
mock_socketio.called_once()
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd.plugins import fortune as fortune_plugin
|
from aprsd.plugins import fortune as fortune_plugin
|
||||||
|
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestFortunePlugin(test_plugin.TestPlugin):
|
class TestFortunePlugin(test_plugin.TestPlugin):
|
||||||
@mock.patch("shutil.which")
|
@mock.patch("shutil.which")
|
||||||
def test_fortune_fail(self, mock_which):
|
def test_fortune_fail(self, mock_which):
|
||||||
mock_which.return_value = None
|
mock_which.return_value = None
|
||||||
fortune = fortune_plugin.FortunePlugin(self.config)
|
fortune = fortune_plugin.FortunePlugin()
|
||||||
expected = "FortunePlugin isn't enabled"
|
expected = "FortunePlugin isn't enabled"
|
||||||
packet = fake.fake_packet(message="fortune")
|
packet = fake.fake_packet(message="fortune")
|
||||||
actual = fortune.filter(packet)
|
actual = fortune.filter(packet)
|
||||||
|
@ -20,7 +26,8 @@ class TestFortunePlugin(test_plugin.TestPlugin):
|
||||||
def test_fortune_success(self, mock_which, mock_output):
|
def test_fortune_success(self, mock_which, mock_output):
|
||||||
mock_which.return_value = "/usr/bin/games/fortune"
|
mock_which.return_value = "/usr/bin/games/fortune"
|
||||||
mock_output.return_value = "Funny fortune"
|
mock_output.return_value = "Funny fortune"
|
||||||
fortune = fortune_plugin.FortunePlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
fortune = fortune_plugin.FortunePlugin()
|
||||||
|
|
||||||
expected = "Funny fortune"
|
expected = "Funny fortune"
|
||||||
packet = fake.fake_packet(message="fortune")
|
packet = fake.fake_packet(message="fortune")
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd.plugins import location as location_plugin
|
from aprsd.plugins import location as location_plugin
|
||||||
|
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestLocationPlugin(test_plugin.TestPlugin):
|
class TestLocationPlugin(test_plugin.TestPlugin):
|
||||||
|
|
||||||
@mock.patch("aprsd.config.Config.check_option")
|
def test_location_not_enabled_missing_aprs_fi_key(self):
|
||||||
def test_location_not_enabled_missing_aprs_fi_key(self, mock_check):
|
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the LocationPlugin will be disabled.
|
# the LocationPlugin will be disabled.
|
||||||
mock_check.side_effect = Exception
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
fortune = location_plugin.LocationPlugin(self.config)
|
CONF.aprs_fi.apiKey = None
|
||||||
|
fortune = location_plugin.LocationPlugin()
|
||||||
expected = "LocationPlugin isn't enabled"
|
expected = "LocationPlugin isn't enabled"
|
||||||
packet = fake.fake_packet(message="location")
|
packet = fake.fake_packet(message="location")
|
||||||
actual = fortune.filter(packet)
|
actual = fortune.filter(packet)
|
||||||
|
@ -23,7 +29,8 @@ class TestLocationPlugin(test_plugin.TestPlugin):
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the LocationPlugin will be disabled.
|
# the LocationPlugin will be disabled.
|
||||||
mock_check.side_effect = Exception
|
mock_check.side_effect = Exception
|
||||||
fortune = location_plugin.LocationPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
fortune = location_plugin.LocationPlugin()
|
||||||
expected = "Failed to fetch aprs.fi location"
|
expected = "Failed to fetch aprs.fi location"
|
||||||
packet = fake.fake_packet(message="location")
|
packet = fake.fake_packet(message="location")
|
||||||
actual = fortune.filter(packet)
|
actual = fortune.filter(packet)
|
||||||
|
@ -34,7 +41,8 @@ class TestLocationPlugin(test_plugin.TestPlugin):
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the LocationPlugin will be disabled.
|
# the LocationPlugin will be disabled.
|
||||||
mock_check.return_value = {"entries": []}
|
mock_check.return_value = {"entries": []}
|
||||||
fortune = location_plugin.LocationPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
fortune = location_plugin.LocationPlugin()
|
||||||
expected = "Failed to fetch aprs.fi location"
|
expected = "Failed to fetch aprs.fi location"
|
||||||
packet = fake.fake_packet(message="location")
|
packet = fake.fake_packet(message="location")
|
||||||
actual = fortune.filter(packet)
|
actual = fortune.filter(packet)
|
||||||
|
@ -57,7 +65,8 @@ class TestLocationPlugin(test_plugin.TestPlugin):
|
||||||
}
|
}
|
||||||
mock_weather.side_effect = Exception
|
mock_weather.side_effect = Exception
|
||||||
mock_time.return_value = 10
|
mock_time.return_value = 10
|
||||||
fortune = location_plugin.LocationPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
fortune = location_plugin.LocationPlugin()
|
||||||
expected = "KFAKE: Unknown Location 0' 10,11 0.0h ago"
|
expected = "KFAKE: Unknown Location 0' 10,11 0.0h ago"
|
||||||
packet = fake.fake_packet(message="location")
|
packet = fake.fake_packet(message="location")
|
||||||
actual = fortune.filter(packet)
|
actual = fortune.filter(packet)
|
||||||
|
@ -82,7 +91,8 @@ class TestLocationPlugin(test_plugin.TestPlugin):
|
||||||
wx_data = {"location": {"areaDescription": expected_town}}
|
wx_data = {"location": {"areaDescription": expected_town}}
|
||||||
mock_weather.return_value = wx_data
|
mock_weather.return_value = wx_data
|
||||||
mock_time.return_value = 10
|
mock_time.return_value = 10
|
||||||
fortune = location_plugin.LocationPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
fortune = location_plugin.LocationPlugin()
|
||||||
expected = f"KFAKE: {expected_town} 0' 10,11 0.0h ago"
|
expected = f"KFAKE: {expected_town} 0' 10,11 0.0h ago"
|
||||||
packet = fake.fake_packet(message="location")
|
packet = fake.fake_packet(message="location")
|
||||||
actual = fortune.filter(packet)
|
actual = fortune.filter(packet)
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from aprsd import client
|
from oslo_config import cfg
|
||||||
from aprsd import config as aprsd_config
|
|
||||||
from aprsd import packets
|
from aprsd import client, packets
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd.plugins import notify as notify_plugin
|
from aprsd.plugins import notify as notify_plugin
|
||||||
|
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_WATCHLIST_CALLSIGNS = [fake.FAKE_FROM_CALLSIGN]
|
CONF = cfg.CONF
|
||||||
|
DEFAULT_WATCHLIST_CALLSIGNS = fake.FAKE_FROM_CALLSIGN
|
||||||
|
|
||||||
|
|
||||||
class TestWatchListPlugin(test_plugin.TestPlugin):
|
class TestWatchListPlugin(test_plugin.TestPlugin):
|
||||||
|
@ -16,7 +18,7 @@ class TestWatchListPlugin(test_plugin.TestPlugin):
|
||||||
self.fromcall = fake.FAKE_FROM_CALLSIGN
|
self.fromcall = fake.FAKE_FROM_CALLSIGN
|
||||||
self.ack = 1
|
self.ack = 1
|
||||||
|
|
||||||
def _config(
|
def config_and_init(
|
||||||
self,
|
self,
|
||||||
watchlist_enabled=True,
|
watchlist_enabled=True,
|
||||||
watchlist_alert_callsign=None,
|
watchlist_alert_callsign=None,
|
||||||
|
@ -24,39 +26,33 @@ class TestWatchListPlugin(test_plugin.TestPlugin):
|
||||||
watchlist_packet_keep_count=None,
|
watchlist_packet_keep_count=None,
|
||||||
watchlist_callsigns=DEFAULT_WATCHLIST_CALLSIGNS,
|
watchlist_callsigns=DEFAULT_WATCHLIST_CALLSIGNS,
|
||||||
):
|
):
|
||||||
_config = aprsd_config.Config(aprsd_config.DEFAULT_CONFIG_DICT)
|
CONF.callsign = self.fromcall
|
||||||
default_wl = aprsd_config.DEFAULT_CONFIG_DICT["aprsd"]["watch_list"]
|
CONF.aprs_network.login = self.fromcall
|
||||||
|
CONF.aprs_fi.apiKey = "something"
|
||||||
_config["ham"]["callsign"] = self.fromcall
|
|
||||||
_config["aprsd"]["callsign"] = self.fromcall
|
|
||||||
_config["aprs"]["login"] = self.fromcall
|
|
||||||
_config["services"]["aprs.fi"]["apiKey"] = "something"
|
|
||||||
|
|
||||||
# Set the watchlist specific config options
|
# Set the watchlist specific config options
|
||||||
|
CONF.watch_list.enabled = watchlist_enabled
|
||||||
|
|
||||||
_config["aprsd"]["watch_list"]["enabled"] = watchlist_enabled
|
|
||||||
if not watchlist_alert_callsign:
|
if not watchlist_alert_callsign:
|
||||||
watchlist_alert_callsign = fake.FAKE_TO_CALLSIGN
|
watchlist_alert_callsign = fake.FAKE_TO_CALLSIGN
|
||||||
_config["aprsd"]["watch_list"]["alert_callsign"] = watchlist_alert_callsign
|
CONF.watch_list.alert_callsign = watchlist_alert_callsign
|
||||||
|
|
||||||
if not watchlist_alert_time_seconds:
|
if not watchlist_alert_time_seconds:
|
||||||
watchlist_alert_time_seconds = default_wl["alert_time_seconds"]
|
watchlist_alert_time_seconds = CONF.watch_list.alert_time_seconds
|
||||||
_config["aprsd"]["watch_list"]["alert_time_seconds"] = watchlist_alert_time_seconds
|
CONF.watch_list.alert_time_seconds = watchlist_alert_time_seconds
|
||||||
|
|
||||||
if not watchlist_packet_keep_count:
|
if not watchlist_packet_keep_count:
|
||||||
watchlist_packet_keep_count = default_wl["packet_keep_count"]
|
watchlist_packet_keep_count = CONF.watch_list.packet_keep_count
|
||||||
_config["aprsd"]["watch_list"]["packet_keep_count"] = watchlist_packet_keep_count
|
CONF.watch_list.packet_keep_count = watchlist_packet_keep_count
|
||||||
|
|
||||||
_config["aprsd"]["watch_list"]["callsigns"] = watchlist_callsigns
|
CONF.watch_list.callsigns = watchlist_callsigns
|
||||||
return _config
|
|
||||||
|
|
||||||
|
|
||||||
class TestAPRSDWatchListPluginBase(TestWatchListPlugin):
|
class TestAPRSDWatchListPluginBase(TestWatchListPlugin):
|
||||||
|
|
||||||
def test_watchlist_not_enabled(self):
|
def test_watchlist_not_enabled(self):
|
||||||
config = self._config(watchlist_enabled=False)
|
self.config_and_init(watchlist_enabled=False)
|
||||||
self.config_and_init(config=config)
|
plugin = fake.FakeWatchListPlugin()
|
||||||
plugin = fake.FakeWatchListPlugin(self.config)
|
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="version",
|
message="version",
|
||||||
|
@ -69,9 +65,8 @@ class TestAPRSDWatchListPluginBase(TestWatchListPlugin):
|
||||||
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
||||||
def test_watchlist_not_in_watchlist(self, mock_factory):
|
def test_watchlist_not_in_watchlist(self, mock_factory):
|
||||||
client.factory = mock_factory
|
client.factory = mock_factory
|
||||||
config = self._config()
|
self.config_and_init()
|
||||||
self.config_and_init(config=config)
|
plugin = fake.FakeWatchListPlugin()
|
||||||
plugin = fake.FakeWatchListPlugin(self.config)
|
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
fromcall="FAKE",
|
fromcall="FAKE",
|
||||||
|
@ -86,9 +81,8 @@ class TestAPRSDWatchListPluginBase(TestWatchListPlugin):
|
||||||
class TestNotifySeenPlugin(TestWatchListPlugin):
|
class TestNotifySeenPlugin(TestWatchListPlugin):
|
||||||
|
|
||||||
def test_disabled(self):
|
def test_disabled(self):
|
||||||
config = self._config(watchlist_enabled=False)
|
self.config_and_init(watchlist_enabled=False)
|
||||||
self.config_and_init(config=config)
|
plugin = notify_plugin.NotifySeenPlugin()
|
||||||
plugin = notify_plugin.NotifySeenPlugin(self.config)
|
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="version",
|
message="version",
|
||||||
|
@ -101,9 +95,8 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
||||||
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
@mock.patch("aprsd.client.ClientFactory", autospec=True)
|
||||||
def test_callsign_not_in_watchlist(self, mock_factory):
|
def test_callsign_not_in_watchlist(self, mock_factory):
|
||||||
client.factory = mock_factory
|
client.factory = mock_factory
|
||||||
config = self._config(watchlist_enabled=False)
|
self.config_and_init(watchlist_enabled=False)
|
||||||
self.config_and_init(config=config)
|
plugin = notify_plugin.NotifySeenPlugin()
|
||||||
plugin = notify_plugin.NotifySeenPlugin(self.config)
|
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="version",
|
message="version",
|
||||||
|
@ -118,12 +111,11 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
||||||
def test_callsign_in_watchlist_not_old(self, mock_is_old, mock_factory):
|
def test_callsign_in_watchlist_not_old(self, mock_is_old, mock_factory):
|
||||||
client.factory = mock_factory
|
client.factory = mock_factory
|
||||||
mock_is_old.return_value = False
|
mock_is_old.return_value = False
|
||||||
config = self._config(
|
self.config_and_init(
|
||||||
watchlist_enabled=True,
|
watchlist_enabled=True,
|
||||||
watchlist_callsigns=["WB4BOR"],
|
watchlist_callsigns=["WB4BOR"],
|
||||||
)
|
)
|
||||||
self.config_and_init(config=config)
|
plugin = notify_plugin.NotifySeenPlugin()
|
||||||
plugin = notify_plugin.NotifySeenPlugin(self.config)
|
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
fromcall="WB4BOR",
|
fromcall="WB4BOR",
|
||||||
|
@ -139,13 +131,12 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
||||||
def test_callsign_in_watchlist_old_same_alert_callsign(self, mock_is_old, mock_factory):
|
def test_callsign_in_watchlist_old_same_alert_callsign(self, mock_is_old, mock_factory):
|
||||||
client.factory = mock_factory
|
client.factory = mock_factory
|
||||||
mock_is_old.return_value = True
|
mock_is_old.return_value = True
|
||||||
config = self._config(
|
self.config_and_init(
|
||||||
watchlist_enabled=True,
|
watchlist_enabled=True,
|
||||||
watchlist_alert_callsign="WB4BOR",
|
watchlist_alert_callsign="WB4BOR",
|
||||||
watchlist_callsigns=["WB4BOR"],
|
watchlist_callsigns=["WB4BOR"],
|
||||||
)
|
)
|
||||||
self.config_and_init(config=config)
|
plugin = notify_plugin.NotifySeenPlugin()
|
||||||
plugin = notify_plugin.NotifySeenPlugin(self.config)
|
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
fromcall="WB4BOR",
|
fromcall="WB4BOR",
|
||||||
|
@ -163,13 +154,12 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
||||||
mock_is_old.return_value = True
|
mock_is_old.return_value = True
|
||||||
notify_callsign = fake.FAKE_TO_CALLSIGN
|
notify_callsign = fake.FAKE_TO_CALLSIGN
|
||||||
fromcall = "WB4BOR"
|
fromcall = "WB4BOR"
|
||||||
config = self._config(
|
self.config_and_init(
|
||||||
watchlist_enabled=True,
|
watchlist_enabled=True,
|
||||||
watchlist_alert_callsign=notify_callsign,
|
watchlist_alert_callsign=notify_callsign,
|
||||||
watchlist_callsigns=["WB4BOR"],
|
watchlist_callsigns=["WB4BOR"],
|
||||||
)
|
)
|
||||||
self.config_and_init(config=config)
|
plugin = notify_plugin.NotifySeenPlugin()
|
||||||
plugin = notify_plugin.NotifySeenPlugin(self.config)
|
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
fromcall=fromcall,
|
fromcall=fromcall,
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd.plugins import ping as ping_plugin
|
from aprsd.plugins import ping as ping_plugin
|
||||||
|
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestPingPlugin(test_plugin.TestPlugin):
|
class TestPingPlugin(test_plugin.TestPlugin):
|
||||||
@mock.patch("time.localtime")
|
@mock.patch("time.localtime")
|
||||||
def test_ping(self, mock_time):
|
def test_ping(self, mock_time):
|
||||||
|
@ -14,7 +20,8 @@ class TestPingPlugin(test_plugin.TestPlugin):
|
||||||
s = fake_time.tm_sec = 55
|
s = fake_time.tm_sec = 55
|
||||||
mock_time.return_value = fake_time
|
mock_time.return_value = fake_time
|
||||||
|
|
||||||
ping = ping_plugin.PingPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
ping = ping_plugin.PingPlugin()
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="location",
|
message="location",
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from aprsd import packets
|
from aprsd import packets
|
||||||
from aprsd.packets import tracker
|
from aprsd.packets import tracker
|
||||||
from aprsd.plugins import query as query_plugin
|
from aprsd.plugins import query as query_plugin
|
||||||
|
@ -7,11 +9,18 @@ from aprsd.plugins import query as query_plugin
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestQueryPlugin(test_plugin.TestPlugin):
|
class TestQueryPlugin(test_plugin.TestPlugin):
|
||||||
@mock.patch("aprsd.packets.tracker.PacketTrack.flush")
|
@mock.patch("aprsd.packets.tracker.PacketTrack.flush")
|
||||||
def test_query_flush(self, mock_flush):
|
def test_query_flush(self, mock_flush):
|
||||||
packet = fake.fake_packet(message="!delete")
|
packet = fake.fake_packet(message="!delete")
|
||||||
query = query_plugin.QueryPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
CONF.save_enabled = True
|
||||||
|
CONF.query_plugin.callsign = fake.FAKE_FROM_CALLSIGN
|
||||||
|
query = query_plugin.QueryPlugin()
|
||||||
|
query.enabled = True
|
||||||
|
|
||||||
expected = "Deleted ALL pending msgs."
|
expected = "Deleted ALL pending msgs."
|
||||||
actual = query.filter(packet)
|
actual = query.filter(packet)
|
||||||
|
@ -20,10 +29,13 @@ class TestQueryPlugin(test_plugin.TestPlugin):
|
||||||
|
|
||||||
@mock.patch("aprsd.packets.tracker.PacketTrack.restart_delayed")
|
@mock.patch("aprsd.packets.tracker.PacketTrack.restart_delayed")
|
||||||
def test_query_restart_delayed(self, mock_restart):
|
def test_query_restart_delayed(self, mock_restart):
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
CONF.save_enabled = True
|
||||||
|
CONF.query_plugin.callsign = fake.FAKE_FROM_CALLSIGN
|
||||||
track = tracker.PacketTrack()
|
track = tracker.PacketTrack()
|
||||||
track.data = {}
|
track.data = {}
|
||||||
packet = fake.fake_packet(message="!4")
|
packet = fake.fake_packet(message="!4")
|
||||||
query = query_plugin.QueryPlugin(self.config)
|
query = query_plugin.QueryPlugin()
|
||||||
|
|
||||||
expected = "No pending msgs to resend"
|
expected = "No pending msgs to resend"
|
||||||
actual = query.filter(packet)
|
actual = query.filter(packet)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from aprsd.plugins import time as time_plugin
|
from aprsd.plugins import time as time_plugin
|
||||||
|
@ -8,6 +9,9 @@ from aprsd.utils import fuzzy
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestTimePlugins(test_plugin.TestPlugin):
|
class TestTimePlugins(test_plugin.TestPlugin):
|
||||||
|
|
||||||
@mock.patch("aprsd.plugins.time.TimePlugin._get_local_tz")
|
@mock.patch("aprsd.plugins.time.TimePlugin._get_local_tz")
|
||||||
|
@ -25,7 +29,8 @@ class TestTimePlugins(test_plugin.TestPlugin):
|
||||||
h = int(local_t.strftime("%H"))
|
h = int(local_t.strftime("%H"))
|
||||||
m = int(local_t.strftime("%M"))
|
m = int(local_t.strftime("%M"))
|
||||||
fake_time.tm_sec = 13
|
fake_time.tm_sec = 13
|
||||||
time = time_plugin.TimePlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
time = time_plugin.TimePlugin()
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="location",
|
message="location",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from unittest import mock
|
from oslo_config import cfg
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd.plugins import version as version_plugin
|
from aprsd.plugins import version as version_plugin
|
||||||
|
@ -6,11 +6,16 @@ from aprsd.plugins import version as version_plugin
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestVersionPlugin(test_plugin.TestPlugin):
|
class TestVersionPlugin(test_plugin.TestPlugin):
|
||||||
@mock.patch("aprsd.plugin.PluginManager.get_plugins")
|
|
||||||
def test_version(self, mock_get_plugins):
|
def test_version(self):
|
||||||
expected = f"APRSD ver:{aprsd.__version__} uptime:00:00:00"
|
expected = f"APRSD ver:{aprsd.__version__} uptime:00:00:00"
|
||||||
version = version_plugin.VersionPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
version = version_plugin.VersionPlugin()
|
||||||
|
version.enabled = True
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="No",
|
message="No",
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd.plugins import weather as weather_plugin
|
from aprsd.plugins import weather as weather_plugin
|
||||||
|
|
||||||
from .. import fake, test_plugin
|
from .. import fake, test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestUSWeatherPluginPlugin(test_plugin.TestPlugin):
|
class TestUSWeatherPluginPlugin(test_plugin.TestPlugin):
|
||||||
|
|
||||||
@mock.patch("aprsd.config.Config.check_option")
|
def test_not_enabled_missing_aprs_fi_key(self):
|
||||||
def test_not_enabled_missing_aprs_fi_key(self, mock_check):
|
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the LocationPlugin will be disabled.
|
# the LocationPlugin will be disabled.
|
||||||
mock_check.side_effect = Exception
|
CONF.aprs_fi.apiKey = None
|
||||||
wx = weather_plugin.USWeatherPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USWeatherPlugin()
|
||||||
expected = "USWeatherPlugin isn't enabled"
|
expected = "USWeatherPlugin isn't enabled"
|
||||||
packet = fake.fake_packet(message="weather")
|
packet = fake.fake_packet(message="weather")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -23,7 +29,9 @@ class TestUSWeatherPluginPlugin(test_plugin.TestPlugin):
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the Plugin will be disabled.
|
# the Plugin will be disabled.
|
||||||
mock_check.side_effect = Exception
|
mock_check.side_effect = Exception
|
||||||
wx = weather_plugin.USWeatherPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USWeatherPlugin()
|
||||||
expected = "Failed to fetch aprs.fi location"
|
expected = "Failed to fetch aprs.fi location"
|
||||||
packet = fake.fake_packet(message="weather")
|
packet = fake.fake_packet(message="weather")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -34,7 +42,10 @@ class TestUSWeatherPluginPlugin(test_plugin.TestPlugin):
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the Plugin will be disabled.
|
# the Plugin will be disabled.
|
||||||
mock_check.return_value = {"entries": []}
|
mock_check.return_value = {"entries": []}
|
||||||
wx = weather_plugin.USWeatherPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USWeatherPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "Failed to fetch aprs.fi location"
|
expected = "Failed to fetch aprs.fi location"
|
||||||
packet = fake.fake_packet(message="weather")
|
packet = fake.fake_packet(message="weather")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -55,7 +66,10 @@ class TestUSWeatherPluginPlugin(test_plugin.TestPlugin):
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
mock_weather.side_effect = Exception
|
mock_weather.side_effect = Exception
|
||||||
wx = weather_plugin.USWeatherPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USWeatherPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "Unable to get weather"
|
expected = "Unable to get weather"
|
||||||
packet = fake.fake_packet(message="weather")
|
packet = fake.fake_packet(message="weather")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -83,7 +97,10 @@ class TestUSWeatherPluginPlugin(test_plugin.TestPlugin):
|
||||||
},
|
},
|
||||||
"time": {"startPeriodName": ["ignored", "sometime"]},
|
"time": {"startPeriodName": ["ignored", "sometime"]},
|
||||||
}
|
}
|
||||||
wx = weather_plugin.USWeatherPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USWeatherPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "400F(10F/11F) test. sometime, another."
|
expected = "400F(10F/11F) test. sometime, another."
|
||||||
packet = fake.fake_packet(message="weather")
|
packet = fake.fake_packet(message="weather")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -92,12 +109,11 @@ class TestUSWeatherPluginPlugin(test_plugin.TestPlugin):
|
||||||
|
|
||||||
class TestUSMetarPlugin(test_plugin.TestPlugin):
|
class TestUSMetarPlugin(test_plugin.TestPlugin):
|
||||||
|
|
||||||
@mock.patch("aprsd.config.Config.check_option")
|
def test_not_enabled_missing_aprs_fi_key(self):
|
||||||
def test_not_enabled_missing_aprs_fi_key(self, mock_check):
|
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the LocationPlugin will be disabled.
|
# the LocationPlugin will be disabled.
|
||||||
mock_check.side_effect = Exception
|
CONF.aprs_fi.apiKey = None
|
||||||
wx = weather_plugin.USMetarPlugin(self.config)
|
wx = weather_plugin.USMetarPlugin()
|
||||||
expected = "USMetarPlugin isn't enabled"
|
expected = "USMetarPlugin isn't enabled"
|
||||||
packet = fake.fake_packet(message="metar")
|
packet = fake.fake_packet(message="metar")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -108,7 +124,10 @@ class TestUSMetarPlugin(test_plugin.TestPlugin):
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the Plugin will be disabled.
|
# the Plugin will be disabled.
|
||||||
mock_check.side_effect = Exception
|
mock_check.side_effect = Exception
|
||||||
wx = weather_plugin.USMetarPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USMetarPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "Failed to fetch aprs.fi location"
|
expected = "Failed to fetch aprs.fi location"
|
||||||
packet = fake.fake_packet(message="metar")
|
packet = fake.fake_packet(message="metar")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -119,7 +138,10 @@ class TestUSMetarPlugin(test_plugin.TestPlugin):
|
||||||
# When the aprs.fi api key isn't set, then
|
# When the aprs.fi api key isn't set, then
|
||||||
# the Plugin will be disabled.
|
# the Plugin will be disabled.
|
||||||
mock_check.return_value = {"entries": []}
|
mock_check.return_value = {"entries": []}
|
||||||
wx = weather_plugin.USMetarPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USMetarPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "Failed to fetch aprs.fi location"
|
expected = "Failed to fetch aprs.fi location"
|
||||||
packet = fake.fake_packet(message="metar")
|
packet = fake.fake_packet(message="metar")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -128,7 +150,10 @@ class TestUSMetarPlugin(test_plugin.TestPlugin):
|
||||||
@mock.patch("aprsd.plugin_utils.get_weather_gov_metar")
|
@mock.patch("aprsd.plugin_utils.get_weather_gov_metar")
|
||||||
def test_gov_metar_fetch_fails(self, mock_metar):
|
def test_gov_metar_fetch_fails(self, mock_metar):
|
||||||
mock_metar.side_effect = Exception
|
mock_metar.side_effect = Exception
|
||||||
wx = weather_plugin.USMetarPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USMetarPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "Unable to find station METAR"
|
expected = "Unable to find station METAR"
|
||||||
packet = fake.fake_packet(message="metar KPAO")
|
packet = fake.fake_packet(message="metar KPAO")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -141,7 +166,10 @@ class TestUSMetarPlugin(test_plugin.TestPlugin):
|
||||||
text = '{"properties": {"rawMessage": "BOGUSMETAR"}}'
|
text = '{"properties": {"rawMessage": "BOGUSMETAR"}}'
|
||||||
mock_metar.return_value = Response()
|
mock_metar.return_value = Response()
|
||||||
|
|
||||||
wx = weather_plugin.USMetarPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USMetarPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "BOGUSMETAR"
|
expected = "BOGUSMETAR"
|
||||||
packet = fake.fake_packet(message="metar KPAO")
|
packet = fake.fake_packet(message="metar KPAO")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
@ -169,7 +197,10 @@ class TestUSMetarPlugin(test_plugin.TestPlugin):
|
||||||
}
|
}
|
||||||
mock_metar.return_value = Response()
|
mock_metar.return_value = Response()
|
||||||
|
|
||||||
wx = weather_plugin.USMetarPlugin(self.config)
|
CONF.aprs_fi.apiKey = "abc123"
|
||||||
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
wx = weather_plugin.USMetarPlugin()
|
||||||
|
wx.enabled = True
|
||||||
expected = "BOGUSMETAR"
|
expected = "BOGUSMETAR"
|
||||||
packet = fake.fake_packet(message="metar")
|
packet = fake.fake_packet(message="metar")
|
||||||
actual = wx.filter(packet)
|
actual = wx.filter(packet)
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd.plugins import email
|
from aprsd.plugins import email
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestEmail(unittest.TestCase):
|
class TestEmail(unittest.TestCase):
|
||||||
def test_get_email_from_shortcut(self):
|
def test_get_email_from_shortcut(self):
|
||||||
config = {"aprsd": {"email": {"shortcuts": {}}}}
|
CONF.email_plugin.shortcuts = None
|
||||||
email_address = "something@something.com"
|
email_address = "something@something.com"
|
||||||
addr = f"-{email_address}"
|
addr = f"-{email_address}"
|
||||||
actual = email.get_email_from_shortcut(config, addr)
|
actual = email.get_email_from_shortcut(addr)
|
||||||
self.assertEqual(addr, actual)
|
self.assertEqual(addr, actual)
|
||||||
|
|
||||||
config = {"aprsd": {"email": {"nothing": "nothing"}}}
|
CONF.email_plugin.shortcuts = None
|
||||||
actual = email.get_email_from_shortcut(config, addr)
|
actual = email.get_email_from_shortcut(addr)
|
||||||
self.assertEqual(addr, actual)
|
self.assertEqual(addr, actual)
|
||||||
|
|
||||||
config = {"aprsd": {"email": {"shortcuts": {"not_used": "empty"}}}}
|
CONF.email_plugin.shortcuts = None
|
||||||
actual = email.get_email_from_shortcut(config, addr)
|
actual = email.get_email_from_shortcut(addr)
|
||||||
self.assertEqual(addr, actual)
|
self.assertEqual(addr, actual)
|
||||||
|
|
||||||
config = {"aprsd": {"email": {"shortcuts": {"-wb": email_address}}}}
|
CONF.email_plugin.email_shortcuts = ["wb=something@something.com"]
|
||||||
short = "-wb"
|
email.shortcuts_dict = None
|
||||||
actual = email.get_email_from_shortcut(config, short)
|
short = "wb"
|
||||||
|
actual = email.get_email_from_shortcut(short)
|
||||||
self.assertEqual(email_address, actual)
|
self.assertEqual(email_address, actual)
|
||||||
|
|
|
@ -17,6 +17,6 @@ class TestMain(unittest.TestCase):
|
||||||
"""Test to make sure we fail."""
|
"""Test to make sure we fail."""
|
||||||
imap_mock.return_value = None
|
imap_mock.return_value = None
|
||||||
smtp_mock.return_value = {"smaiof": "fire"}
|
smtp_mock.return_value = {"smaiof": "fire"}
|
||||||
config = mock.MagicMock()
|
mock.MagicMock()
|
||||||
|
|
||||||
email.validate_email_config(config, True)
|
email.validate_email_config(True)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from aprsd import config as aprsd_config
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd import conf # noqa: F401
|
||||||
from aprsd import packets
|
from aprsd import packets
|
||||||
from aprsd import plugin as aprsd_plugin
|
from aprsd import plugin as aprsd_plugin
|
||||||
from aprsd import plugins, stats
|
from aprsd import plugins, stats
|
||||||
|
@ -10,6 +12,9 @@ from aprsd.packets import core
|
||||||
from . import fake
|
from . import fake
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestPluginManager(unittest.TestCase):
|
class TestPluginManager(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
|
@ -21,34 +26,26 @@ class TestPluginManager(unittest.TestCase):
|
||||||
aprsd_plugin.PluginManager._instance = None
|
aprsd_plugin.PluginManager._instance = None
|
||||||
|
|
||||||
def config_and_init(self):
|
def config_and_init(self):
|
||||||
self.config = aprsd_config.Config(aprsd_config.DEFAULT_CONFIG_DICT)
|
CONF.callsign = self.fromcall
|
||||||
self.config["ham"]["callsign"] = self.fromcall
|
CONF.aprs_network.login = fake.FAKE_TO_CALLSIGN
|
||||||
self.config["aprs"]["login"] = fake.FAKE_TO_CALLSIGN
|
CONF.aprs_fi.apiKey = "something"
|
||||||
self.config["services"]["aprs.fi"]["apiKey"] = "something"
|
CONF.enabled_plugins = "aprsd.plugins.ping.PingPlugin"
|
||||||
self.config["aprsd"]["enabled_plugins"] = [
|
CONF.enable_save = False
|
||||||
"aprsd.plugins.ping.PingPlugin",
|
|
||||||
]
|
|
||||||
print(self.config)
|
|
||||||
|
|
||||||
def test_init_no_config(self):
|
|
||||||
pm = aprsd_plugin.PluginManager()
|
|
||||||
self.assertEqual(None, pm.config)
|
|
||||||
|
|
||||||
def test_init_with_config(self):
|
|
||||||
pm = aprsd_plugin.PluginManager(self.config)
|
|
||||||
self.assertEqual(self.config, pm.config)
|
|
||||||
|
|
||||||
def test_get_plugins_no_plugins(self):
|
def test_get_plugins_no_plugins(self):
|
||||||
pm = aprsd_plugin.PluginManager(self.config)
|
CONF.enabled_plugins = []
|
||||||
|
pm = aprsd_plugin.PluginManager()
|
||||||
plugin_list = pm.get_plugins()
|
plugin_list = pm.get_plugins()
|
||||||
self.assertEqual([], plugin_list)
|
self.assertEqual([], plugin_list)
|
||||||
|
|
||||||
def test_get_plugins_with_plugins(self):
|
def test_get_plugins_with_plugins(self):
|
||||||
pm = aprsd_plugin.PluginManager(self.config)
|
CONF.enabled_plugins = ["aprsd.plugins.ping.PingPlugin"]
|
||||||
|
pm = aprsd_plugin.PluginManager()
|
||||||
plugin_list = pm.get_plugins()
|
plugin_list = pm.get_plugins()
|
||||||
self.assertEqual([], plugin_list)
|
self.assertEqual([], plugin_list)
|
||||||
pm.setup_plugins()
|
pm.setup_plugins()
|
||||||
plugin_list = pm.get_plugins()
|
plugin_list = pm.get_plugins()
|
||||||
|
print(plugin_list)
|
||||||
self.assertIsInstance(plugin_list, list)
|
self.assertIsInstance(plugin_list, list)
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
plugin_list[0],
|
plugin_list[0],
|
||||||
|
@ -59,7 +56,7 @@ class TestPluginManager(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_watchlist_plugins(self):
|
def test_get_watchlist_plugins(self):
|
||||||
pm = aprsd_plugin.PluginManager(self.config)
|
pm = aprsd_plugin.PluginManager()
|
||||||
plugin_list = pm.get_plugins()
|
plugin_list = pm.get_plugins()
|
||||||
self.assertEqual([], plugin_list)
|
self.assertEqual([], plugin_list)
|
||||||
pm.setup_plugins()
|
pm.setup_plugins()
|
||||||
|
@ -68,7 +65,8 @@ class TestPluginManager(unittest.TestCase):
|
||||||
self.assertEqual(0, len(plugin_list))
|
self.assertEqual(0, len(plugin_list))
|
||||||
|
|
||||||
def test_get_message_plugins(self):
|
def test_get_message_plugins(self):
|
||||||
pm = aprsd_plugin.PluginManager(self.config)
|
CONF.enabled_plugins = ["aprsd.plugins.ping.PingPlugin"]
|
||||||
|
pm = aprsd_plugin.PluginManager()
|
||||||
plugin_list = pm.get_plugins()
|
plugin_list = pm.get_plugins()
|
||||||
self.assertEqual([], plugin_list)
|
self.assertEqual([], plugin_list)
|
||||||
pm.setup_plugins()
|
pm.setup_plugins()
|
||||||
|
@ -98,27 +96,19 @@ class TestPlugin(unittest.TestCase):
|
||||||
packets.PacketTrack._instance = None
|
packets.PacketTrack._instance = None
|
||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
def config_and_init(self, config=None):
|
def config_and_init(self):
|
||||||
if not config:
|
CONF.callsign = self.fromcall
|
||||||
self.config = aprsd_config.Config(aprsd_config.DEFAULT_CONFIG_DICT)
|
CONF.aprs_network.login = fake.FAKE_TO_CALLSIGN
|
||||||
self.config["ham"]["callsign"] = self.fromcall
|
CONF.aprs_fi.apiKey = "something"
|
||||||
self.config["aprs"]["login"] = fake.FAKE_TO_CALLSIGN
|
CONF.enabled_plugins = "aprsd.plugins.ping.PingPlugin"
|
||||||
self.config["services"]["aprs.fi"]["apiKey"] = "something"
|
CONF.enable_save = False
|
||||||
else:
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
# Inintialize the stats object with the config
|
|
||||||
stats.APRSDStats(self.config)
|
|
||||||
packets.WatchList(config=self.config)
|
|
||||||
packets.SeenList(config=self.config)
|
|
||||||
packets.PacketTrack(config=self.config)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPluginBase(TestPlugin):
|
class TestPluginBase(TestPlugin):
|
||||||
|
|
||||||
@mock.patch.object(fake.FakeBaseNoThreadsPlugin, "process")
|
@mock.patch.object(fake.FakeBaseNoThreadsPlugin, "process")
|
||||||
def test_base_plugin_no_threads(self, mock_process):
|
def test_base_plugin_no_threads(self, mock_process):
|
||||||
p = fake.FakeBaseNoThreadsPlugin(self.config)
|
p = fake.FakeBaseNoThreadsPlugin()
|
||||||
|
|
||||||
expected = []
|
expected = []
|
||||||
actual = p.create_threads()
|
actual = p.create_threads()
|
||||||
|
@ -139,19 +129,20 @@ class TestPluginBase(TestPlugin):
|
||||||
|
|
||||||
@mock.patch.object(fake.FakeBaseThreadsPlugin, "create_threads")
|
@mock.patch.object(fake.FakeBaseThreadsPlugin, "create_threads")
|
||||||
def test_base_plugin_threads_created(self, mock_create):
|
def test_base_plugin_threads_created(self, mock_create):
|
||||||
p = fake.FakeBaseThreadsPlugin(self.config)
|
p = fake.FakeBaseThreadsPlugin()
|
||||||
mock_create.assert_called_once()
|
mock_create.assert_called_once()
|
||||||
p.stop_threads()
|
p.stop_threads()
|
||||||
|
|
||||||
def test_base_plugin_threads(self):
|
def test_base_plugin_threads(self):
|
||||||
p = fake.FakeBaseThreadsPlugin(self.config)
|
p = fake.FakeBaseThreadsPlugin()
|
||||||
actual = p.create_threads()
|
actual = p.create_threads()
|
||||||
self.assertTrue(isinstance(actual, fake.FakeThread))
|
self.assertTrue(isinstance(actual, fake.FakeThread))
|
||||||
p.stop_threads()
|
p.stop_threads()
|
||||||
|
|
||||||
@mock.patch.object(fake.FakeRegexCommandPlugin, "process")
|
@mock.patch.object(fake.FakeRegexCommandPlugin, "process")
|
||||||
def test_regex_base_not_called(self, mock_process):
|
def test_regex_base_not_called(self, mock_process):
|
||||||
p = fake.FakeRegexCommandPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
p = fake.FakeRegexCommandPlugin()
|
||||||
packet = fake.fake_packet(message="a")
|
packet = fake.fake_packet(message="a")
|
||||||
expected = None
|
expected = None
|
||||||
actual = p.filter(packet)
|
actual = p.filter(packet)
|
||||||
|
@ -165,32 +156,32 @@ class TestPluginBase(TestPlugin):
|
||||||
mock_process.assert_not_called()
|
mock_process.assert_not_called()
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="F",
|
|
||||||
message_format=core.PACKET_TYPE_MICE,
|
message_format=core.PACKET_TYPE_MICE,
|
||||||
)
|
)
|
||||||
expected = None
|
expected = packets.NULL_MESSAGE
|
||||||
actual = p.filter(packet)
|
actual = p.filter(packet)
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
mock_process.assert_not_called()
|
mock_process.assert_not_called()
|
||||||
|
|
||||||
packet = fake.fake_packet(
|
packet = fake.fake_packet(
|
||||||
message="f",
|
|
||||||
message_format=core.PACKET_TYPE_ACK,
|
message_format=core.PACKET_TYPE_ACK,
|
||||||
)
|
)
|
||||||
expected = None
|
expected = packets.NULL_MESSAGE
|
||||||
actual = p.filter(packet)
|
actual = p.filter(packet)
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
mock_process.assert_not_called()
|
mock_process.assert_not_called()
|
||||||
|
|
||||||
@mock.patch.object(fake.FakeRegexCommandPlugin, "process")
|
@mock.patch.object(fake.FakeRegexCommandPlugin, "process")
|
||||||
def test_regex_base_assert_called(self, mock_process):
|
def test_regex_base_assert_called(self, mock_process):
|
||||||
p = fake.FakeRegexCommandPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
p = fake.FakeRegexCommandPlugin()
|
||||||
packet = fake.fake_packet(message="f")
|
packet = fake.fake_packet(message="f")
|
||||||
p.filter(packet)
|
p.filter(packet)
|
||||||
mock_process.assert_called_once()
|
mock_process.assert_called_once()
|
||||||
|
|
||||||
def test_regex_base_process_called(self):
|
def test_regex_base_process_called(self):
|
||||||
p = fake.FakeRegexCommandPlugin(self.config)
|
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||||
|
p = fake.FakeRegexCommandPlugin()
|
||||||
|
|
||||||
packet = fake.fake_packet(message="f")
|
packet = fake.fake_packet(message="f")
|
||||||
expected = fake.FAKE_MESSAGE_TEXT
|
expected = fake.FAKE_MESSAGE_TEXT
|
||||||
|
|
Loading…
Reference in New Issue