mirror of
https://github.com/hemna/aprsd-weewx-plugin.git
synced 2024-12-11 09:30:16 -05:00
Update to aprsd 3.0.0 and include config options!
This patch adds the entry points for aprsd to generate config options when this plugin is installed. The patch also switches over to using the new oslo_config style config object.
This commit is contained in:
parent
bb7a5ed08e
commit
514612fa05
1
AUTHORS
1
AUTHORS
@ -1 +1,2 @@
|
||||
Hemna <waboring@hemna.com>
|
||||
Walter A. Boring IV <waboring@hemna.com>
|
||||
|
12
ChangeLog
12
ChangeLog
@ -1,16 +1,14 @@
|
||||
CHANGES
|
||||
=======
|
||||
|
||||
v0.1.4
|
||||
------
|
||||
|
||||
* don't dump the whole packet
|
||||
* use Tx to send
|
||||
* Working with pre 2.7.0
|
||||
* Removed trace
|
||||
* lint
|
||||
* Added pbr version
|
||||
|
||||
v0.1.3
|
||||
------
|
||||
|
||||
* Fixed missing entry in requirements.txt
|
||||
* Create FUNDING.yml
|
||||
|
||||
v0.1.2
|
||||
------
|
||||
|
10
aprsd_weewx_plugin/conf/__init__.py
Normal file
10
aprsd_weewx_plugin/conf/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
import logging
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd_weewx_plugin.conf import weewx
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
weewx.register_opts(CONF)
|
80
aprsd_weewx_plugin/conf/opts.py
Normal file
80
aprsd_weewx_plugin/conf/opts.py
Normal file
@ -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_weewx_plugin.conf." + modname)
|
||||
if not hasattr(mod, LIST_OPTS_FUNC_NAME):
|
||||
msg = "The module 'aprsd_weewx_plugin.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)
|
62
aprsd_weewx_plugin/conf/weewx.py
Normal file
62
aprsd_weewx_plugin/conf/weewx.py
Normal file
@ -0,0 +1,62 @@
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
weewx_group = cfg.OptGroup(
|
||||
name="aprsd_weewx_plugin",
|
||||
title="APRSD Weewx Plugin settings",
|
||||
)
|
||||
|
||||
weewx_opts = [
|
||||
cfg.FloatOpt(
|
||||
"latitude",
|
||||
default=None,
|
||||
help="Latitude of the station you want to report as",
|
||||
),
|
||||
cfg.FloatOpt(
|
||||
"longitude",
|
||||
default=None,
|
||||
help="Longitude of the station you want to report as",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"report_interval",
|
||||
default=60,
|
||||
help="How long (in seconds) in between weather reports",
|
||||
),
|
||||
]
|
||||
|
||||
weewx_mqtt_opts = [
|
||||
cfg.StrOpt(
|
||||
"mqtt_user",
|
||||
help="MQTT username",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"mqtt_password",
|
||||
secret=True,
|
||||
help="MQTT password",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"mqtt_host",
|
||||
help="MQTT Hostname to connect to",
|
||||
),
|
||||
cfg.PortOpt(
|
||||
"mqtt_port",
|
||||
help="MQTT Port",
|
||||
),
|
||||
]
|
||||
|
||||
ALL_OPTS = (
|
||||
weewx_opts +
|
||||
weewx_mqtt_opts
|
||||
)
|
||||
|
||||
|
||||
def register_opts(cfg):
|
||||
cfg.register_group(weewx_group)
|
||||
cfg.register_opts(ALL_OPTS, group=weewx_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
register_opts(cfg.CONF)
|
||||
return {
|
||||
weewx_group.name: ALL_OPTS,
|
||||
}
|
@ -9,8 +9,12 @@ import aprsd.messaging
|
||||
import paho.mqtt.client as mqtt
|
||||
from aprsd import plugin, threads
|
||||
from aprsd.threads import tx
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd_weewx_plugin import conf # noqa
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger("APRSD")
|
||||
|
||||
|
||||
@ -41,21 +45,22 @@ class WeewxMQTTPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||
|
||||
def setup(self):
|
||||
"""Ensure that the plugin has been configured."""
|
||||
LOG.info("Looking for weewx.mqtt.host config entry")
|
||||
if self.config.exists("services.weewx.mqtt.host"):
|
||||
self.enabled = True
|
||||
else:
|
||||
LOG.error("Failed to find config weewx:mqtt:host")
|
||||
LOG.info("Disabling the weewx mqtt subsription thread.")
|
||||
self.enabled = True
|
||||
if not CONF.aprsd_weewx_plugin.mqtt_host:
|
||||
LOG.error("aprsd_weewx_plugin.mqtt_host is not set in config!")
|
||||
self.enabled = False
|
||||
|
||||
if not CONF.aprsd_weewx_plugin.mqtt_user:
|
||||
LOG.warning("aprsd_weewx_plugin.mqtt_user is not set")
|
||||
if not CONF.aprsd_weewx_plugin.mqtt_password:
|
||||
LOG.warning("aprsd_weewx_plugin.mqtt_password is not set")
|
||||
|
||||
def create_threads(self):
|
||||
if self.enabled:
|
||||
LOG.info("Creating WeewxMQTTThread")
|
||||
self.queue = ClearableQueue(maxsize=1)
|
||||
self.wx_queue = ClearableQueue(maxsize=1)
|
||||
mqtt_thread = WeewxMQTTThread(
|
||||
config=self.config,
|
||||
wx_queue=self.wx_queue,
|
||||
msg_queue=self.queue,
|
||||
)
|
||||
@ -65,19 +70,21 @@ class WeewxMQTTPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||
# Then we can periodically report weather data
|
||||
# to APRS
|
||||
if (
|
||||
self.config.exists("services.weewx.location.latitude") and
|
||||
self.config.exists("services.weewx.location.longitude")
|
||||
CONF.aprsd_weewx_plugin.latitude and
|
||||
CONF.aprsd_weewx_plugin.longitude
|
||||
):
|
||||
LOG.info("Creating WeewxWXAPRSThread")
|
||||
wx_thread = WeewxWXAPRSThread(
|
||||
config=self.config,
|
||||
wx_queue=self.wx_queue,
|
||||
)
|
||||
threads.append(wx_thread)
|
||||
else:
|
||||
LOG.info(
|
||||
"NOT starting Weewx WX APRS Thread due to missing "
|
||||
"GPS location settings.",
|
||||
"GPS location settings. Please set "
|
||||
"aprsd_weewx_plugin.latitude and "
|
||||
"aprsd_weewx_plugin.longitude to start reporting as an "
|
||||
"aprs weather station.",
|
||||
)
|
||||
|
||||
return threads
|
||||
@ -90,89 +97,87 @@ class WeewxMQTTPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||
packet.get("message_text", None)
|
||||
# ack = packet.get("msgNo", "0")
|
||||
|
||||
if self.enabled:
|
||||
# see if there are any weather messages in the queue.
|
||||
msg = None
|
||||
LOG.info("Looking for a message")
|
||||
if not self.queue.empty():
|
||||
msg = self.queue.get(timeout=1)
|
||||
else:
|
||||
try:
|
||||
msg = self.queue.get(timeout=30)
|
||||
except Exception:
|
||||
return "No Weewx Data"
|
||||
|
||||
if not msg:
|
||||
return "No Weewx data"
|
||||
else:
|
||||
LOG.info(f"Got a message {msg}")
|
||||
# Wants format of 71.5F/54.0F Wind 1@49G7 54%
|
||||
if "outTemp_F" in msg:
|
||||
temperature = "{:0.2f}F".format(float(msg["outTemp_F"]))
|
||||
dewpoint = "{:0.2f}F".format(float(msg["dewpoint_F"]))
|
||||
else:
|
||||
temperature = "{:0.2f}C".format(float(msg["outTemp_C"]))
|
||||
dewpoint = "{:0.2f}C".format(float(msg["dewpoint_C"]))
|
||||
|
||||
wind_direction = "{:0.0f}".format(float(msg.get("windDir", 0)))
|
||||
LOG.info(f"wind direction {wind_direction}")
|
||||
if "windSpeed_mps" in msg:
|
||||
wind_speed = "{:0.0f}".format(float(msg["windSpeed_mps"]))
|
||||
wind_gust = "{:0.0f}".format(float(msg["windGust_mps"]))
|
||||
else:
|
||||
wind_speed = "{:0.0f}".format(float(msg["windSpeed_mph"]))
|
||||
wind_gust = "{:0.0f}".format(float(msg["windGust_mph"]))
|
||||
|
||||
wind = "{}@{}G{}".format(
|
||||
wind_speed,
|
||||
wind_direction,
|
||||
wind_gust,
|
||||
)
|
||||
|
||||
humidity = "{:0.0f}%".format(float(msg["outHumidity"]))
|
||||
|
||||
ts = int("{:0.0f}".format(float(msg["dateTime"])))
|
||||
ts = datetime.datetime.fromtimestamp(ts)
|
||||
|
||||
# do rain in format of last hour/day/month/year
|
||||
|
||||
rain = "RA {:.2f} {:.2f}/hr".format(
|
||||
float(msg.get("dayRain_in", 0.00)),
|
||||
float(msg.get("rainRate_inch_per_hour", 0.00)),
|
||||
)
|
||||
|
||||
wx = "WX: {}/{} Wind {} {} {} {:.2f}inHg".format(
|
||||
temperature,
|
||||
dewpoint,
|
||||
wind,
|
||||
humidity,
|
||||
rain,
|
||||
float(msg.get("pressure_inHg", 0.00)),
|
||||
)
|
||||
LOG.debug(
|
||||
"Got weather {} -- len {}".format(
|
||||
wx,
|
||||
len(wx),
|
||||
),
|
||||
)
|
||||
return wx
|
||||
|
||||
# see if there are any weather messages in the queue.
|
||||
msg = None
|
||||
LOG.info("Looking for a message")
|
||||
if not self.queue.empty():
|
||||
msg = self.queue.get(timeout=1)
|
||||
else:
|
||||
return "WeewxMQTT Not enabled"
|
||||
try:
|
||||
msg = self.queue.get(timeout=30)
|
||||
except Exception:
|
||||
return "No Weewx Data"
|
||||
|
||||
if not msg:
|
||||
return "No Weewx data"
|
||||
else:
|
||||
LOG.info(f"Got a message {msg}")
|
||||
# Wants format of 71.5F/54.0F Wind 1@49G7 54%
|
||||
if "outTemp_F" in msg:
|
||||
temperature = "{:0.2f}F".format(float(msg["outTemp_F"]))
|
||||
dewpoint = "{:0.2f}F".format(float(msg["dewpoint_F"]))
|
||||
else:
|
||||
temperature = "{:0.2f}C".format(float(msg["outTemp_C"]))
|
||||
dewpoint = "{:0.2f}C".format(float(msg["dewpoint_C"]))
|
||||
|
||||
wind_direction = "{:0.0f}".format(float(msg.get("windDir", 0)))
|
||||
LOG.info(f"wind direction {wind_direction}")
|
||||
if "windSpeed_mps" in msg:
|
||||
wind_speed = "{:0.0f}".format(float(msg["windSpeed_mps"]))
|
||||
wind_gust = "{:0.0f}".format(float(msg["windGust_mps"]))
|
||||
else:
|
||||
wind_speed = "{:0.0f}".format(float(msg["windSpeed_mph"]))
|
||||
wind_gust = "{:0.0f}".format(float(msg["windGust_mph"]))
|
||||
|
||||
wind = "{}@{}G{}".format(
|
||||
wind_speed,
|
||||
wind_direction,
|
||||
wind_gust,
|
||||
)
|
||||
|
||||
humidity = "{:0.0f}%".format(float(msg["outHumidity"]))
|
||||
|
||||
ts = int("{:0.0f}".format(float(msg["dateTime"])))
|
||||
ts = datetime.datetime.fromtimestamp(ts)
|
||||
|
||||
# do rain in format of last hour/day/month/year
|
||||
|
||||
rain = "RA {:.2f} {:.2f}/hr".format(
|
||||
float(msg.get("dayRain_in", 0.00)),
|
||||
float(msg.get("rainRate_inch_per_hour", 0.00)),
|
||||
)
|
||||
|
||||
wx = "WX: {}/{} Wind {} {} {} {:.2f}inHg".format(
|
||||
temperature,
|
||||
dewpoint,
|
||||
wind,
|
||||
humidity,
|
||||
rain,
|
||||
float(msg.get("pressure_inHg", 0.00)),
|
||||
)
|
||||
LOG.debug(
|
||||
"Got weather {} -- len {}".format(
|
||||
wx,
|
||||
len(wx),
|
||||
),
|
||||
)
|
||||
return wx
|
||||
|
||||
|
||||
class WeewxMQTTThread(threads.APRSDThread):
|
||||
def __init__(self, config, wx_queue, msg_queue):
|
||||
_mqtt_host = None
|
||||
_mqtt_port = None
|
||||
client = None
|
||||
def __init__(self, wx_queue, msg_queue):
|
||||
super().__init__("WeewxMQTTThread")
|
||||
self.config = config
|
||||
self.msg_queue = msg_queue
|
||||
self.wx_queue = wx_queue
|
||||
self.setup()
|
||||
|
||||
def setup(self):
|
||||
LOG.info("Creating mqtt client")
|
||||
self._mqtt_host = self.config["services"]["weewx"]["mqtt"]["host"]
|
||||
self._mqtt_port = self.config["services"]["weewx"]["mqtt"]["port"]
|
||||
self._mqtt_host = CONF.aprsd_weewx_plugin.mqtt_host
|
||||
self._mqtt_port = CONF.aprsd_weewx_plugin.mqtt_port
|
||||
LOG.info(
|
||||
"Connecting to mqtt {}:{}".format(
|
||||
self._mqtt_host,
|
||||
@ -183,9 +188,9 @@ class WeewxMQTTThread(threads.APRSDThread):
|
||||
self.client.on_connect = self.on_connect
|
||||
self.client.on_message = self.on_message
|
||||
self.client.connect(self._mqtt_host, self._mqtt_port, 60)
|
||||
if self.config.exists("services.weewx.mqtt.user"):
|
||||
username = self.config.get("services.weewx.mqtt.user")
|
||||
password = self.config.get("services.weewx.mqtt.password")
|
||||
if CONF.aprsd_weewx_plugin.mqtt_user:
|
||||
username = CONF.aprsd_weewx_plugin.mqtt_user
|
||||
password = CONF.aprsd_weewx_plugin.mqtt_password
|
||||
LOG.info(f"Using MQTT username/password {username}/XXXXXX")
|
||||
self.client.username_pw_set(
|
||||
username=username,
|
||||
@ -224,13 +229,13 @@ class WeewxMQTTThread(threads.APRSDThread):
|
||||
|
||||
|
||||
class WeewxWXAPRSThread(threads.APRSDThread):
|
||||
def __init__(self, config, wx_queue):
|
||||
def __init__(self, wx_queue):
|
||||
super().__init__(self.__class__.__name__)
|
||||
self.config = config
|
||||
self.wx_queue = wx_queue
|
||||
self.latitude = self.config.get("services.weewx.location.latitude")
|
||||
self.longitude = self.config.get("services.weewx.location.longitude")
|
||||
self.callsign = self.config.get("aprsd.callsign")
|
||||
self.latitude = CONF.aprsd_weewx_plugin.latitude
|
||||
self.longitude = CONF.aprsd_weewx_plugin.longitude
|
||||
self.callsign = CONF.callsign
|
||||
self.report_interval = CONF.aprsd_weewx_plugin.report_interval
|
||||
self.last_send = datetime.datetime.now()
|
||||
|
||||
if self.latitude and self.longitude:
|
||||
@ -332,7 +337,7 @@ class WeewxWXAPRSThread(threads.APRSDThread):
|
||||
def loop(self):
|
||||
now = datetime.datetime.now()
|
||||
delta = now - self.last_send
|
||||
max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
|
||||
max_timeout = {"seconds": self.report_interval}
|
||||
max_delta = datetime.timedelta(**max_timeout)
|
||||
if delta >= max_delta:
|
||||
if not self.wx_queue.empty():
|
||||
|
@ -1,3 +1,4 @@
|
||||
pbr
|
||||
aprsd>=2.4.0
|
||||
aprsd>=3.0.0
|
||||
paho-mqtt
|
||||
oslo-config
|
||||
|
Loading…
Reference in New Issue
Block a user