From 45d5f439be9dd9519ae56476fcb198318e044cf6 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 22 Apr 2023 22:42:11 +0200 Subject: [PATCH] Added support for system temperature sensor entities. --- .../panels/Entities/SystemTemperature.vue | 1 + .../src/components/panels/Entities/meta.json | 24 ++-- platypush/entities/system.py | 24 ++++ platypush/message/response/system/__init__.py | 40 ------ platypush/plugins/system/__init__.py | 123 ++++++++---------- platypush/schemas/system/__init__.py | 3 + platypush/schemas/system/_model.py | 2 + .../schemas/system/_temperature/__init__.py | 4 + .../schemas/system/_temperature/_base.py | 15 +++ .../schemas/system/_temperature/_model.py | 54 ++++++++ .../schemas/system/_temperature/_schemas.py | 7 + 11 files changed, 178 insertions(+), 119 deletions(-) create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/SystemTemperature.vue create mode 100644 platypush/schemas/system/_temperature/__init__.py create mode 100644 platypush/schemas/system/_temperature/_base.py create mode 100644 platypush/schemas/system/_temperature/_model.py create mode 100644 platypush/schemas/system/_temperature/_schemas.py diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/SystemTemperature.vue b/platypush/backend/http/webapp/src/components/panels/Entities/SystemTemperature.vue new file mode 120000 index 000000000..84cf6cc14 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/SystemTemperature.vue @@ -0,0 +1 @@ +TemperatureSensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json index 801665c76..6a1acd5ae 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -39,14 +39,6 @@ } }, - "cpu": { - "name": "System", - "name_plural": "System", - "icon": { - "class": "fas fa-microchip" - } - }, - "memory_stats": { "name": "System", "name_plural": "System", @@ -79,6 +71,14 @@ } }, + "system_temperature": { + "name": "System", + "name_plural": "System", + "icon": { + "class": "fas fa-temperature-half" + } + }, + "current_sensor": { "name": "Sensor", "name_plural": "Sensors", @@ -87,6 +87,14 @@ } }, + "cpu": { + "name": "System", + "name_plural": "System", + "icon": { + "class": "fas fa-microchip" + } + }, + "motion_sensor": { "name": "Sensor", "name_plural": "Sensors", diff --git a/platypush/entities/system.py b/platypush/entities/system.py index 5932ea4e0..865dcc8a4 100644 --- a/platypush/entities/system.py +++ b/platypush/entities/system.py @@ -4,6 +4,7 @@ from platypush.common.db import Base from . import Entity from .devices import Device +from .temperature import TemperatureSensor if 'cpu' not in Base.metadata: @@ -208,3 +209,26 @@ if 'network_interface' not in Base.metadata: __mapper_args__ = { 'polymorphic_identity': __tablename__, } + + +if 'system_temperature' not in Base.metadata: + + class SystemTemperature(TemperatureSensor): + """ + Extends the ``TemperatureSensor``. + """ + + __tablename__ = 'system_temperature' + + id = Column( + Integer, + ForeignKey(TemperatureSensor.id, ondelete='CASCADE'), + primary_key=True, + ) + + high = Column(Float) + critical = Column(Float) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } diff --git a/platypush/message/response/system/__init__.py b/platypush/message/response/system/__init__.py index a4269af06..83228a7f7 100644 --- a/platypush/message/response/system/__init__.py +++ b/platypush/message/response/system/__init__.py @@ -8,50 +8,10 @@ class SystemResponse(Response): pass -class CpuResponse(SystemResponse): - pass - - -class MemoryResponse(SystemResponse): - pass - - -class DiskResponse(SystemResponse): - pass - - -class NetworkResponse(SystemResponse): - pass - - class SensorResponse(SystemResponse): pass -class SensorTemperatureResponse(SensorResponse): - def __init__( - self, - name: str, - current: float, - high: Optional[float] = None, - critical: Optional[float] = None, - label: Optional[str] = None, - *args, - **kwargs - ): - super().__init__( - *args, - output={ - 'name': name, - 'current': current, - 'high': high, - 'critical': critical, - 'label': label, - }, - **kwargs - ) - - class SensorFanResponse(SensorResponse): def __init__( self, name: str, current: int, label: Optional[str] = None, *args, **kwargs diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index 9d0e31011..a13a5254e 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -1,7 +1,7 @@ import os from datetime import datetime -from typing import Tuple, Union, List, Optional, Dict +from typing import Tuple, Union, List, Optional from typing_extensions import override import psutil @@ -19,9 +19,9 @@ from platypush.entities.system import ( MemoryStats as MemoryStatsModel, NetworkInterface as NetworkInterfaceModel, SwapStats as SwapStatsModel, + SystemTemperature, ) from platypush.message.response.system import ( - SensorTemperatureResponse, SensorResponseList, SensorFanResponse, SensorBatteryResponse, @@ -51,6 +51,8 @@ from platypush.schemas.system import ( SwapStats, SwapStatsSchema, SystemInfoSchema, + Temperature, + TemperatureSchema, ) @@ -145,9 +147,9 @@ class SystemPlugin(SensorPlugin, EntityManager): return list(percent) # type: ignore return percent - def _cpu_stats(self) -> CpuStats: - stats = psutil.cpu_stats() - return CpuStatsSchema().load(stats) # type: ignore + @staticmethod + def _cpu_stats() -> CpuStats: + return CpuStatsSchema().load(psutil.cpu_stats()) # type: ignore @action def cpu_stats(self) -> CpuStats: @@ -158,13 +160,13 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return CpuStatsSchema().dump(self._cpu_stats()) # type: ignore - def _cpu_frequency_avg(self) -> CpuFrequency: - freq = psutil.cpu_freq(percpu=False) - return CpuFrequencySchema().load(freq) # type: ignore + @staticmethod + def _cpu_frequency_avg() -> CpuFrequency: + return CpuFrequencySchema().load(psutil.cpu_freq(percpu=False)) # type: ignore - def _cpu_frequency_per_cpu(self) -> List[CpuFrequency]: - freq = psutil.cpu_freq(percpu=True) - return CpuFrequencySchema().load(freq, many=True) # type: ignore + @staticmethod + def _cpu_frequency_per_cpu() -> List[CpuFrequency]: + return CpuFrequencySchema().load(psutil.cpu_freq(percpu=True), many=True) # type: ignore @action def cpu_frequency( @@ -193,7 +195,8 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return psutil.getloadavg() - def _mem_virtual(self) -> MemoryStats: + @staticmethod + def _mem_virtual() -> MemoryStats: return MemoryStatsSchema().load(psutil.virtual_memory()) # type: ignore @action @@ -205,7 +208,8 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return MemoryStatsSchema().dump(self._mem_virtual()) # type: ignore - def _mem_swap(self) -> SwapStats: + @staticmethod + def _mem_swap() -> SwapStats: return SwapStatsSchema().load(psutil.swap_memory()) # type: ignore @action @@ -217,7 +221,8 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return SwapStatsSchema().dump(self._mem_swap()) # type: ignore - def _disk_info(self) -> List[Disk]: + @staticmethod + def _disk_info() -> List[Disk]: parts = {part.device: part._asdict() for part in psutil.disk_partitions()} basename_parts = {os.path.basename(part): part for part in parts} io_stats = { @@ -252,7 +257,8 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return DiskSchema().dump(self._disk_info(), many=True) - def _network_info(self) -> List[NetworkInterface]: + @staticmethod + def _network_info() -> List[NetworkInterface]: addrs = psutil.net_if_addrs() stats = psutil.net_if_stats() @@ -270,7 +276,8 @@ class SystemPlugin(SensorPlugin, EntityManager): many=True, ) - def _network_info_avg(self) -> NetworkInterface: + @staticmethod + def _network_info_avg() -> NetworkInterface: stats = psutil.net_io_counters(pernic=False) return NetworkInterfaceSchema().load( # type: ignore { @@ -332,65 +339,29 @@ class SystemPlugin(SensorPlugin, EntityManager): many=True, ) + @staticmethod + def _sensors_temperature() -> List[Temperature]: + return TemperatureSchema().load( # type: ignore + [ + { + **sensor._asdict(), + 'id': f'{kind}_{i + 1}', + 'label': (f'{kind} #{i + 1}' if not sensor.label else sensor.label), + } + for kind, sensors in psutil.sensors_temperatures().items() + for i, sensor in enumerate(sensors) + ], + many=True, + ) + @action - def sensors_temperature( - self, sensor: Optional[str] = None, fahrenheit: bool = False - ) -> Union[ - SensorTemperatureResponse, - List[SensorTemperatureResponse], - Dict[str, Union[SensorTemperatureResponse, List[SensorTemperatureResponse]]], - ]: + def sensors_temperature(self) -> List[dict]: """ Get stats from the temperature sensors. - :param sensor: Select the sensor name. - :param fahrenheit: Return the temperature in Fahrenheit (default: Celsius). + :return: .. schema:: system.TemperatureSchema(many=True) """ - stats = psutil.sensors_temperatures(fahrenheit=fahrenheit) - - 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=sensor, - current=s.current, - high=s.high, - critical=s.critical, - label=s.label, - ) - for s in stats - ] - - 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 - - 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 + return TemperatureSchema().dump(self._sensors_temperature(), many=True) @action def sensors_fan(self, sensor: Optional[str] = None) -> SensorResponseList: @@ -588,6 +559,7 @@ class SystemPlugin(SensorPlugin, EntityManager): 'swap': self._mem_swap(), 'disks': self._disk_info(), 'network': self._network_info(), + 'temperature': self._sensors_temperature(), } ) @@ -679,7 +651,16 @@ class SystemPlugin(SensorPlugin, EntityManager): reachable=nic.pop('is_up'), **nic, ) - for nic in entities['network'] + for nic in entities.get('network', []) + ], + *[ + SystemTemperature( + id=f'system:temperature:{temp.pop("id")}', + name=temp.pop('label'), + unit='°C', + **temp, + ) + for temp in entities.get('temperature', []) ], ] diff --git a/platypush/schemas/system/__init__.py b/platypush/schemas/system/__init__.py index 2b02c0200..e3196c363 100644 --- a/platypush/schemas/system/__init__.py +++ b/platypush/schemas/system/__init__.py @@ -15,6 +15,7 @@ from ._memory import MemoryStats, MemoryStatsSchema, SwapStats, SwapStatsSchema from ._model import SystemInfo from ._network import NetworkInterface, NetworkInterfaceSchema from ._schemas import SystemInfoSchema +from ._temperature import Temperature, TemperatureSchema __all__ = [ @@ -39,4 +40,6 @@ __all__ = [ "NetworkInterfaceSchema", "SystemInfo", "SystemInfoSchema", + "Temperature", + "TemperatureSchema", ] diff --git a/platypush/schemas/system/_model.py b/platypush/schemas/system/_model.py index 89bd7261f..18a6d0519 100644 --- a/platypush/schemas/system/_model.py +++ b/platypush/schemas/system/_model.py @@ -5,6 +5,7 @@ from ._cpu import Cpu from ._disk import Disk from ._memory import MemoryStats, SwapStats from ._network import NetworkInterface +from ._temperature import Temperature @dataclass @@ -18,3 +19,4 @@ class SystemInfo: swap: SwapStats disks: List[Disk] network: List[NetworkInterface] + temperature: List[Temperature] diff --git a/platypush/schemas/system/_temperature/__init__.py b/platypush/schemas/system/_temperature/__init__.py new file mode 100644 index 000000000..be7bb8e91 --- /dev/null +++ b/platypush/schemas/system/_temperature/__init__.py @@ -0,0 +1,4 @@ +from ._model import Temperature +from ._schemas import TemperatureSchema + +__all__ = ["Temperature", "TemperatureSchema"] diff --git a/platypush/schemas/system/_temperature/_base.py b/platypush/schemas/system/_temperature/_base.py new file mode 100644 index 000000000..a5ada9dc6 --- /dev/null +++ b/platypush/schemas/system/_temperature/_base.py @@ -0,0 +1,15 @@ +from marshmallow import pre_load + +from .._base import SystemBaseSchema + + +class TemperatureBaseSchema(SystemBaseSchema): + """ + Base schema for system temperature sensors. + """ + + @pre_load + def pre_load(self, data: dict, **_) -> dict: + data = super().pre_load(data) + data['value'] = data.pop('current', data.pop('value', None)) + return data diff --git a/platypush/schemas/system/_temperature/_model.py b/platypush/schemas/system/_temperature/_model.py new file mode 100644 index 000000000..d53b405c7 --- /dev/null +++ b/platypush/schemas/system/_temperature/_model.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass, field +from typing import Optional + + +@dataclass +class Temperature: + """ + System temperature sensor wrapper. + """ + + id: str = field( + metadata={ + 'metadata': { + 'description': 'Unique ID for the sensor', + 'example': 'acpi_1', + } + } + ) + + label: str = field( + metadata={ + 'metadata': { + 'description': 'Name of the sensor', + 'example': 'CPU', + } + } + ) + + value: float = field( + metadata={ + 'metadata': { + 'description': 'Current temperature value, in Celsius', + 'example': 55, + } + } + ) + + high: Optional[float] = field( + metadata={ + 'metadata': { + 'description': 'High threshold for the temperature sensor, in Celsius', + 'example': 75, + } + } + ) + + critical: Optional[float] = field( + metadata={ + 'metadata': { + 'description': 'Critical threshold for the temperature sensor, in Celsius', + 'example': 95, + } + } + ) diff --git a/platypush/schemas/system/_temperature/_schemas.py b/platypush/schemas/system/_temperature/_schemas.py new file mode 100644 index 000000000..09bc1463f --- /dev/null +++ b/platypush/schemas/system/_temperature/_schemas.py @@ -0,0 +1,7 @@ +from marshmallow_dataclass import class_schema + +from ._base import TemperatureBaseSchema +from ._model import Temperature + + +TemperatureSchema = class_schema(Temperature, base_schema=TemperatureBaseSchema)