Improve systemmonitor (#36283)
* Improvements for systemmonitor * Use MDI CPU icon (bit architecture determined at runtime) * Consistent usages of "percent" (ensured backwards compatibility by explicity setting the entity_id) * Default value "/" (root) for disk_* sensors to prevent runtime issues if not specified in configuration * Added CPU temperature sensor to systemmonitor * Code streamlining of CPU temperature retrieval * Corrected sensor state handling and added "available" logic * Corrected ENTITY_ID to include argument * Optimized "available" handling & CPU temperature detection * Corrected tuple for CPU temperature determination * - Do not create CPU temperature entity if no sensor data can be read - Re-use temperature sensor labels from "glances" integration * Array fix * Corrected sensor array access * Handle empty temperature sensor labels (same logic as used by "glances") * PR comments: Setting unique_ID and f-strings * Removed unused constants * Revert entity rename (wait until next release)
This commit is contained in:
parent
2183ff5906
commit
7be5ef8b6c
1 changed files with 81 additions and 3 deletions
|
@ -2,6 +2,7 @@
|
|||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
|
||||
import psutil
|
||||
import voluptuous as vol
|
||||
|
@ -15,10 +16,12 @@ from homeassistant.const import (
|
|||
DATA_RATE_MEGABYTES_PER_SECOND,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
TEMP_CELSIUS,
|
||||
UNIT_PERCENTAGE,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import slugify
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
||||
|
@ -27,6 +30,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
CONF_ARG = "arg"
|
||||
|
||||
if sys.maxsize > 2 ** 32:
|
||||
CPU_ICON = "mdi:cpu-64-bit"
|
||||
else:
|
||||
CPU_ICON = "mdi:cpu-32-bit"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
"disk_free": ["Disk free", DATA_GIBIBYTES, "mdi:harddisk", None],
|
||||
"disk_use": ["Disk use", DATA_GIBIBYTES, "mdi:harddisk", None],
|
||||
|
@ -56,8 +64,9 @@ SENSOR_TYPES = {
|
|||
"mdi:server-network",
|
||||
None,
|
||||
],
|
||||
"process": ["Process", " ", "mdi:memory", None],
|
||||
"processor_use": ["Processor use", UNIT_PERCENTAGE, "mdi:memory", None],
|
||||
"process": ["Process", " ", CPU_ICON, None],
|
||||
"processor_use": ["Processor use", UNIT_PERCENTAGE, CPU_ICON, None],
|
||||
"processor_temperature": ["Processor temperature", TEMP_CELSIUS, CPU_ICON, None],
|
||||
"swap_free": ["Swap free", DATA_MEBIBYTES, "mdi:harddisk", None],
|
||||
"swap_use": ["Swap use", DATA_MEBIBYTES, "mdi:harddisk", None],
|
||||
"swap_use_percent": ["Swap use (percent)", UNIT_PERCENTAGE, "mdi:harddisk", None],
|
||||
|
@ -90,13 +99,48 @@ IO_COUNTER = {
|
|||
|
||||
IF_ADDRS_FAMILY = {"ipv4_address": socket.AF_INET, "ipv6_address": socket.AF_INET6}
|
||||
|
||||
# There might be additional keys to be added for different
|
||||
# platforms / hardware combinations.
|
||||
# Taken from last version of "glances" integration before they moved to
|
||||
# a generic temperature sensor logic.
|
||||
# https://github.com/home-assistant/core/blob/5e15675593ba94a2c11f9f929cdad317e27ce190/homeassistant/components/glances/sensor.py#L199
|
||||
CPU_SENSOR_PREFIXES = [
|
||||
"amdgpu 1",
|
||||
"aml_thermal",
|
||||
"Core 0",
|
||||
"Core 1",
|
||||
"CPU Temperature",
|
||||
"CPU",
|
||||
"cpu-thermal 1",
|
||||
"cpu_thermal 1",
|
||||
"exynos-therm 1",
|
||||
"Package id 0",
|
||||
"Physical id 0",
|
||||
"radeon 1",
|
||||
"soc-thermal 1",
|
||||
"soc_thermal 1",
|
||||
]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the system monitor sensors."""
|
||||
dev = []
|
||||
for resource in config[CONF_RESOURCES]:
|
||||
# Initialize the sensor argument if none was provided.
|
||||
# For disk monitoring default to "/" (root) to prevent runtime errors, if argument was not specified.
|
||||
if CONF_ARG not in resource:
|
||||
resource[CONF_ARG] = ""
|
||||
if resource[CONF_TYPE].startswith("disk_"):
|
||||
resource[CONF_ARG] = "/"
|
||||
else:
|
||||
resource[CONF_ARG] = ""
|
||||
|
||||
# Verify if we can retrieve CPU / processor temperatures.
|
||||
# If not, do not create the entity and add a warning to the log
|
||||
if resource[CONF_TYPE] == "processor_temperature":
|
||||
if SystemMonitorSensor.read_cpu_temperature() is None:
|
||||
_LOGGER.warning("Cannot read CPU / processor temperature information.")
|
||||
continue
|
||||
|
||||
dev.append(SystemMonitorSensor(resource[CONF_TYPE], resource[CONF_ARG]))
|
||||
|
||||
add_entities(dev, True)
|
||||
|
@ -108,10 +152,12 @@ class SystemMonitorSensor(Entity):
|
|||
def __init__(self, sensor_type, argument=""):
|
||||
"""Initialize the sensor."""
|
||||
self._name = "{} {}".format(SENSOR_TYPES[sensor_type][0], argument)
|
||||
self._unique_id = slugify(f"{sensor_type}_{argument}")
|
||||
self.argument = argument
|
||||
self.type = sensor_type
|
||||
self._state = None
|
||||
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||
self._available = True
|
||||
if sensor_type in ["throughput_network_out", "throughput_network_in"]:
|
||||
self._last_value = None
|
||||
self._last_update_time = None
|
||||
|
@ -121,6 +167,11 @@ class SystemMonitorSensor(Entity):
|
|||
"""Return the name of the sensor."""
|
||||
return self._name.rstrip()
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
|
@ -141,6 +192,11 @@ class SystemMonitorSensor(Entity):
|
|||
"""Return the unit of measurement of this entity, if any."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self._available
|
||||
|
||||
def update(self):
|
||||
"""Get the latest system information."""
|
||||
if self.type == "disk_use_percent":
|
||||
|
@ -166,6 +222,8 @@ class SystemMonitorSensor(Entity):
|
|||
self._state = round(psutil.swap_memory().free / 1024 ** 2, 1)
|
||||
elif self.type == "processor_use":
|
||||
self._state = round(psutil.cpu_percent(interval=None))
|
||||
elif self.type == "processor_temperature":
|
||||
self._state = self.read_cpu_temperature()
|
||||
elif self.type == "process":
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
|
@ -231,3 +289,23 @@ class SystemMonitorSensor(Entity):
|
|||
self._state = round(os.getloadavg()[1], 2)
|
||||
elif self.type == "load_15m":
|
||||
self._state = round(os.getloadavg()[2], 2)
|
||||
|
||||
@staticmethod
|
||||
def read_cpu_temperature():
|
||||
"""Attempt to read CPU / processor temperature."""
|
||||
temps = psutil.sensors_temperatures()
|
||||
|
||||
for name, entries in temps.items():
|
||||
i = 1
|
||||
for entry in entries:
|
||||
# In case the label is empty (e.g. on Raspberry PI 4),
|
||||
# construct it ourself here based on the sensor key name.
|
||||
if not entry.label:
|
||||
_label = f"{name} {i}"
|
||||
else:
|
||||
_label = entry.label
|
||||
|
||||
if _label in CPU_SENSOR_PREFIXES:
|
||||
return round(entry.current, 1)
|
||||
|
||||
i += 1
|
||||
|
|
Loading…
Add table
Reference in a new issue