From 35cefcc9f543f10478f64140588a8c0d80452b53 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Wed, 8 Jan 2020 22:58:24 +0100 Subject: [PATCH] Improved consistency and robustness of sensors backend --- platypush/backend/sensor/__init__.py | 79 ++++++++++++++++------------ platypush/backend/sensor/battery.py | 7 +-- platypush/plugins/system/__init__.py | 55 +++++++++++++------ 3 files changed, 87 insertions(+), 54 deletions(-) diff --git a/platypush/backend/sensor/__init__.py b/platypush/backend/sensor/__init__.py index 263556af5..3902de879 100644 --- a/platypush/backend/sensor/__init__.py +++ b/platypush/backend/sensor/__init__.py @@ -94,6 +94,12 @@ class SensorBackend(Backend): 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): if self.data is None or new_data is None: return new_data @@ -101,11 +107,11 @@ class SensorBackend(Backend): # noinspection PyBroadException try: # 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 except: # 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 = {} for k, v in new_data.items(): @@ -123,8 +129,8 @@ class SensorBackend(Backend): old_v = None try: - v = float(v) - old_v = float(self.data.get(k)) + v = self._get_value(v) + old_v = self._get_value(self.data.get(k)) except (TypeError, ValueError): is_nan = True @@ -155,46 +161,49 @@ class SensorBackend(Backend): self.logger.info('Initialized {} sensor backend'.format(self.__class__.__name__)) while not self.should_stop(): - data = self.get_measurement() - new_data = self.get_new_data(data) + try: + data = self.get_measurement() + new_data = self.get_new_data(data) - if new_data: - self.bus.post(SensorDataChangeEvent(data=new_data)) + if new_data: + self.bus.post(SensorDataChangeEvent(data=new_data)) - data_below_threshold = {} - data_above_threshold = {} + data_below_threshold = {} + data_above_threshold = {} - if self.thresholds: - if isinstance(self.thresholds, dict) and isinstance(data, dict): - for (measure, thresholds) in self.thresholds.items(): - if measure not in data: - continue + if self.thresholds: + if isinstance(self.thresholds, dict) and isinstance(data, dict): + for (measure, thresholds) in self.thresholds.items(): + if measure not in data: + continue - if not isinstance(thresholds, list): - thresholds = [thresholds] + if not isinstance(thresholds, list): + thresholds = [thresholds] - for threshold in thresholds: - if data[measure] > threshold and (self.data is None or ( - measure in self.data and self.data[measure] <= threshold)): - data_above_threshold[measure] = data[measure] - elif data[measure] < threshold and (self.data is None or ( - measure in self.data and self.data[measure] >= threshold)): - data_below_threshold[measure] = data[measure] + for threshold in thresholds: + if data[measure] > threshold and (self.data is None or ( + measure in self.data and self.data[measure] <= threshold)): + data_above_threshold[measure] = data[measure] + elif data[measure] < threshold and (self.data is None or ( + measure in self.data and self.data[measure] >= threshold)): + data_below_threshold[measure] = data[measure] - if data_below_threshold: - self.bus.post(SensorDataBelowThresholdEvent(data=data_below_threshold)) + if data_below_threshold: + self.bus.post(SensorDataBelowThresholdEvent(data=data_below_threshold)) - if data_above_threshold: - self.bus.post(SensorDataAboveThresholdEvent(data=data_above_threshold)) + if data_above_threshold: + self.bus.post(SensorDataAboveThresholdEvent(data=data_above_threshold)) - self.data = data + self.data = data - if new_data: - if isinstance(new_data, dict): - for k, v in new_data.items(): - self.data[k] = v - else: - self.data = new_data + if new_data: + if isinstance(new_data, dict): + for k, v in new_data.items(): + self.data[k] = v + else: + self.data = new_data + except Exception as e: + self.logger.exception(e) if self.poll_seconds: time.sleep(self.poll_seconds) diff --git a/platypush/backend/sensor/battery.py b/platypush/backend/sensor/battery.py index aa40cac30..28226b908 100644 --- a/platypush/backend/sensor/battery.py +++ b/platypush/backend/sensor/battery.py @@ -8,7 +8,6 @@ class SensorBatteryBackend(SensorBackend): The sensor events triggered by this backend will include any of the following fields: - ``battery_percent`` - - ``battery_secs_left`` - ``battery_power_plugged`` Requires: @@ -21,9 +20,11 @@ class SensorBatteryBackend(SensorBackend): def get_measurement(self): plugin = get_plugin('system') + battery = plugin.sensors_battery().output + return { - 'battery_' + name: value - for name, value in plugin.sensors_battery().output.items() + 'battery_percent': battery.get('percent'), + 'battery_power_plugged': bool(battery.get('power_plugged')), } diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index 2504fec4a..085f0526b 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -1,7 +1,7 @@ import socket 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, \ CpuFrequencyResponse, VirtualMemoryUsageResponse, SwapMemoryUsageResponse, DiskResponseList, \ @@ -468,38 +468,61 @@ class SystemPlugin(Plugin): # noinspection DuplicatedCode @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. :param sensor: Select the sensor name. :param fahrenheit: Return the temperature in Fahrenheit (default: Celsius). - :return: List of :class:`platypush.message.response.system.SensorTemperatureResponse`. """ import psutil stats = psutil.sensors_temperatures(fahrenheit=fahrenheit) - def _expand_stats(name, _stats): - return SensorResponseList([ + if sensor: + 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( - name=name, + name=sensor, current=s.current, high=s.high, critical=s.critical, label=s.label, ) - for s in _stats - ]) + for s in stats + ] - if sensor: - stats = [addr for name, addr in stats.items() if name == sensor] - assert stats, 'No such sensor name: {}'.format(sensor) - return _expand_stats(sensor, stats[0]) + ret = {} + for name, data in stats.items(): + for stat in data: + resp = SensorTemperatureResponse( + name=sensor, + current=stat.current, + high=stat.high, + critical=stat.critical, + label=stat.label, + ).output - return SensorResponseList([ - _expand_stats(name, stat) - for name, stat in stats.items() - ]) + if name not in ret: + ret[name] = resp + else: + if isinstance(ret[name], list): + ret[name].append(resp) + else: + ret[name] = [ret[name], resp] + + return ret # noinspection DuplicatedCode @action