Improved consistency and robustness of sensors backend

This commit is contained in:
Fabio Manganiello 2020-01-08 22:58:24 +01:00
parent 9d592fe370
commit 35cefcc9f5
3 changed files with 87 additions and 54 deletions

View file

@ -94,6 +94,12 @@ class SensorBackend(Backend):
return data return data
@staticmethod
def _get_value(value):
if isinstance(value, float) or isinstance(value, int) or isinstance(value, bool):
return value
return float(value)
def get_new_data(self, new_data): def get_new_data(self, new_data):
if self.data is None or new_data is None: if self.data is None or new_data is None:
return new_data return new_data
@ -101,11 +107,11 @@ class SensorBackend(Backend):
# noinspection PyBroadException # noinspection PyBroadException
try: try:
# Scalar data case # Scalar data case
new_data = float(new_data) new_data = self._get_value(new_data)
return new_data if abs(new_data - self.data) >= self.tolerance else None return new_data if abs(new_data - self.data) >= self.tolerance else None
except: except:
# If it's not a scalar then it should be a dict # If it's not a scalar then it should be a dict
assert isinstance(new_data, dict) assert isinstance(new_data, dict), 'Invalid type {} received for sensor data'.format(type(new_data))
ret = {} ret = {}
for k, v in new_data.items(): for k, v in new_data.items():
@ -123,8 +129,8 @@ class SensorBackend(Backend):
old_v = None old_v = None
try: try:
v = float(v) v = self._get_value(v)
old_v = float(self.data.get(k)) old_v = self._get_value(self.data.get(k))
except (TypeError, ValueError): except (TypeError, ValueError):
is_nan = True is_nan = True
@ -155,46 +161,49 @@ class SensorBackend(Backend):
self.logger.info('Initialized {} sensor backend'.format(self.__class__.__name__)) self.logger.info('Initialized {} sensor backend'.format(self.__class__.__name__))
while not self.should_stop(): while not self.should_stop():
data = self.get_measurement() try:
new_data = self.get_new_data(data) data = self.get_measurement()
new_data = self.get_new_data(data)
if new_data: if new_data:
self.bus.post(SensorDataChangeEvent(data=new_data)) self.bus.post(SensorDataChangeEvent(data=new_data))
data_below_threshold = {} data_below_threshold = {}
data_above_threshold = {} data_above_threshold = {}
if self.thresholds: if self.thresholds:
if isinstance(self.thresholds, dict) and isinstance(data, dict): if isinstance(self.thresholds, dict) and isinstance(data, dict):
for (measure, thresholds) in self.thresholds.items(): for (measure, thresholds) in self.thresholds.items():
if measure not in data: if measure not in data:
continue continue
if not isinstance(thresholds, list): if not isinstance(thresholds, list):
thresholds = [thresholds] thresholds = [thresholds]
for threshold in thresholds: for threshold in thresholds:
if data[measure] > threshold and (self.data is None or ( if data[measure] > threshold and (self.data is None or (
measure in self.data and self.data[measure] <= threshold)): measure in self.data and self.data[measure] <= threshold)):
data_above_threshold[measure] = data[measure] data_above_threshold[measure] = data[measure]
elif data[measure] < threshold and (self.data is None or ( elif data[measure] < threshold and (self.data is None or (
measure in self.data and self.data[measure] >= threshold)): measure in self.data and self.data[measure] >= threshold)):
data_below_threshold[measure] = data[measure] data_below_threshold[measure] = data[measure]
if data_below_threshold: if data_below_threshold:
self.bus.post(SensorDataBelowThresholdEvent(data=data_below_threshold)) self.bus.post(SensorDataBelowThresholdEvent(data=data_below_threshold))
if data_above_threshold: if data_above_threshold:
self.bus.post(SensorDataAboveThresholdEvent(data=data_above_threshold)) self.bus.post(SensorDataAboveThresholdEvent(data=data_above_threshold))
self.data = data self.data = data
if new_data: if new_data:
if isinstance(new_data, dict): if isinstance(new_data, dict):
for k, v in new_data.items(): for k, v in new_data.items():
self.data[k] = v self.data[k] = v
else: else:
self.data = new_data self.data = new_data
except Exception as e:
self.logger.exception(e)
if self.poll_seconds: if self.poll_seconds:
time.sleep(self.poll_seconds) time.sleep(self.poll_seconds)

