Added support for fan sensors on the system plugin.

This commit is contained in:
Fabio Manganiello 2023-04-23 00:08:27 +02:00
parent 45d5f439be
commit b3440ab96b
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
11 changed files with 127 additions and 50 deletions

View file

@ -0,0 +1 @@
NumericSensor.vue

View file

@ -87,6 +87,14 @@
} }
}, },
"system_fan": {
"name": "System",
"name_plural": "System",
"icon": {
"class": "fas fa-fan"
}
},
"cpu": { "cpu": {
"name": "System", "name": "System",
"name_plural": "System", "name_plural": "System",

View file

@ -4,6 +4,7 @@ from platypush.common.db import Base
from . import Entity from . import Entity
from .devices import Device from .devices import Device
from .sensors import NumericSensor
from .temperature import TemperatureSensor from .temperature import TemperatureSensor
@ -232,3 +233,23 @@ if 'system_temperature' not in Base.metadata:
__mapper_args__ = { __mapper_args__ = {
'polymorphic_identity': __tablename__, '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__,
}

View file

@ -12,21 +12,6 @@ class SensorResponse(SystemResponse):
pass 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): class SensorBatteryResponse(SensorResponse):
def __init__( def __init__(
self, percent: float, secs_left: int, power_plugged: bool, *args, **kwargs 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) 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): class ConnectedUserResponseList(SystemResponseList):
def __init__(self, responses: List[ConnectUserResponse], *args, **kwargs): def __init__(self, responses: List[ConnectUserResponse], *args, **kwargs):
super().__init__(responses=responses, *args, **kwargs) super().__init__(responses=responses, *args, **kwargs)

View file

@ -19,11 +19,10 @@ from platypush.entities.system import (
MemoryStats as MemoryStatsModel, MemoryStats as MemoryStatsModel,
NetworkInterface as NetworkInterfaceModel, NetworkInterface as NetworkInterfaceModel,
SwapStats as SwapStatsModel, SwapStats as SwapStatsModel,
SystemFan,
SystemTemperature, SystemTemperature,
) )
from platypush.message.response.system import ( from platypush.message.response.system import (
SensorResponseList,
SensorFanResponse,
SensorBatteryResponse, SensorBatteryResponse,
ConnectedUserResponseList, ConnectedUserResponseList,
ConnectUserResponse, ConnectUserResponse,
@ -44,6 +43,8 @@ from platypush.schemas.system import (
CpuTimesSchema, CpuTimesSchema,
Disk, Disk,
DiskSchema, DiskSchema,
Fan,
FanSchema,
MemoryStats, MemoryStats,
MemoryStatsSchema, MemoryStatsSchema,
NetworkInterface, NetworkInterface,
@ -363,36 +364,28 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
return TemperatureSchema().dump(self._sensors_temperature(), many=True) 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 @action
def sensors_fan(self, sensor: Optional[str] = None) -> SensorResponseList: def sensors_fan(self) -> List[dict]:
""" """
Get stats from the fan sensors. Get stats from the fan sensors.
:param sensor: Select the sensor name. :return: .. schema:: system.FanSchema(many=True)
:return: List of :class:`platypush.message.response.system.SensorFanResponse`.
""" """
stats = psutil.sensors_fans() return FanSchema().dump(self._sensors_fan(), many=True)
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()]
)
@action @action
def sensors_battery(self) -> SensorBatteryResponse: def sensors_battery(self) -> SensorBatteryResponse:
@ -545,7 +538,7 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
:return: .. schema:: system.SystemInfoSchema :return: .. schema:: system.SystemInfoSchema
""" """
ret = SystemInfoSchema().dump( return SystemInfoSchema().dump(
{ {
'cpu': { 'cpu': {
'frequency': self._cpu_frequency_avg(), 'frequency': self._cpu_frequency_avg(),
@ -560,11 +553,10 @@ class SystemPlugin(SensorPlugin, EntityManager):
'disks': self._disk_info(), 'disks': self._disk_info(),
'network': self._network_info(), 'network': self._network_info(),
'temperature': self._sensors_temperature(), 'temperature': self._sensors_temperature(),
'fans': self._sensors_fan(),
} }
) )
return ret
@override @override
def transform_entities(self, entities: dict) -> List[Entity]: def transform_entities(self, entities: dict) -> List[Entity]:
cpu = entities['cpu'].copy() cpu = entities['cpu'].copy()
@ -662,6 +654,15 @@ class SystemPlugin(SensorPlugin, EntityManager):
) )
for temp in entities.get('temperature', []) 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', [])
],
] ]

View file

@ -11,6 +11,7 @@ from ._cpu import (
CpuTimesSchema, CpuTimesSchema,
) )
from ._disk import Disk, DiskSchema from ._disk import Disk, DiskSchema
from ._fan import Fan, FanSchema
from ._memory import MemoryStats, MemoryStatsSchema, SwapStats, SwapStatsSchema from ._memory import MemoryStats, MemoryStatsSchema, SwapStats, SwapStatsSchema
from ._model import SystemInfo from ._model import SystemInfo
from ._network import NetworkInterface, NetworkInterfaceSchema from ._network import NetworkInterface, NetworkInterfaceSchema
@ -32,6 +33,8 @@ __all__ = [
"CpuTimesSchema", "CpuTimesSchema",
"Disk", "Disk",
"DiskSchema", "DiskSchema",
"Fan",
"FanSchema",
"MemoryStats", "MemoryStats",
"MemoryStatsSchema", "MemoryStatsSchema",
"SwapStats", "SwapStats",

View file

@ -0,0 +1,4 @@
from ._model import Fan
from ._schemas import FanSchema
__all__ = ["Fan", "FanSchema"]

View file

@ -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

View file

@ -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,
}
}
)

View file

@ -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)

View file

@ -3,6 +3,7 @@ from typing import List
from ._cpu import Cpu from ._cpu import Cpu
from ._disk import Disk from ._disk import Disk
from ._fan import Fan
from ._memory import MemoryStats, SwapStats from ._memory import MemoryStats, SwapStats
from ._network import NetworkInterface from ._network import NetworkInterface
from ._temperature import Temperature from ._temperature import Temperature
@ -20,3 +21,4 @@ class SystemInfo:
disks: List[Disk] disks: List[Disk]
network: List[NetworkInterface] network: List[NetworkInterface]
temperature: List[Temperature] temperature: List[Temperature]
fans: List[Fan]