Added support for system temperature sensor entities.

This commit is contained in:
Fabio Manganiello 2023-04-22 22:42:11 +02:00
parent 1b048e1952
commit 45d5f439be
Signed by: blacklight
GPG key ID: D90FBA7F76362774
11 changed files with 178 additions and 119 deletions

View file

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

View file

@ -39,14 +39,6 @@
} }
}, },
"cpu": {
"name": "System",
"name_plural": "System",
"icon": {
"class": "fas fa-microchip"
}
},
"memory_stats": { "memory_stats": {
"name": "System", "name": "System",
"name_plural": "System", "name_plural": "System",
@ -79,6 +71,14 @@
} }
}, },
"system_temperature": {
"name": "System",
"name_plural": "System",
"icon": {
"class": "fas fa-temperature-half"
}
},
"current_sensor": { "current_sensor": {
"name": "Sensor", "name": "Sensor",
"name_plural": "Sensors", "name_plural": "Sensors",
@ -87,6 +87,14 @@
} }
}, },
"cpu": {
"name": "System",
"name_plural": "System",
"icon": {
"class": "fas fa-microchip"
}
},
"motion_sensor": { "motion_sensor": {
"name": "Sensor", "name": "Sensor",
"name_plural": "Sensors", "name_plural": "Sensors",

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 .temperature import TemperatureSensor
if 'cpu' not in Base.metadata: if 'cpu' not in Base.metadata:
@ -208,3 +209,26 @@ if 'network_interface' not in Base.metadata:
__mapper_args__ = { __mapper_args__ = {
'polymorphic_identity': __tablename__, '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__,
}

View file

@ -8,50 +8,10 @@ class SystemResponse(Response):
pass pass
class CpuResponse(SystemResponse):
pass
class MemoryResponse(SystemResponse):
pass
class DiskResponse(SystemResponse):
pass
class NetworkResponse(SystemResponse):
pass
class SensorResponse(SystemResponse): class SensorResponse(SystemResponse):
pass 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): class SensorFanResponse(SensorResponse):
def __init__( def __init__(
self, name: str, current: int, label: Optional[str] = None, *args, **kwargs self, name: str, current: int, label: Optional[str] = None, *args, **kwargs

View file

@ -1,7 +1,7 @@
import os import os
from datetime import datetime from datetime import datetime
from typing import Tuple, Union, List, Optional, Dict from typing import Tuple, Union, List, Optional
from typing_extensions import override from typing_extensions import override
import psutil import psutil
@ -19,9 +19,9 @@ from platypush.entities.system import (
MemoryStats as MemoryStatsModel, MemoryStats as MemoryStatsModel,
NetworkInterface as NetworkInterfaceModel, NetworkInterface as NetworkInterfaceModel,
SwapStats as SwapStatsModel, SwapStats as SwapStatsModel,
SystemTemperature,
) )
from platypush.message.response.system import ( from platypush.message.response.system import (
SensorTemperatureResponse,
SensorResponseList, SensorResponseList,
SensorFanResponse, SensorFanResponse,
SensorBatteryResponse, SensorBatteryResponse,
@ -51,6 +51,8 @@ from platypush.schemas.system import (
SwapStats, SwapStats,
SwapStatsSchema, SwapStatsSchema,
SystemInfoSchema, SystemInfoSchema,
Temperature,
TemperatureSchema,
) )
@ -145,9 +147,9 @@ class SystemPlugin(SensorPlugin, EntityManager):
return list(percent) # type: ignore return list(percent) # type: ignore
return percent return percent
def _cpu_stats(self) -> CpuStats: @staticmethod
stats = psutil.cpu_stats() def _cpu_stats() -> CpuStats:
return CpuStatsSchema().load(stats) # type: ignore return CpuStatsSchema().load(psutil.cpu_stats()) # type: ignore
@action @action
def cpu_stats(self) -> CpuStats: def cpu_stats(self) -> CpuStats:
@ -158,13 +160,13 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
return CpuStatsSchema().dump(self._cpu_stats()) # type: ignore return CpuStatsSchema().dump(self._cpu_stats()) # type: ignore
def _cpu_frequency_avg(self) -> CpuFrequency: @staticmethod
freq = psutil.cpu_freq(percpu=False) def _cpu_frequency_avg() -> CpuFrequency:
return CpuFrequencySchema().load(freq) # type: ignore return CpuFrequencySchema().load(psutil.cpu_freq(percpu=False)) # type: ignore
def _cpu_frequency_per_cpu(self) -> List[CpuFrequency]: @staticmethod
freq = psutil.cpu_freq(percpu=True) def _cpu_frequency_per_cpu() -> List[CpuFrequency]:
return CpuFrequencySchema().load(freq, many=True) # type: ignore return CpuFrequencySchema().load(psutil.cpu_freq(percpu=True), many=True) # type: ignore
@action @action
def cpu_frequency( def cpu_frequency(
@ -193,7 +195,8 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
return psutil.getloadavg() return psutil.getloadavg()
def _mem_virtual(self) -> MemoryStats: @staticmethod
def _mem_virtual() -> MemoryStats:
return MemoryStatsSchema().load(psutil.virtual_memory()) # type: ignore return MemoryStatsSchema().load(psutil.virtual_memory()) # type: ignore
@action @action
@ -205,7 +208,8 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
return MemoryStatsSchema().dump(self._mem_virtual()) # type: ignore 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 return SwapStatsSchema().load(psutil.swap_memory()) # type: ignore
@action @action
@ -217,7 +221,8 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
return SwapStatsSchema().dump(self._mem_swap()) # type: ignore 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()} parts = {part.device: part._asdict() for part in psutil.disk_partitions()}
basename_parts = {os.path.basename(part): part for part in parts} basename_parts = {os.path.basename(part): part for part in parts}
io_stats = { io_stats = {
@ -252,7 +257,8 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
return DiskSchema().dump(self._disk_info(), many=True) 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() addrs = psutil.net_if_addrs()
stats = psutil.net_if_stats() stats = psutil.net_if_stats()
@ -270,7 +276,8 @@ class SystemPlugin(SensorPlugin, EntityManager):
many=True, many=True,
) )
def _network_info_avg(self) -> NetworkInterface: @staticmethod
def _network_info_avg() -> NetworkInterface:
stats = psutil.net_io_counters(pernic=False) stats = psutil.net_io_counters(pernic=False)
return NetworkInterfaceSchema().load( # type: ignore return NetworkInterfaceSchema().load( # type: ignore
{ {
@ -332,65 +339,29 @@ class SystemPlugin(SensorPlugin, EntityManager):
many=True, 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 @action
def sensors_temperature( def sensors_temperature(self) -> List[dict]:
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. :return: .. schema:: system.TemperatureSchema(many=True)
:param fahrenheit: Return the temperature in Fahrenheit (default: Celsius).
""" """
stats = psutil.sensors_temperatures(fahrenheit=fahrenheit) return TemperatureSchema().dump(self._sensors_temperature(), many=True)
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
@action @action
def sensors_fan(self, sensor: Optional[str] = None) -> SensorResponseList: def sensors_fan(self, sensor: Optional[str] = None) -> SensorResponseList:
@ -588,6 +559,7 @@ class SystemPlugin(SensorPlugin, EntityManager):
'swap': self._mem_swap(), 'swap': self._mem_swap(),
'disks': self._disk_info(), 'disks': self._disk_info(),
'network': self._network_info(), 'network': self._network_info(),
'temperature': self._sensors_temperature(),
} }
) )
@ -679,7 +651,16 @@ class SystemPlugin(SensorPlugin, EntityManager):
reachable=nic.pop('is_up'), reachable=nic.pop('is_up'),
**nic, **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', [])
], ],
] ]

View file

@ -15,6 +15,7 @@ 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
from ._schemas import SystemInfoSchema from ._schemas import SystemInfoSchema
from ._temperature import Temperature, TemperatureSchema
__all__ = [ __all__ = [
@ -39,4 +40,6 @@ __all__ = [
"NetworkInterfaceSchema", "NetworkInterfaceSchema",
"SystemInfo", "SystemInfo",
"SystemInfoSchema", "SystemInfoSchema",
"Temperature",
"TemperatureSchema",
] ]

View file

@ -5,6 +5,7 @@ from ._cpu import Cpu
from ._disk import Disk from ._disk import Disk
from ._memory import MemoryStats, SwapStats from ._memory import MemoryStats, SwapStats
from ._network import NetworkInterface from ._network import NetworkInterface
from ._temperature import Temperature
@dataclass @dataclass
@ -18,3 +19,4 @@ class SystemInfo:
swap: SwapStats swap: SwapStats
disks: List[Disk] disks: List[Disk]
network: List[NetworkInterface] network: List[NetworkInterface]
temperature: List[Temperature]

View file

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

View file

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

View file

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

View file

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