add support for dumb sensors, support more sensors, fix JSON issue with single and double quotes

This commit is contained in:
Kim Bauters 2018-11-29 10:10:51 +00:00
parent 8658995acc
commit 20dd428942
2 changed files with 41 additions and 8 deletions

View File

@ -3,6 +3,7 @@ import json
import logging import logging
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
import time import time
from multiprocessing import Process
from typing import Any, Callable, Dict, Iterator from typing import Any, Callable, Dict, Iterator
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -32,20 +33,25 @@ class Collector:
self._online = {} self._online = {}
self._collected = {} self._collected = {}
self._client = mqtt.Client() self._client = mqtt.Client()
self._previous_process = None
self._current_process = None
self.callback = self._callback
log.info("New %s object created to connect to %s:%s and TLS support set to %s.", log.info("New %s object created to connect to %s:%s and TLS support set to %s.",
Collector.__name__, self._host, self._port, self._tls) Collector.__name__, self._host, self._port, self._tls)
def add_sensor(self, sensor: str, key: str, *, def add_sensor(self, sensor: str, key: str, *,
stale: bool = False, max_hours: int = 0, same_day: bool = False) -> None: stale: bool = False, max_hours: int = 0, same_day: bool = False,
dumb: bool = False) -> None:
""" Add the topic of a sensor, and the key to access its value. """ Add the topic of a sensor, and the key to access its value.
:param sensor: the topic of the sensor to listen to. :param sensor: the topic of the sensor to listen to.
:param key: the key to access the value of the sensor. :param key: the key to access the value of the sensor.
:param stale: flag indicating whether stale results are acceptable. :param stale: flag indicating whether stale results are acceptable.
:param max_hours: (when stale is allowed) the maximum hours staleness to tolerate. :param max_hours: (when stale is allowed) the maximum hours staleness to tolerate.
:param same_day: (when stale is allowed) whether stale info is only allowed on the same day. """ :param same_day: (when stale is allowed) whether stale info is only allowed on the same day.
:param dumb: whether this is a dumb source which does not have any availability information: enforces unlimited stale"""
log.info("Adding sensor %s accessible through the key %s", sensor, key) log.info("Adding sensor %s accessible through the key %s", sensor, key)
self._sensors.append((sensor, key)) self._sensors.append((sensor, key))
self._stale_allowed[key] = (stale, max_hours, same_day) self._stale_allowed[key] = (stale, max_hours, same_day, dumb)
def connect(self) -> None: def connect(self) -> None:
""" Start a non-blocking connection to the MQTT broker. This will also """ Start a non-blocking connection to the MQTT broker. This will also
@ -60,6 +66,7 @@ class Collector:
log.info("Creating the callbacks for each specified sensor.") log.info("Creating the callbacks for each specified sensor.")
for sensor, key in self._sensors: for sensor, key in self._sensors:
self._client.message_callback_add(sensor, self._create_handler(key)) self._client.message_callback_add(sensor, self._create_handler(key))
self._online[key] = True
log.info("Asynchronously starting the connection to the MQTT broker.") log.info("Asynchronously starting the connection to the MQTT broker.")
self._client.loop_start() self._client.loop_start()
@ -123,8 +130,11 @@ class Collector:
if key in self._online and self._online[key] is True: if key in self._online and self._online[key] is True:
result = self._collected.get(key, None) result = self._collected.get(key, None)
elif key in self._collected: elif key in self._collected:
stale, max_hours, same_day = self._stale_allowed.get(key, (False, 0, False)) stale, max_hours, same_day, dumb = self._stale_allowed.get(key, (False, 0, False, False))
if stale: if dumb:
# no need to check the liveliness of the sensor, just give the result
result = self._collected[key]
elif stale:
last_updated = self._collected[key]["lastupdated"] last_updated = self._collected[key]["lastupdated"]
if last_updated: # verify that we definitely have the information on the last update time if last_updated: # verify that we definitely have the information on the last update time
datetime_key = datetime.strptime(last_updated, '%Y-%m-%dT%H-%M-%S') datetime_key = datetime.strptime(last_updated, '%Y-%m-%dT%H-%M-%S')
@ -146,7 +156,8 @@ class Collector:
def _process(self, message: mqtt.MQTTMessage, key: str) -> None: def _process(self, message: mqtt.MQTTMessage, key: str) -> None:
topic = message.topic topic = message.topic
payload = message.payload.decode("utf-8") # hack the JSON: convert single quotes into double quotes as needed
payload = message.payload.decode("utf-8").replace("'", '"')
if topic.endswith("/status"): if topic.endswith("/status"):
self._online[key] = payload == "on" self._online[key] = payload == "on"
@ -156,9 +167,17 @@ class Collector:
elif topic.endswith("/value"): elif topic.endswith("/value"):
self._collected[key] = json.loads(payload) self._collected[key] = json.loads(payload)
log.debug("Changed the value of %s to %s.", topic, json.loads(payload)) log.debug("Changed the value of %s to %s.", topic, json.loads(payload))
if self._previous_process:
self._previous_process.terminate()
self._previous_process = self._current_process
self._current_process = Process(target=self.callback)
self._current_process.start()
def _create_handler(self, key: str) -> Callable[[mqtt.Client, Any, mqtt.MQTTMessage], None]: def _create_handler(self, key: str) -> Callable[[mqtt.Client, Any, mqtt.MQTTMessage], None]:
def handler(_client: mqtt.Client, _userdata: Any, message: mqtt.MQTTMessage): def handler(_client: mqtt.Client, _userdata: Any, message: mqtt.MQTTMessage):
self._process(message, key) self._process(message, key)
return handler return handler
def _callback(self):
pass

