mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
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
@ -2,6 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -15,10 +16,12 @@ from homeassistant.const import (
|
|||||||
DATA_RATE_MEGABYTES_PER_SECOND,
|
DATA_RATE_MEGABYTES_PER_SECOND,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
|
TEMP_CELSIUS,
|
||||||
UNIT_PERCENTAGE,
|
UNIT_PERCENTAGE,
|
||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.util import slugify
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
# mypy: allow-untyped-defs, no-check-untyped-defs
|
||||||
@ -27,6 +30,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
CONF_ARG = "arg"
|
CONF_ARG = "arg"
|
||||||
|
|
||||||
|
if sys.maxsize > 2 ** 32:
|
||||||
|
CPU_ICON = "mdi:cpu-64-bit"
|
||||||
|
else:
|
||||||
|
CPU_ICON = "mdi:cpu-32-bit"
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
"disk_free": ["Disk free", DATA_GIBIBYTES, "mdi:harddisk", None],
|
"disk_free": ["Disk free", DATA_GIBIBYTES, "mdi:harddisk", None],
|
||||||
"disk_use": ["Disk use", DATA_GIBIBYTES, "mdi:harddisk", None],
|
"disk_use": ["Disk use", DATA_GIBIBYTES, "mdi:harddisk", None],
|
||||||
@ -56,8 +64,9 @@ SENSOR_TYPES = {
|
|||||||
"mdi:server-network",
|
"mdi:server-network",
|
||||||
None,
|
None,
|
||||||
],
|
],
|
||||||
"process": ["Process", " ", "mdi:memory", None],
|
"process": ["Process", " ", CPU_ICON, None],
|
||||||
"processor_use": ["Processor use", UNIT_PERCENTAGE, "mdi:memory", 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_free": ["Swap free", DATA_MEBIBYTES, "mdi:harddisk", None],
|
||||||
"swap_use": ["Swap use", 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],
|
"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}
|
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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the system monitor sensors."""
|
"""Set up the system monitor sensors."""
|
||||||
dev = []
|
dev = []
|
||||||
for resource in config[CONF_RESOURCES]:
|
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:
|
if CONF_ARG not in resource:
|
||||||
|
if resource[CONF_TYPE].startswith("disk_"):
|
||||||
|
resource[CONF_ARG] = "/"
|
||||||
|
else:
|
||||||
resource[CONF_ARG] = ""
|
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]))
|
dev.append(SystemMonitorSensor(resource[CONF_TYPE], resource[CONF_ARG]))
|
||||||
|
|
||||||
add_entities(dev, True)
|
add_entities(dev, True)
|
||||||
@ -108,10 +152,12 @@ class SystemMonitorSensor(Entity):
|
|||||||
def __init__(self, sensor_type, argument=""):
|
def __init__(self, sensor_type, argument=""):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self._name = "{} {}".format(SENSOR_TYPES[sensor_type][0], argument)
|
self._name = "{} {}".format(SENSOR_TYPES[sensor_type][0], argument)
|
||||||
|
self._unique_id = slugify(f"{sensor_type}_{argument}")
|
||||||
self.argument = argument
|
self.argument = argument
|
||||||
self.type = sensor_type
|
self.type = sensor_type
|
||||||
self._state = None
|
self._state = None
|
||||||
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||||
|
self._available = True
|
||||||
if sensor_type in ["throughput_network_out", "throughput_network_in"]:
|
if sensor_type in ["throughput_network_out", "throughput_network_in"]:
|
||||||
self._last_value = None
|
self._last_value = None
|
||||||
self._last_update_time = None
|
self._last_update_time = None
|
||||||
@ -121,6 +167,11 @@ class SystemMonitorSensor(Entity):
|
|||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return self._name.rstrip()
|
return self._name.rstrip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the unique ID."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the class of this sensor."""
|
"""Return the class of this sensor."""
|
||||||
@ -141,6 +192,11 @@ class SystemMonitorSensor(Entity):
|
|||||||
"""Return the unit of measurement of this entity, if any."""
|
"""Return the unit of measurement of this entity, if any."""
|
||||||
return self._unit_of_measurement
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self._available
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest system information."""
|
"""Get the latest system information."""
|
||||||
if self.type == "disk_use_percent":
|
if self.type == "disk_use_percent":
|
||||||
@ -166,6 +222,8 @@ class SystemMonitorSensor(Entity):
|
|||||||
self._state = round(psutil.swap_memory().free / 1024 ** 2, 1)
|
self._state = round(psutil.swap_memory().free / 1024 ** 2, 1)
|
||||||
elif self.type == "processor_use":
|
elif self.type == "processor_use":
|
||||||
self._state = round(psutil.cpu_percent(interval=None))
|
self._state = round(psutil.cpu_percent(interval=None))
|
||||||
|
elif self.type == "processor_temperature":
|
||||||
|
self._state = self.read_cpu_temperature()
|
||||||
elif self.type == "process":
|
elif self.type == "process":
|
||||||
for proc in psutil.process_iter():
|
for proc in psutil.process_iter():
|
||||||
try:
|
try:
|
||||||
@ -231,3 +289,23 @@ class SystemMonitorSensor(Entity):
|
|||||||
self._state = round(os.getloadavg()[1], 2)
|
self._state = round(os.getloadavg()[1], 2)
|
||||||
elif self.type == "load_15m":
|
elif self.type == "load_15m":
|
||||||
self._state = round(os.getloadavg()[2], 2)
|
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…
x
Reference in New Issue
Block a user