View file

@ -8,7 +8,6 @@ class SensorBatteryBackend(SensorBackend):
The sensor events triggered by this backend will include any of the following fields: The sensor events triggered by this backend will include any of the following fields:
- ``battery_percent`` - ``battery_percent``
- ``battery_secs_left``
- ``battery_power_plugged`` - ``battery_power_plugged``
Requires: Requires:
@ -21,9 +20,11 @@ class SensorBatteryBackend(SensorBackend):
def get_measurement(self): def get_measurement(self):
plugin = get_plugin('system') plugin = get_plugin('system')
battery = plugin.sensors_battery().output
return { return {
'battery_' + name: value 'battery_percent': battery.get('percent'),
for name, value in plugin.sensors_battery().output.items() 'battery_power_plugged': bool(battery.get('power_plugged')),
} }

View file

@ -1,7 +1,7 @@
import socket import socket
from datetime import datetime from datetime import datetime
from typing import Union, List, Optional from typing import Union, List, Optional, Dict
from platypush.message.response.system import CpuInfoResponse, CpuTimesResponse, CpuResponseList, CpuStatsResponse, \ from platypush.message.response.system import CpuInfoResponse, CpuTimesResponse, CpuResponseList, CpuStatsResponse, \
CpuFrequencyResponse, VirtualMemoryUsageResponse, SwapMemoryUsageResponse, DiskResponseList, \ CpuFrequencyResponse, VirtualMemoryUsageResponse, SwapMemoryUsageResponse, DiskResponseList, \
@ -468,38 +468,61 @@ class SystemPlugin(Plugin):
# noinspection DuplicatedCode # noinspection DuplicatedCode
@action @action
def sensors_temperature(self, sensor: Optional[str] = None, fahrenheit: bool = False) -> SensorResponseList: def sensors_temperature(self, sensor: Optional[str] = None, fahrenheit: bool = False) \
-> Union[SensorTemperatureResponse, List[SensorTemperatureResponse],
Dict[str, Union[SensorTemperatureResponse, List[SensorTemperatureResponse]]]]:
""" """
Get stats from the temperature sensors. Get stats from the temperature sensors.
:param sensor: Select the sensor name. :param sensor: Select the sensor name.
:param fahrenheit: Return the temperature in Fahrenheit (default: Celsius). :param fahrenheit: Return the temperature in Fahrenheit (default: Celsius).
:return: List of :class:`platypush.message.response.system.SensorTemperatureResponse`.
""" """
import psutil import psutil
stats = psutil.sensors_temperatures(fahrenheit=fahrenheit) stats = psutil.sensors_temperatures(fahrenheit=fahrenheit)
def _expand_stats(name, _stats): if sensor:
return SensorResponseList([ stats = [addr for name, addr in stats.items() if name == sensor]
assert stats, 'No such sensor name: {}'.format(sensor)
if len(stats) == 1:
return SensorTemperatureResponse(
name=sensor,
current=stats[0].current,
high=stats[0].high,
critical=stats[0].critical,
label=stats[0].label,
)
return [
SensorTemperatureResponse( SensorTemperatureResponse(
name=name, name=sensor,
current=s.current, current=s.current,
high=s.high, high=s.high,
critical=s.critical, critical=s.critical,
label=s.label, label=s.label,
) )
for s in _stats for s in stats
]) ]
if sensor: ret = {}
stats = [addr for name, addr in stats.items() if name == sensor] for name, data in stats.items():
assert stats, 'No such sensor name: {}'.format(sensor) for stat in data:
return _expand_stats(sensor, stats[0]) resp = SensorTemperatureResponse(
name=sensor,
current=stat.current,
high=stat.high,
critical=stat.critical,
label=stat.label,
).output
return SensorResponseList([ if name not in ret:
_expand_stats(name, stat) ret[name] = resp
for name, stat in stats.items() else:
]) if isinstance(ret[name], list):
ret[name].append(resp)
else:
ret[name] = [ret[name], resp]
return ret
# noinspection DuplicatedCode # noinspection DuplicatedCode
@action @action