View File

@ -39,6 +39,13 @@ UVINDEX_SENSE = 128
ICON_SENSE = 256 ICON_SENSE = 256
ICON_3H_SENSE = 512 ICON_3H_SENSE = 512
ICON_6H_SENSE = 1024 ICON_6H_SENSE = 1024
APPARENT_SENSE = 2048
WINDGUSTS_SENSE = 4096
CLOUDCOVER_SENSE = 8192
RAIN_INTENSITY_SENSE = 16384
RAIN_PROBABILITY_SENSE = 32768
SOLAR_RADIATION_SENSE = 65536
# Constants used to create a family of light sensors. # Constants used to create a family of light sensors.
# This can be switched to enum.Flag as of Python 3.6. # This can be switched to enum.Flag as of Python 3.6.
@ -277,7 +284,7 @@ class Publisher:
:return: a Payload object, which can be used to set the Payload :return: a Payload object, which can be used to set the Payload
for this type of weather sensor with the given version. """ for this type of weather sensor with the given version. """
"""The version of a sensor is given as a bit mask.""" """The version of a sensor is given as a bit mask."""
if version < 1 or version > 2047: if version < 1 or version > 131071:
raise ValueError("Version type too low or too high.") raise ValueError("Version type too low or too high.")
senses = [(TEMPERATURE_SENSE, "temperature"), senses = [(TEMPERATURE_SENSE, "temperature"),
@ -290,7 +297,14 @@ class Publisher:
(UVINDEX_SENSE, "uvindex"), (UVINDEX_SENSE, "uvindex"),
(ICON_SENSE, "icon"), (ICON_SENSE, "icon"),
(ICON_3H_SENSE, "icon_3h"), (ICON_3H_SENSE, "icon_3h"),
(ICON_6H_SENSE, "icon_6h")] (ICON_6H_SENSE, "icon_6h"),
(APPARENT_SENSE, "apparent_temperature"),
(WINDGUSTS_SENSE, "windgusts"),
(CLOUDCOVER_SENSE, "cloud_cover"),
(RAIN_INTENSITY_SENSE, "rain_intensity"),
(RAIN_PROBABILITY_SENSE, "rain_probability"),
(SOLAR_RADIATION_SENSE, "solar_radiation")
]
keys = [] keys = []