diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/SystemFan.vue b/platypush/backend/http/webapp/src/components/panels/Entities/SystemFan.vue new file mode 120000 index 00000000..528582b6 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/SystemFan.vue @@ -0,0 +1 @@ +NumericSensor.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 6a1acd5a..087ce71a 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -87,6 +87,14 @@ } }, + "system_fan": { + "name": "System", + "name_plural": "System", + "icon": { + "class": "fas fa-fan" + } + }, + "cpu": { "name": "System", "name_plural": "System", diff --git a/platypush/entities/system.py b/platypush/entities/system.py index 865dcc8a..78df645c 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 .sensors import NumericSensor from .temperature import TemperatureSensor @@ -232,3 +233,23 @@ if 'system_temperature' not in Base.metadata: __mapper_args__ = { 'polymorphic_identity': __tablename__, } + + +if 'system_fan' not in Base.metadata: + + class SystemFan(NumericSensor): + """ + ``SystemFan`` ORM model. + """ + + __tablename__ = 'system_fan' + + id = Column( + Integer, + ForeignKey(NumericSensor.id, ondelete='CASCADE'), + primary_key=True, + ) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } diff --git a/platypush/message/response/system/__init__.py b/platypush/message/response/system/__init__.py index 83228a7f..d3735066 100644 --- a/platypush/message/response/system/__init__.py +++ b/platypush/message/response/system/__init__.py @@ -12,21 +12,6 @@ class SensorResponse(SystemResponse): pass -class SensorFanResponse(SensorResponse): - def __init__( - self, name: str, current: int, label: Optional[str] = None, *args, **kwargs - ): - super().__init__( - *args, - output={ - 'name': name, - 'current': current, - 'label': label, - }, - **kwargs - ) - - class SensorBatteryResponse(SensorResponse): def __init__( self, percent: float, secs_left: int, power_plugged: bool, *args, **kwargs @@ -127,11 +112,6 @@ class SystemResponseList(SystemResponse): super().__init__(output=[r.output for r in responses], *args, **kwargs) -class SensorResponseList(SensorResponse, SystemResponseList): - def __init__(self, responses: List[SensorResponse], *args, **kwargs): - super().__init__(responses=responses, *args, **kwargs) - - class ConnectedUserResponseList(SystemResponseList): def __init__(self, responses: List[ConnectUserResponse], *args, **kwargs): super().__init__(responses=responses, *args, **kwargs) diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index a13a5254..e2cb6a75 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -19,11 +19,10 @@ from platypush.entities.system import ( MemoryStats as MemoryStatsModel, NetworkInterface as NetworkInterfaceModel, SwapStats as SwapStatsModel, + SystemFan, SystemTemperature, ) from platypush.message.response.system import ( - SensorResponseList, - SensorFanResponse, SensorBatteryResponse, ConnectedUserResponseList, ConnectUserResponse, @@ -44,6 +43,8 @@ from platypush.schemas.system import ( CpuTimesSchema, Disk, DiskSchema, + Fan, + FanSchema, MemoryStats, MemoryStatsSchema, NetworkInterface, @@ -363,36 +364,28 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return TemperatureSchema().dump(self._sensors_temperature(), many=True) + def _sensors_fan(self) -> List[Fan]: + return FanSchema().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_fans().items() + for i, sensor in enumerate(sensors) + ], + many=True, + ) + @action - def sensors_fan(self, sensor: Optional[str] = None) -> SensorResponseList: + def sensors_fan(self) -> List[dict]: """ Get stats from the fan sensors. - :param sensor: Select the sensor name. - :return: List of :class:`platypush.message.response.system.SensorFanResponse`. + :return: .. schema:: system.FanSchema(many=True) """ - stats = psutil.sensors_fans() - - def _expand_stats(name, _stats): - return SensorResponseList( - [ - SensorFanResponse( - name=name, - current=s.current, - label=s.label, - ) - 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]) - - return SensorResponseList( - [_expand_stats(name, stat) for name, stat in stats.items()] - ) + return FanSchema().dump(self._sensors_fan(), many=True) @action def sensors_battery(self) -> SensorBatteryResponse: @@ -545,7 +538,7 @@ class SystemPlugin(SensorPlugin, EntityManager): """ :return: .. schema:: system.SystemInfoSchema """ - ret = SystemInfoSchema().dump( + return SystemInfoSchema().dump( { 'cpu': { 'frequency': self._cpu_frequency_avg(), @@ -560,11 +553,10 @@ class SystemPlugin(SensorPlugin, EntityManager): 'disks': self._disk_info(), 'network': self._network_info(), 'temperature': self._sensors_temperature(), + 'fans': self._sensors_fan(), } ) - return ret - @override def transform_entities(self, entities: dict) -> List[Entity]: cpu = entities['cpu'].copy() @@ -662,6 +654,15 @@ class SystemPlugin(SensorPlugin, EntityManager): ) for temp in entities.get('temperature', []) ], + *[ + SystemFan( + id=f'system:fan:{fan.pop("id")}', + name=fan.pop('label'), + unit='rpm', + **fan, + ) + for fan in entities.get('fans', []) + ], ] diff --git a/platypush/schemas/system/__init__.py b/platypush/schemas/system/__init__.py index e3196c36..9a2a876e 100644 --- a/platypush/schemas/system/__init__.py +++ b/platypush/schemas/system/__init__.py @@ -11,6 +11,7 @@ from ._cpu import ( CpuTimesSchema, ) from ._disk import Disk, DiskSchema +from ._fan import Fan, FanSchema from ._memory import MemoryStats, MemoryStatsSchema, SwapStats, SwapStatsSchema from ._model import SystemInfo from ._network import NetworkInterface, NetworkInterfaceSchema @@ -32,6 +33,8 @@ __all__ = [ "CpuTimesSchema", "Disk", "DiskSchema", + "Fan", + "FanSchema", "MemoryStats", "MemoryStatsSchema", "SwapStats", diff --git a/platypush/schemas/system/_fan/__init__.py b/platypush/schemas/system/_fan/__init__.py new file mode 100644 index 00000000..d3433f32 --- /dev/null +++ b/platypush/schemas/system/_fan/__init__.py @@ -0,0 +1,4 @@ +from ._model import Fan +from ._schemas import FanSchema + +__all__ = ["Fan", "FanSchema"] diff --git a/platypush/schemas/system/_fan/_base.py b/platypush/schemas/system/_fan/_base.py new file mode 100644 index 00000000..2a3fc749 --- /dev/null +++ b/platypush/schemas/system/_fan/_base.py @@ -0,0 +1,15 @@ +from marshmallow import pre_load + +from .._base import SystemBaseSchema + + +class FanBaseSchema(SystemBaseSchema): + """ + Base schema for system fan 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/_fan/_model.py b/platypush/schemas/system/_fan/_model.py new file mode 100644 index 00000000..17a6ff93 --- /dev/null +++ b/platypush/schemas/system/_fan/_model.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass, field + + +@dataclass +class Fan: + """ + System fan sensor data class. + """ + + 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 fan speed, in RPM', + 'example': 3000, + } + } + ) diff --git a/platypush/schemas/system/_fan/_schemas.py b/platypush/schemas/system/_fan/_schemas.py new file mode 100644 index 00000000..1c82d8b2 --- /dev/null +++ b/platypush/schemas/system/_fan/_schemas.py @@ -0,0 +1,7 @@ +from marshmallow_dataclass import class_schema + +from ._base import FanBaseSchema +from ._model import Fan + + +FanSchema = class_schema(Fan, base_schema=FanBaseSchema) diff --git a/platypush/schemas/system/_model.py b/platypush/schemas/system/_model.py index 18a6d051..342dd58e 100644 --- a/platypush/schemas/system/_model.py +++ b/platypush/schemas/system/_model.py @@ -3,6 +3,7 @@ from typing import List from ._cpu import Cpu from ._disk import Disk +from ._fan import Fan from ._memory import MemoryStats, SwapStats from ._network import NetworkInterface from ._temperature import Temperature @@ -20,3 +21,4 @@ class SystemInfo: disks: List[Disk] network: List[NetworkInterface] temperature: List[Temperature] + fans: List[Fan]