Move all System Monitor updates into single Data Update Coordinator (#112055)
This commit is contained in:
parent
faee9d996d
commit
cdd7b94a95
8 changed files with 395 additions and 399 deletions
|
@ -10,7 +10,9 @@ from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, DOMAIN_COORDINATOR
|
||||||
|
from .coordinator import SystemMonitorCoordinator
|
||||||
|
from .util import get_all_disk_mounts
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -21,6 +23,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up System Monitor from a config entry."""
|
"""Set up System Monitor from a config entry."""
|
||||||
psutil_wrapper = await hass.async_add_executor_job(ha_psutil.PsutilWrapper)
|
psutil_wrapper = await hass.async_add_executor_job(ha_psutil.PsutilWrapper)
|
||||||
hass.data[DOMAIN] = psutil_wrapper
|
hass.data[DOMAIN] = psutil_wrapper
|
||||||
|
|
||||||
|
disk_arguments = list(await hass.async_add_executor_job(get_all_disk_mounts, hass))
|
||||||
|
legacy_resources: set[str] = set(entry.options.get("resources", []))
|
||||||
|
for resource in legacy_resources:
|
||||||
|
if resource.startswith("disk_"):
|
||||||
|
split_index = resource.rfind("_")
|
||||||
|
_type = resource[:split_index]
|
||||||
|
argument = resource[split_index + 1 :]
|
||||||
|
_LOGGER.debug("Loading legacy %s with argument %s", _type, argument)
|
||||||
|
disk_arguments.append(argument)
|
||||||
|
|
||||||
|
_LOGGER.debug("disk arguments to be added: %s", disk_arguments)
|
||||||
|
|
||||||
|
coordinator: SystemMonitorCoordinator = SystemMonitorCoordinator(
|
||||||
|
hass, psutil_wrapper, disk_arguments
|
||||||
|
)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
hass.data[DOMAIN_COORDINATOR] = coordinator
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -7,10 +7,9 @@ from dataclasses import dataclass
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from typing import Generic, Literal
|
from typing import Literal
|
||||||
|
|
||||||
from psutil import NoSuchProcess, Process
|
from psutil import NoSuchProcess
|
||||||
import psutil_home_assistant as ha_psutil
|
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||||
|
@ -26,8 +25,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
from .const import CONF_PROCESS, DOMAIN
|
from .const import CONF_PROCESS, DOMAIN, DOMAIN_COORDINATOR
|
||||||
from .coordinator import MonitorCoordinator, SystemMonitorProcessCoordinator, dataT
|
from .coordinator import SystemMonitorCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -51,10 +50,10 @@ def get_cpu_icon() -> Literal["mdi:cpu-64-bit", "mdi:cpu-32-bit"]:
|
||||||
return "mdi:cpu-32-bit"
|
return "mdi:cpu-32-bit"
|
||||||
|
|
||||||
|
|
||||||
def get_process(entity: SystemMonitorSensor[list[Process]]) -> bool:
|
def get_process(entity: SystemMonitorSensor) -> bool:
|
||||||
"""Return process."""
|
"""Return process."""
|
||||||
state = False
|
state = False
|
||||||
for proc in entity.coordinator.data:
|
for proc in entity.coordinator.data.processes:
|
||||||
try:
|
try:
|
||||||
_LOGGER.debug("process %s for argument %s", proc.name(), entity.argument)
|
_LOGGER.debug("process %s for argument %s", proc.name(), entity.argument)
|
||||||
if entity.argument == proc.name():
|
if entity.argument == proc.name():
|
||||||
|
@ -70,21 +69,21 @@ def get_process(entity: SystemMonitorSensor[list[Process]]) -> bool:
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class SysMonitorBinarySensorEntityDescription(
|
class SysMonitorBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||||
BinarySensorEntityDescription, Generic[dataT]
|
|
||||||
):
|
|
||||||
"""Describes System Monitor binary sensor entities."""
|
"""Describes System Monitor binary sensor entities."""
|
||||||
|
|
||||||
value_fn: Callable[[SystemMonitorSensor[dataT]], bool]
|
value_fn: Callable[[SystemMonitorSensor], bool]
|
||||||
|
add_to_update: Callable[[SystemMonitorSensor], tuple[str, str]]
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES: tuple[SysMonitorBinarySensorEntityDescription[list[Process]], ...] = (
|
SENSOR_TYPES: tuple[SysMonitorBinarySensorEntityDescription, ...] = (
|
||||||
SysMonitorBinarySensorEntityDescription[list[Process]](
|
SysMonitorBinarySensorEntityDescription(
|
||||||
key="binary_process",
|
key="binary_process",
|
||||||
translation_key="process",
|
translation_key="process",
|
||||||
icon=get_cpu_icon(),
|
icon=get_cpu_icon(),
|
||||||
value_fn=get_process,
|
value_fn=get_process,
|
||||||
device_class=BinarySensorDeviceClass.RUNNING,
|
device_class=BinarySensorDeviceClass.RUNNING,
|
||||||
|
add_to_update=lambda entity: ("processes", ""),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,20 +92,15 @@ async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up System Montor binary sensors based on a config entry."""
|
"""Set up System Montor binary sensors based on a config entry."""
|
||||||
psutil_wrapper: ha_psutil.PsutilWrapper = hass.data[DOMAIN]
|
|
||||||
|
|
||||||
entities: list[SystemMonitorSensor] = []
|
entities: list[SystemMonitorSensor] = []
|
||||||
process_coordinator = SystemMonitorProcessCoordinator(
|
coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR]
|
||||||
hass, psutil_wrapper, "Process coordinator"
|
|
||||||
)
|
|
||||||
await process_coordinator.async_request_refresh()
|
|
||||||
|
|
||||||
for sensor_description in SENSOR_TYPES:
|
for sensor_description in SENSOR_TYPES:
|
||||||
_entry = entry.options.get(BINARY_SENSOR_DOMAIN, {})
|
_entry = entry.options.get(BINARY_SENSOR_DOMAIN, {})
|
||||||
for argument in _entry.get(CONF_PROCESS, []):
|
for argument in _entry.get(CONF_PROCESS, []):
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
process_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -116,18 +110,18 @@ async def async_setup_entry(
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorSensor(
|
class SystemMonitorSensor(
|
||||||
CoordinatorEntity[MonitorCoordinator[dataT]], BinarySensorEntity
|
CoordinatorEntity[SystemMonitorCoordinator], BinarySensorEntity
|
||||||
):
|
):
|
||||||
"""Implementation of a system monitor binary sensor."""
|
"""Implementation of a system monitor binary sensor."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
entity_description: SysMonitorBinarySensorEntityDescription[dataT]
|
entity_description: SysMonitorBinarySensorEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: MonitorCoordinator[dataT],
|
coordinator: SystemMonitorCoordinator,
|
||||||
sensor_description: SysMonitorBinarySensorEntityDescription[dataT],
|
sensor_description: SysMonitorBinarySensorEntityDescription,
|
||||||
entry_id: str,
|
entry_id: str,
|
||||||
argument: str,
|
argument: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -144,6 +138,20 @@ class SystemMonitorSensor(
|
||||||
)
|
)
|
||||||
self.argument = argument
|
self.argument = argument
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""When added to hass."""
|
||||||
|
self.coordinator.update_subscribers[
|
||||||
|
self.entity_description.add_to_update(self)
|
||||||
|
].add(self.entity_id)
|
||||||
|
return await super().async_added_to_hass()
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
|
"""When removed from hass."""
|
||||||
|
self.coordinator.update_subscribers[
|
||||||
|
self.entity_description.add_to_update(self)
|
||||||
|
].remove(self.entity_id)
|
||||||
|
return await super().async_will_remove_from_hass()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Constants for System Monitor."""
|
"""Constants for System Monitor."""
|
||||||
|
|
||||||
DOMAIN = "systemmonitor"
|
DOMAIN = "systemmonitor"
|
||||||
DOMAIN_COORDINATORS = "systemmonitor_coordinators"
|
DOMAIN_COORDINATOR = "systemmonitor_coordinator"
|
||||||
|
|
||||||
CONF_INDEX = "index"
|
CONF_INDEX = "index"
|
||||||
CONF_PROCESS = "process"
|
CONF_PROCESS = "process"
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import abstractmethod
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import NamedTuple, TypeVar
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
from psutil import Process
|
from psutil import Process
|
||||||
from psutil._common import sdiskusage, shwtemp, snetio, snicaddr, sswap
|
from psutil._common import sdiskusage, shwtemp, snetio, snicaddr, sswap
|
||||||
|
@ -14,15 +14,43 @@ import psutil_home_assistant as ha_psutil
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_component import DEFAULT_SCAN_INTERVAL
|
from homeassistant.helpers.entity_component import DEFAULT_SCAN_INTERVAL
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
|
||||||
TimestampDataUpdateCoordinator,
|
|
||||||
UpdateFailed,
|
|
||||||
)
|
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True, slots=True)
|
||||||
|
class SensorData:
|
||||||
|
"""Sensor data."""
|
||||||
|
|
||||||
|
disk_usage: dict[str, sdiskusage]
|
||||||
|
swap: sswap
|
||||||
|
memory: VirtualMemory
|
||||||
|
io_counters: dict[str, snetio]
|
||||||
|
addresses: dict[str, list[snicaddr]]
|
||||||
|
load: tuple[float, float, float]
|
||||||
|
cpu_percent: float | None
|
||||||
|
boot_time: datetime
|
||||||
|
processes: list[Process]
|
||||||
|
temperatures: dict[str, list[shwtemp]]
|
||||||
|
|
||||||
|
def as_dict(self) -> dict[str, Any]:
|
||||||
|
"""Return as dict."""
|
||||||
|
return {
|
||||||
|
"disk_usage": {k: str(v) for k, v in self.disk_usage.items()},
|
||||||
|
"swap": str(self.swap),
|
||||||
|
"memory": str(self.memory),
|
||||||
|
"io_counters": {k: str(v) for k, v in self.io_counters.items()},
|
||||||
|
"addresses": {k: str(v) for k, v in self.addresses.items()},
|
||||||
|
"load": str(self.load),
|
||||||
|
"cpu_percent": str(self.cpu_percent),
|
||||||
|
"boot_time": str(self.boot_time),
|
||||||
|
"processes": str(self.processes),
|
||||||
|
"temperatures": {k: str(v) for k, v in self.temperatures.items()},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VirtualMemory(NamedTuple):
|
class VirtualMemory(NamedTuple):
|
||||||
"""Represents virtual memory.
|
"""Represents virtual memory.
|
||||||
|
|
||||||
|
@ -37,177 +65,148 @@ class VirtualMemory(NamedTuple):
|
||||||
free: float
|
free: float
|
||||||
|
|
||||||
|
|
||||||
dataT = TypeVar(
|
class SystemMonitorCoordinator(TimestampDataUpdateCoordinator[SensorData]):
|
||||||
"dataT",
|
"""A System monitor Data Update Coordinator."""
|
||||||
bound=datetime
|
|
||||||
| dict[str, list[shwtemp]]
|
|
||||||
| dict[str, list[snicaddr]]
|
|
||||||
| dict[str, snetio]
|
|
||||||
| float
|
|
||||||
| list[Process]
|
|
||||||
| sswap
|
|
||||||
| VirtualMemory
|
|
||||||
| tuple[float, float, float]
|
|
||||||
| sdiskusage
|
|
||||||
| None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MonitorCoordinator(TimestampDataUpdateCoordinator[dataT]):
|
|
||||||
"""A System monitor Base Data Update Coordinator."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, hass: HomeAssistant, psutil_wrapper: ha_psutil.PsutilWrapper, name: str
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the coordinator."""
|
|
||||||
super().__init__(
|
|
||||||
hass,
|
|
||||||
_LOGGER,
|
|
||||||
name=f"System Monitor {name}",
|
|
||||||
update_interval=DEFAULT_SCAN_INTERVAL,
|
|
||||||
always_update=False,
|
|
||||||
)
|
|
||||||
self._psutil = psutil_wrapper.psutil
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> dataT:
|
|
||||||
"""Fetch data."""
|
|
||||||
return await self.hass.async_add_executor_job(self.update_data)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def update_data(self) -> dataT:
|
|
||||||
"""To be extended by data update coordinators."""
|
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorDiskCoordinator(MonitorCoordinator[sdiskusage]):
|
|
||||||
"""A System monitor Disk Data Update Coordinator."""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
psutil_wrapper: ha_psutil.PsutilWrapper,
|
psutil_wrapper: ha_psutil.PsutilWrapper,
|
||||||
name: str,
|
arguments: list[str],
|
||||||
argument: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the disk coordinator."""
|
"""Initialize the coordinator."""
|
||||||
super().__init__(hass, psutil_wrapper, name)
|
super().__init__(
|
||||||
self._argument = argument
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="System Monitor update coordinator",
|
||||||
|
update_interval=DEFAULT_SCAN_INTERVAL,
|
||||||
|
always_update=False,
|
||||||
|
)
|
||||||
|
self._psutil = psutil_wrapper.psutil
|
||||||
|
self._arguments = arguments
|
||||||
|
self.boot_time: datetime | None = None
|
||||||
|
|
||||||
def update_data(self) -> sdiskusage:
|
self._initial_update: bool = True
|
||||||
|
self.update_subscribers: dict[
|
||||||
|
tuple[str, str], set[str]
|
||||||
|
] = self.set_subscribers_tuples(arguments)
|
||||||
|
|
||||||
|
def set_subscribers_tuples(
|
||||||
|
self, arguments: list[str]
|
||||||
|
) -> dict[tuple[str, str], set[str]]:
|
||||||
|
"""Set tuples in subscribers dictionary."""
|
||||||
|
_disk_defaults: dict[tuple[str, str], set[str]] = {}
|
||||||
|
for argument in arguments:
|
||||||
|
_disk_defaults[("disks", argument)] = set()
|
||||||
|
return {
|
||||||
|
**_disk_defaults,
|
||||||
|
("swap", ""): set(),
|
||||||
|
("memory", ""): set(),
|
||||||
|
("io_counters", ""): set(),
|
||||||
|
("addresses", ""): set(),
|
||||||
|
("load", ""): set(),
|
||||||
|
("cpu_percent", ""): set(),
|
||||||
|
("boot", ""): set(),
|
||||||
|
("processes", ""): set(),
|
||||||
|
("temperatures", ""): set(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> SensorData:
|
||||||
"""Fetch data."""
|
"""Fetch data."""
|
||||||
try:
|
_LOGGER.debug("Update list is: %s", self.update_subscribers)
|
||||||
usage: sdiskusage = self._psutil.disk_usage(self._argument)
|
|
||||||
_LOGGER.debug("sdiskusage: %s", usage)
|
|
||||||
return usage
|
|
||||||
except PermissionError as err:
|
|
||||||
raise UpdateFailed(f"No permission to access {self._argument}") from err
|
|
||||||
except OSError as err:
|
|
||||||
raise UpdateFailed(f"OS error for {self._argument}") from err
|
|
||||||
|
|
||||||
|
_data = await self.hass.async_add_executor_job(self.update_data)
|
||||||
|
|
||||||
class SystemMonitorSwapCoordinator(MonitorCoordinator[sswap]):
|
load: tuple = (None, None, None)
|
||||||
"""A System monitor Swap Data Update Coordinator."""
|
if self.update_subscribers[("load", "")] or self._initial_update:
|
||||||
|
load = os.getloadavg()
|
||||||
|
_LOGGER.debug("Load: %s", load)
|
||||||
|
|
||||||
def update_data(self) -> sswap:
|
cpu_percent: float | None = None
|
||||||
"""Fetch data."""
|
if self.update_subscribers[("cpu_percent", "")] or self._initial_update:
|
||||||
swap: sswap = self._psutil.swap_memory()
|
cpu_percent = self._psutil.cpu_percent(interval=None)
|
||||||
_LOGGER.debug("sswap: %s", swap)
|
_LOGGER.debug("cpu_percent: %s", cpu_percent)
|
||||||
return swap
|
|
||||||
|
|
||||||
|
self._initial_update = False
|
||||||
class SystemMonitorMemoryCoordinator(MonitorCoordinator[VirtualMemory]):
|
return SensorData(
|
||||||
"""A System monitor Memory Data Update Coordinator."""
|
disk_usage=_data["disks"],
|
||||||
|
swap=_data["swap"],
|
||||||
def update_data(self) -> VirtualMemory:
|
memory=_data["memory"],
|
||||||
"""Fetch data."""
|
io_counters=_data["io_counters"],
|
||||||
memory = self._psutil.virtual_memory()
|
addresses=_data["addresses"],
|
||||||
_LOGGER.debug("memory: %s", memory)
|
load=load,
|
||||||
return VirtualMemory(
|
cpu_percent=cpu_percent,
|
||||||
memory.total, memory.available, memory.percent, memory.used, memory.free
|
boot_time=_data["boot_time"],
|
||||||
|
processes=_data["processes"],
|
||||||
|
temperatures=_data["temperatures"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_data(self) -> dict[str, Any]:
|
||||||
|
"""To be extended by data update coordinators."""
|
||||||
|
disks: dict[str, sdiskusage] = {}
|
||||||
|
for argument in self._arguments:
|
||||||
|
if self.update_subscribers[("disks", argument)] or self._initial_update:
|
||||||
|
try:
|
||||||
|
usage: sdiskusage = self._psutil.disk_usage(argument)
|
||||||
|
_LOGGER.debug("sdiskusagefor %s: %s", argument, usage)
|
||||||
|
except PermissionError as err:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"No permission to access %s, error %s", argument, err
|
||||||
|
)
|
||||||
|
except OSError as err:
|
||||||
|
_LOGGER.warning("OS error for %s, error %s", argument, err)
|
||||||
|
else:
|
||||||
|
disks[argument] = usage
|
||||||
|
|
||||||
class SystemMonitorNetIOCoordinator(MonitorCoordinator[dict[str, snetio]]):
|
swap: sswap | None = None
|
||||||
"""A System monitor Network IO Data Update Coordinator."""
|
if self.update_subscribers[("swap", "")] or self._initial_update:
|
||||||
|
swap = self._psutil.swap_memory()
|
||||||
|
_LOGGER.debug("sswap: %s", swap)
|
||||||
|
|
||||||
def update_data(self) -> dict[str, snetio]:
|
memory = None
|
||||||
"""Fetch data."""
|
if self.update_subscribers[("memory", "")] or self._initial_update:
|
||||||
io_counters: dict[str, snetio] = self._psutil.net_io_counters(pernic=True)
|
memory = self._psutil.virtual_memory()
|
||||||
_LOGGER.debug("io_counters: %s", io_counters)
|
_LOGGER.debug("memory: %s", memory)
|
||||||
return io_counters
|
memory = VirtualMemory(
|
||||||
|
memory.total, memory.available, memory.percent, memory.used, memory.free
|
||||||
|
)
|
||||||
|
|
||||||
|
io_counters: dict[str, snetio] | None = None
|
||||||
|
if self.update_subscribers[("io_counters", "")] or self._initial_update:
|
||||||
|
io_counters = self._psutil.net_io_counters(pernic=True)
|
||||||
|
_LOGGER.debug("io_counters: %s", io_counters)
|
||||||
|
|
||||||
class SystemMonitorNetAddrCoordinator(MonitorCoordinator[dict[str, list[snicaddr]]]):
|
addresses: dict[str, list[snicaddr]] | None = None
|
||||||
"""A System monitor Network Address Data Update Coordinator."""
|
if self.update_subscribers[("addresses", "")] or self._initial_update:
|
||||||
|
addresses = self._psutil.net_if_addrs()
|
||||||
|
_LOGGER.debug("ip_addresses: %s", addresses)
|
||||||
|
|
||||||
def update_data(self) -> dict[str, list[snicaddr]]:
|
if self._initial_update:
|
||||||
"""Fetch data."""
|
# Boot time only needs to refresh on first pass
|
||||||
addresses: dict[str, list[snicaddr]] = self._psutil.net_if_addrs()
|
self.boot_time = dt_util.utc_from_timestamp(self._psutil.boot_time())
|
||||||
_LOGGER.debug("ip_addresses: %s", addresses)
|
_LOGGER.debug("boot time: %s", self.boot_time)
|
||||||
return addresses
|
|
||||||
|
|
||||||
|
processes = None
|
||||||
|
if self.update_subscribers[("processes", "")] or self._initial_update:
|
||||||
|
processes = self._psutil.process_iter()
|
||||||
|
_LOGGER.debug("processes: %s", processes)
|
||||||
|
processes = list(processes)
|
||||||
|
|
||||||
class SystemMonitorLoadCoordinator(
|
temps: dict[str, list[shwtemp]] = {}
|
||||||
MonitorCoordinator[tuple[float, float, float] | None]
|
if self.update_subscribers[("temperatures", "")] or self._initial_update:
|
||||||
):
|
try:
|
||||||
"""A System monitor Load Data Update Coordinator."""
|
temps = self._psutil.sensors_temperatures()
|
||||||
|
_LOGGER.debug("temps: %s", temps)
|
||||||
|
except AttributeError:
|
||||||
|
_LOGGER.debug("OS does not provide temperature sensors")
|
||||||
|
|
||||||
def update_data(self) -> tuple[float, float, float] | None:
|
return {
|
||||||
"""Coordinator is not async."""
|
"disks": disks,
|
||||||
|
"swap": swap,
|
||||||
async def _async_update_data(self) -> tuple[float, float, float] | None:
|
"memory": memory,
|
||||||
"""Fetch data."""
|
"io_counters": io_counters,
|
||||||
return os.getloadavg()
|
"addresses": addresses,
|
||||||
|
"boot_time": self.boot_time,
|
||||||
|
"processes": processes,
|
||||||
class SystemMonitorProcessorCoordinator(MonitorCoordinator[float | None]):
|
"temperatures": temps,
|
||||||
"""A System monitor Processor Data Update Coordinator."""
|
}
|
||||||
|
|
||||||
def update_data(self) -> float | None:
|
|
||||||
"""Coordinator is not async."""
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> float | None:
|
|
||||||
"""Get cpu usage.
|
|
||||||
|
|
||||||
Unlikely the rest of the coordinators, this one is async
|
|
||||||
since it does not block and we need to make sure it runs
|
|
||||||
in the same thread every time as psutil checks the thread
|
|
||||||
tid and compares it against the previous one.
|
|
||||||
"""
|
|
||||||
cpu_percent: float = self._psutil.cpu_percent(interval=None)
|
|
||||||
_LOGGER.debug("cpu_percent: %s", cpu_percent)
|
|
||||||
if cpu_percent > 0.0:
|
|
||||||
return cpu_percent
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorBootTimeCoordinator(MonitorCoordinator[datetime]):
|
|
||||||
"""A System monitor Processor Data Update Coordinator."""
|
|
||||||
|
|
||||||
def update_data(self) -> datetime:
|
|
||||||
"""Fetch data."""
|
|
||||||
boot_time = dt_util.utc_from_timestamp(self._psutil.boot_time())
|
|
||||||
_LOGGER.debug("boot time: %s", boot_time)
|
|
||||||
return boot_time
|
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorProcessCoordinator(MonitorCoordinator[list[Process]]):
|
|
||||||
"""A System monitor Process Data Update Coordinator."""
|
|
||||||
|
|
||||||
def update_data(self) -> list[Process]:
|
|
||||||
"""Fetch data."""
|
|
||||||
processes = self._psutil.process_iter()
|
|
||||||
_LOGGER.debug("processes: %s", processes)
|
|
||||||
return list(processes)
|
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorCPUtempCoordinator(MonitorCoordinator[dict[str, list[shwtemp]]]):
|
|
||||||
"""A System monitor CPU Temperature Data Update Coordinator."""
|
|
||||||
|
|
||||||
def update_data(self) -> dict[str, list[shwtemp]]:
|
|
||||||
"""Fetch data."""
|
|
||||||
try:
|
|
||||||
temps: dict[str, list[shwtemp]] = self._psutil.sensors_temperatures()
|
|
||||||
_LOGGER.debug("temps: %s", temps)
|
|
||||||
return temps
|
|
||||||
except AttributeError as err:
|
|
||||||
raise UpdateFailed("OS does not provide temperature sensors") from err
|
|
||||||
|
|
|
@ -6,23 +6,21 @@ from typing import Any
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import DOMAIN_COORDINATORS
|
from .const import DOMAIN_COORDINATOR
|
||||||
from .coordinator import MonitorCoordinator
|
from .coordinator import SystemMonitorCoordinator
|
||||||
|
|
||||||
|
|
||||||
async def async_get_config_entry_diagnostics(
|
async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, entry: ConfigEntry
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return diagnostics for Sensibo config entry."""
|
"""Return diagnostics for Sensibo config entry."""
|
||||||
coordinators: dict[str, MonitorCoordinator] = hass.data[DOMAIN_COORDINATORS]
|
coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR]
|
||||||
|
|
||||||
diag_data = {}
|
diag_data = {
|
||||||
for _type, coordinator in coordinators.items():
|
"last_update_success": coordinator.last_update_success,
|
||||||
diag_data[_type] = {
|
"last_update": str(coordinator.last_update_success_time),
|
||||||
"last_update_success": coordinator.last_update_success,
|
"data": coordinator.data.as_dict(),
|
||||||
"last_update": str(coordinator.last_update_success_time),
|
}
|
||||||
"data": str(coordinator.data),
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"entry": entry.as_dict(),
|
"entry": entry.as_dict(),
|
||||||
|
|
|
@ -10,11 +10,9 @@ import logging
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Any, Generic, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from psutil import NoSuchProcess, Process
|
from psutil import NoSuchProcess
|
||||||
from psutil._common import sdiskusage, shwtemp, snetio, snicaddr, sswap
|
|
||||||
import psutil_home_assistant as ha_psutil
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
|
@ -47,22 +45,8 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateTyp
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
from .const import CONF_PROCESS, DOMAIN, DOMAIN_COORDINATORS, NET_IO_TYPES
|
from .const import CONF_PROCESS, DOMAIN, DOMAIN_COORDINATOR, NET_IO_TYPES
|
||||||
from .coordinator import (
|
from .coordinator import SystemMonitorCoordinator
|
||||||
MonitorCoordinator,
|
|
||||||
SystemMonitorBootTimeCoordinator,
|
|
||||||
SystemMonitorCPUtempCoordinator,
|
|
||||||
SystemMonitorDiskCoordinator,
|
|
||||||
SystemMonitorLoadCoordinator,
|
|
||||||
SystemMonitorMemoryCoordinator,
|
|
||||||
SystemMonitorNetAddrCoordinator,
|
|
||||||
SystemMonitorNetIOCoordinator,
|
|
||||||
SystemMonitorProcessCoordinator,
|
|
||||||
SystemMonitorProcessorCoordinator,
|
|
||||||
SystemMonitorSwapCoordinator,
|
|
||||||
VirtualMemory,
|
|
||||||
dataT,
|
|
||||||
)
|
|
||||||
from .util import get_all_disk_mounts, get_all_network_interfaces, read_cpu_temperature
|
from .util import get_all_disk_mounts, get_all_network_interfaces, read_cpu_temperature
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -88,16 +72,16 @@ def get_cpu_icon() -> Literal["mdi:cpu-64-bit", "mdi:cpu-32-bit"]:
|
||||||
|
|
||||||
|
|
||||||
def get_processor_temperature(
|
def get_processor_temperature(
|
||||||
entity: SystemMonitorSensor[dict[str, list[shwtemp]]],
|
entity: SystemMonitorSensor,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
"""Return processor temperature."""
|
"""Return processor temperature."""
|
||||||
return read_cpu_temperature(entity.hass, entity.coordinator.data)
|
return read_cpu_temperature(entity.hass, entity.coordinator.data.temperatures)
|
||||||
|
|
||||||
|
|
||||||
def get_process(entity: SystemMonitorSensor[list[Process]]) -> str:
|
def get_process(entity: SystemMonitorSensor) -> str:
|
||||||
"""Return process."""
|
"""Return process."""
|
||||||
state = STATE_OFF
|
state = STATE_OFF
|
||||||
for proc in entity.coordinator.data:
|
for proc in entity.coordinator.data.processes:
|
||||||
try:
|
try:
|
||||||
_LOGGER.debug("process %s for argument %s", proc.name(), entity.argument)
|
_LOGGER.debug("process %s for argument %s", proc.name(), entity.argument)
|
||||||
if entity.argument == proc.name():
|
if entity.argument == proc.name():
|
||||||
|
@ -112,26 +96,26 @@ def get_process(entity: SystemMonitorSensor[list[Process]]) -> str:
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
def get_network(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | None:
|
def get_network(entity: SystemMonitorSensor) -> float | None:
|
||||||
"""Return network in and out."""
|
"""Return network in and out."""
|
||||||
counters = entity.coordinator.data
|
counters = entity.coordinator.data.io_counters
|
||||||
if entity.argument in counters:
|
if entity.argument in counters:
|
||||||
counter = counters[entity.argument][IO_COUNTER[entity.entity_description.key]]
|
counter = counters[entity.argument][IO_COUNTER[entity.entity_description.key]]
|
||||||
return round(counter / 1024**2, 1)
|
return round(counter / 1024**2, 1)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_packets(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | None:
|
def get_packets(entity: SystemMonitorSensor) -> float | None:
|
||||||
"""Return packets in and out."""
|
"""Return packets in and out."""
|
||||||
counters = entity.coordinator.data
|
counters = entity.coordinator.data.io_counters
|
||||||
if entity.argument in counters:
|
if entity.argument in counters:
|
||||||
return counters[entity.argument][IO_COUNTER[entity.entity_description.key]]
|
return counters[entity.argument][IO_COUNTER[entity.entity_description.key]]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_throughput(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | None:
|
def get_throughput(entity: SystemMonitorSensor) -> float | None:
|
||||||
"""Return network throughput in and out."""
|
"""Return network throughput in and out."""
|
||||||
counters = entity.coordinator.data
|
counters = entity.coordinator.data.io_counters
|
||||||
state = None
|
state = None
|
||||||
if entity.argument in counters:
|
if entity.argument in counters:
|
||||||
counter = counters[entity.argument][IO_COUNTER[entity.entity_description.key]]
|
counter = counters[entity.argument][IO_COUNTER[entity.entity_description.key]]
|
||||||
|
@ -151,10 +135,10 @@ def get_throughput(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | No
|
||||||
|
|
||||||
|
|
||||||
def get_ip_address(
|
def get_ip_address(
|
||||||
entity: SystemMonitorSensor[dict[str, list[snicaddr]]],
|
entity: SystemMonitorSensor,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""Return network ip address."""
|
"""Return network ip address."""
|
||||||
addresses = entity.coordinator.data
|
addresses = entity.coordinator.data.addresses
|
||||||
if entity.argument in addresses:
|
if entity.argument in addresses:
|
||||||
for addr in addresses[entity.argument]:
|
for addr in addresses[entity.argument]:
|
||||||
if addr.family == IF_ADDRS_FAMILY[entity.entity_description.key]:
|
if addr.family == IF_ADDRS_FAMILY[entity.entity_description.key]:
|
||||||
|
@ -163,16 +147,18 @@ def get_ip_address(
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class SysMonitorSensorEntityDescription(SensorEntityDescription, Generic[dataT]):
|
class SysMonitorSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Describes System Monitor sensor entities."""
|
"""Describes System Monitor sensor entities."""
|
||||||
|
|
||||||
value_fn: Callable[[SystemMonitorSensor[dataT]], StateType | datetime]
|
value_fn: Callable[[SystemMonitorSensor], StateType | datetime]
|
||||||
|
add_to_update: Callable[[SystemMonitorSensor], tuple[str, str]]
|
||||||
|
none_is_unavailable: bool = False
|
||||||
mandatory_arg: bool = False
|
mandatory_arg: bool = False
|
||||||
placeholder: str | None = None
|
placeholder: str | None = None
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
||||||
"disk_free": SysMonitorSensorEntityDescription[sdiskusage](
|
"disk_free": SysMonitorSensorEntityDescription(
|
||||||
key="disk_free",
|
key="disk_free",
|
||||||
translation_key="disk_free",
|
translation_key="disk_free",
|
||||||
placeholder="mount_point",
|
placeholder="mount_point",
|
||||||
|
@ -180,9 +166,15 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
icon="mdi:harddisk",
|
icon="mdi:harddisk",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data.free / 1024**3, 1),
|
value_fn=lambda entity: round(
|
||||||
|
entity.coordinator.data.disk_usage[entity.argument].free / 1024**3, 1
|
||||||
|
)
|
||||||
|
if entity.argument in entity.coordinator.data.disk_usage
|
||||||
|
else None,
|
||||||
|
none_is_unavailable=True,
|
||||||
|
add_to_update=lambda entity: ("disks", entity.argument),
|
||||||
),
|
),
|
||||||
"disk_use": SysMonitorSensorEntityDescription[sdiskusage](
|
"disk_use": SysMonitorSensorEntityDescription(
|
||||||
key="disk_use",
|
key="disk_use",
|
||||||
translation_key="disk_use",
|
translation_key="disk_use",
|
||||||
placeholder="mount_point",
|
placeholder="mount_point",
|
||||||
|
@ -190,70 +182,91 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
icon="mdi:harddisk",
|
icon="mdi:harddisk",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data.used / 1024**3, 1),
|
value_fn=lambda entity: round(
|
||||||
|
entity.coordinator.data.disk_usage[entity.argument].used / 1024**3, 1
|
||||||
|
)
|
||||||
|
if entity.argument in entity.coordinator.data.disk_usage
|
||||||
|
else None,
|
||||||
|
none_is_unavailable=True,
|
||||||
|
add_to_update=lambda entity: ("disks", entity.argument),
|
||||||
),
|
),
|
||||||
"disk_use_percent": SysMonitorSensorEntityDescription[sdiskusage](
|
"disk_use_percent": SysMonitorSensorEntityDescription(
|
||||||
key="disk_use_percent",
|
key="disk_use_percent",
|
||||||
translation_key="disk_use_percent",
|
translation_key="disk_use_percent",
|
||||||
placeholder="mount_point",
|
placeholder="mount_point",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:harddisk",
|
icon="mdi:harddisk",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: entity.coordinator.data.percent,
|
value_fn=lambda entity: entity.coordinator.data.disk_usage[
|
||||||
|
entity.argument
|
||||||
|
].percent
|
||||||
|
if entity.argument in entity.coordinator.data.disk_usage
|
||||||
|
else None,
|
||||||
|
none_is_unavailable=True,
|
||||||
|
add_to_update=lambda entity: ("disks", entity.argument),
|
||||||
),
|
),
|
||||||
"ipv4_address": SysMonitorSensorEntityDescription[dict[str, list[snicaddr]]](
|
"ipv4_address": SysMonitorSensorEntityDescription(
|
||||||
key="ipv4_address",
|
key="ipv4_address",
|
||||||
translation_key="ipv4_address",
|
translation_key="ipv4_address",
|
||||||
placeholder="ip_address",
|
placeholder="ip_address",
|
||||||
icon="mdi:ip-network",
|
icon="mdi:ip-network",
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_ip_address,
|
value_fn=get_ip_address,
|
||||||
|
add_to_update=lambda entity: ("addresses", ""),
|
||||||
),
|
),
|
||||||
"ipv6_address": SysMonitorSensorEntityDescription[dict[str, list[snicaddr]]](
|
"ipv6_address": SysMonitorSensorEntityDescription(
|
||||||
key="ipv6_address",
|
key="ipv6_address",
|
||||||
translation_key="ipv6_address",
|
translation_key="ipv6_address",
|
||||||
placeholder="ip_address",
|
placeholder="ip_address",
|
||||||
icon="mdi:ip-network",
|
icon="mdi:ip-network",
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_ip_address,
|
value_fn=get_ip_address,
|
||||||
|
add_to_update=lambda entity: ("addresses", ""),
|
||||||
),
|
),
|
||||||
"last_boot": SysMonitorSensorEntityDescription[datetime](
|
"last_boot": SysMonitorSensorEntityDescription(
|
||||||
key="last_boot",
|
key="last_boot",
|
||||||
translation_key="last_boot",
|
translation_key="last_boot",
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
value_fn=lambda entity: entity.coordinator.data,
|
value_fn=lambda entity: entity.coordinator.data.boot_time,
|
||||||
|
add_to_update=lambda entity: ("boot", ""),
|
||||||
),
|
),
|
||||||
"load_15m": SysMonitorSensorEntityDescription[tuple[float, float, float]](
|
"load_15m": SysMonitorSensorEntityDescription(
|
||||||
key="load_15m",
|
key="load_15m",
|
||||||
translation_key="load_15m",
|
translation_key="load_15m",
|
||||||
icon=get_cpu_icon(),
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data[2], 2),
|
value_fn=lambda entity: round(entity.coordinator.data.load[2], 2),
|
||||||
|
add_to_update=lambda entity: ("load", ""),
|
||||||
),
|
),
|
||||||
"load_1m": SysMonitorSensorEntityDescription[tuple[float, float, float]](
|
"load_1m": SysMonitorSensorEntityDescription(
|
||||||
key="load_1m",
|
key="load_1m",
|
||||||
translation_key="load_1m",
|
translation_key="load_1m",
|
||||||
icon=get_cpu_icon(),
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data[0], 2),
|
value_fn=lambda entity: round(entity.coordinator.data.load[0], 2),
|
||||||
|
add_to_update=lambda entity: ("load", ""),
|
||||||
),
|
),
|
||||||
"load_5m": SysMonitorSensorEntityDescription[tuple[float, float, float]](
|
"load_5m": SysMonitorSensorEntityDescription(
|
||||||
key="load_5m",
|
key="load_5m",
|
||||||
translation_key="load_5m",
|
translation_key="load_5m",
|
||||||
icon=get_cpu_icon(),
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data[1], 2),
|
value_fn=lambda entity: round(entity.coordinator.data.load[1], 2),
|
||||||
|
add_to_update=lambda entity: ("load", ""),
|
||||||
),
|
),
|
||||||
"memory_free": SysMonitorSensorEntityDescription[VirtualMemory](
|
"memory_free": SysMonitorSensorEntityDescription(
|
||||||
key="memory_free",
|
key="memory_free",
|
||||||
translation_key="memory_free",
|
translation_key="memory_free",
|
||||||
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
icon="mdi:memory",
|
icon="mdi:memory",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data.available / 1024**2, 1),
|
value_fn=lambda entity: round(
|
||||||
|
entity.coordinator.data.memory.available / 1024**2, 1
|
||||||
|
),
|
||||||
|
add_to_update=lambda entity: ("memory", ""),
|
||||||
),
|
),
|
||||||
"memory_use": SysMonitorSensorEntityDescription[VirtualMemory](
|
"memory_use": SysMonitorSensorEntityDescription(
|
||||||
key="memory_use",
|
key="memory_use",
|
||||||
translation_key="memory_use",
|
translation_key="memory_use",
|
||||||
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
||||||
|
@ -261,20 +274,25 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
icon="mdi:memory",
|
icon="mdi:memory",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(
|
value_fn=lambda entity: round(
|
||||||
(entity.coordinator.data.total - entity.coordinator.data.available)
|
(
|
||||||
|
entity.coordinator.data.memory.total
|
||||||
|
- entity.coordinator.data.memory.available
|
||||||
|
)
|
||||||
/ 1024**2,
|
/ 1024**2,
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
|
add_to_update=lambda entity: ("memory", ""),
|
||||||
),
|
),
|
||||||
"memory_use_percent": SysMonitorSensorEntityDescription[VirtualMemory](
|
"memory_use_percent": SysMonitorSensorEntityDescription(
|
||||||
key="memory_use_percent",
|
key="memory_use_percent",
|
||||||
translation_key="memory_use_percent",
|
translation_key="memory_use_percent",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:memory",
|
icon="mdi:memory",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: entity.coordinator.data.percent,
|
value_fn=lambda entity: entity.coordinator.data.memory.percent,
|
||||||
|
add_to_update=lambda entity: ("memory", ""),
|
||||||
),
|
),
|
||||||
"network_in": SysMonitorSensorEntityDescription[dict[str, snetio]](
|
"network_in": SysMonitorSensorEntityDescription(
|
||||||
key="network_in",
|
key="network_in",
|
||||||
translation_key="network_in",
|
translation_key="network_in",
|
||||||
placeholder="interface",
|
placeholder="interface",
|
||||||
|
@ -284,8 +302,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_network,
|
value_fn=get_network,
|
||||||
|
add_to_update=lambda entity: ("io_counters", ""),
|
||||||
),
|
),
|
||||||
"network_out": SysMonitorSensorEntityDescription[dict[str, snetio]](
|
"network_out": SysMonitorSensorEntityDescription(
|
||||||
key="network_out",
|
key="network_out",
|
||||||
translation_key="network_out",
|
translation_key="network_out",
|
||||||
placeholder="interface",
|
placeholder="interface",
|
||||||
|
@ -295,8 +314,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_network,
|
value_fn=get_network,
|
||||||
|
add_to_update=lambda entity: ("io_counters", ""),
|
||||||
),
|
),
|
||||||
"packets_in": SysMonitorSensorEntityDescription[dict[str, snetio]](
|
"packets_in": SysMonitorSensorEntityDescription(
|
||||||
key="packets_in",
|
key="packets_in",
|
||||||
translation_key="packets_in",
|
translation_key="packets_in",
|
||||||
placeholder="interface",
|
placeholder="interface",
|
||||||
|
@ -304,8 +324,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_packets,
|
value_fn=get_packets,
|
||||||
|
add_to_update=lambda entity: ("io_counters", ""),
|
||||||
),
|
),
|
||||||
"packets_out": SysMonitorSensorEntityDescription[dict[str, snetio]](
|
"packets_out": SysMonitorSensorEntityDescription(
|
||||||
key="packets_out",
|
key="packets_out",
|
||||||
translation_key="packets_out",
|
translation_key="packets_out",
|
||||||
placeholder="interface",
|
placeholder="interface",
|
||||||
|
@ -313,8 +334,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_packets,
|
value_fn=get_packets,
|
||||||
|
add_to_update=lambda entity: ("io_counters", ""),
|
||||||
),
|
),
|
||||||
"throughput_network_in": SysMonitorSensorEntityDescription[dict[str, snetio]](
|
"throughput_network_in": SysMonitorSensorEntityDescription(
|
||||||
key="throughput_network_in",
|
key="throughput_network_in",
|
||||||
translation_key="throughput_network_in",
|
translation_key="throughput_network_in",
|
||||||
placeholder="interface",
|
placeholder="interface",
|
||||||
|
@ -323,8 +345,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_throughput,
|
value_fn=get_throughput,
|
||||||
|
add_to_update=lambda entity: ("io_counters", ""),
|
||||||
),
|
),
|
||||||
"throughput_network_out": SysMonitorSensorEntityDescription[dict[str, snetio]](
|
"throughput_network_out": SysMonitorSensorEntityDescription(
|
||||||
key="throughput_network_out",
|
key="throughput_network_out",
|
||||||
translation_key="throughput_network_out",
|
translation_key="throughput_network_out",
|
||||||
placeholder="interface",
|
placeholder="interface",
|
||||||
|
@ -333,60 +356,68 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = {
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_throughput,
|
value_fn=get_throughput,
|
||||||
|
add_to_update=lambda entity: ("io_counters", ""),
|
||||||
),
|
),
|
||||||
"process": SysMonitorSensorEntityDescription[list[Process]](
|
"process": SysMonitorSensorEntityDescription(
|
||||||
key="process",
|
key="process",
|
||||||
translation_key="process",
|
translation_key="process",
|
||||||
placeholder="process",
|
placeholder="process",
|
||||||
icon=get_cpu_icon(),
|
icon=get_cpu_icon(),
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
value_fn=get_process,
|
value_fn=get_process,
|
||||||
|
add_to_update=lambda entity: ("processes", ""),
|
||||||
),
|
),
|
||||||
"processor_use": SysMonitorSensorEntityDescription[float](
|
"processor_use": SysMonitorSensorEntityDescription(
|
||||||
key="processor_use",
|
key="processor_use",
|
||||||
translation_key="processor_use",
|
translation_key="processor_use",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon=get_cpu_icon(),
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: (
|
value_fn=lambda entity: (
|
||||||
round(entity.coordinator.data) if entity.coordinator.data else None
|
round(entity.coordinator.data.cpu_percent)
|
||||||
|
if entity.coordinator.data.cpu_percent
|
||||||
|
else None
|
||||||
),
|
),
|
||||||
|
add_to_update=lambda entity: ("cpu_percent", ""),
|
||||||
),
|
),
|
||||||
"processor_temperature": SysMonitorSensorEntityDescription[
|
"processor_temperature": SysMonitorSensorEntityDescription(
|
||||||
dict[str, list[shwtemp]]
|
|
||||||
](
|
|
||||||
key="processor_temperature",
|
key="processor_temperature",
|
||||||
translation_key="processor_temperature",
|
translation_key="processor_temperature",
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=get_processor_temperature,
|
value_fn=get_processor_temperature,
|
||||||
|
none_is_unavailable=True,
|
||||||
|
add_to_update=lambda entity: ("temperatures", ""),
|
||||||
),
|
),
|
||||||
"swap_free": SysMonitorSensorEntityDescription[sswap](
|
"swap_free": SysMonitorSensorEntityDescription(
|
||||||
key="swap_free",
|
key="swap_free",
|
||||||
translation_key="swap_free",
|
translation_key="swap_free",
|
||||||
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
icon="mdi:harddisk",
|
icon="mdi:harddisk",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data.free / 1024**2, 1),
|
value_fn=lambda entity: round(entity.coordinator.data.swap.free / 1024**2, 1),
|
||||||
|
add_to_update=lambda entity: ("swap", ""),
|
||||||
),
|
),
|
||||||
"swap_use": SysMonitorSensorEntityDescription[sswap](
|
"swap_use": SysMonitorSensorEntityDescription(
|
||||||
key="swap_use",
|
key="swap_use",
|
||||||
translation_key="swap_use",
|
translation_key="swap_use",
|
||||||
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
icon="mdi:harddisk",
|
icon="mdi:harddisk",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: round(entity.coordinator.data.used / 1024**2, 1),
|
value_fn=lambda entity: round(entity.coordinator.data.swap.used / 1024**2, 1),
|
||||||
|
add_to_update=lambda entity: ("swap", ""),
|
||||||
),
|
),
|
||||||
"swap_use_percent": SysMonitorSensorEntityDescription[sswap](
|
"swap_use_percent": SysMonitorSensorEntityDescription(
|
||||||
key="swap_use_percent",
|
key="swap_use_percent",
|
||||||
translation_key="swap_use_percent",
|
translation_key="swap_use_percent",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:harddisk",
|
icon="mdi:harddisk",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda entity: entity.coordinator.data.percent,
|
value_fn=lambda entity: entity.coordinator.data.swap.percent,
|
||||||
|
add_to_update=lambda entity: ("swap", ""),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,7 +520,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
entities: list[SystemMonitorSensor] = []
|
entities: list[SystemMonitorSensor] = []
|
||||||
legacy_resources: set[str] = set(entry.options.get("resources", []))
|
legacy_resources: set[str] = set(entry.options.get("resources", []))
|
||||||
loaded_resources: set[str] = set()
|
loaded_resources: set[str] = set()
|
||||||
psutil_wrapper: ha_psutil.PsutilWrapper = hass.data[DOMAIN]
|
coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR]
|
||||||
|
|
||||||
def get_arguments() -> dict[str, Any]:
|
def get_arguments() -> dict[str, Any]:
|
||||||
"""Return startup information."""
|
"""Return startup information."""
|
||||||
|
@ -507,44 +538,6 @@ async def async_setup_entry( # noqa: C901
|
||||||
|
|
||||||
startup_arguments = await hass.async_add_executor_job(get_arguments)
|
startup_arguments = await hass.async_add_executor_job(get_arguments)
|
||||||
|
|
||||||
disk_coordinators: dict[str, SystemMonitorDiskCoordinator] = {}
|
|
||||||
for argument in startup_arguments["disk_arguments"]:
|
|
||||||
disk_coordinators[argument] = SystemMonitorDiskCoordinator(
|
|
||||||
hass, psutil_wrapper, f"Disk {argument} coordinator", argument
|
|
||||||
)
|
|
||||||
swap_coordinator = SystemMonitorSwapCoordinator(
|
|
||||||
hass, psutil_wrapper, "Swap coordinator"
|
|
||||||
)
|
|
||||||
memory_coordinator = SystemMonitorMemoryCoordinator(
|
|
||||||
hass, psutil_wrapper, "Memory coordinator"
|
|
||||||
)
|
|
||||||
net_io_coordinator = SystemMonitorNetIOCoordinator(
|
|
||||||
hass, psutil_wrapper, "Net IO coordnator"
|
|
||||||
)
|
|
||||||
net_addr_coordinator = SystemMonitorNetAddrCoordinator(
|
|
||||||
hass, psutil_wrapper, "Net address coordinator"
|
|
||||||
)
|
|
||||||
system_load_coordinator = SystemMonitorLoadCoordinator(
|
|
||||||
hass, psutil_wrapper, "System load coordinator"
|
|
||||||
)
|
|
||||||
processor_coordinator = SystemMonitorProcessorCoordinator(
|
|
||||||
hass, psutil_wrapper, "Processor coordinator"
|
|
||||||
)
|
|
||||||
boot_time_coordinator = SystemMonitorBootTimeCoordinator(
|
|
||||||
hass, psutil_wrapper, "Boot time coordinator"
|
|
||||||
)
|
|
||||||
process_coordinator = SystemMonitorProcessCoordinator(
|
|
||||||
hass, psutil_wrapper, "Process coordinator"
|
|
||||||
)
|
|
||||||
cpu_temp_coordinator = SystemMonitorCPUtempCoordinator(
|
|
||||||
hass, psutil_wrapper, "CPU temperature coordinator"
|
|
||||||
)
|
|
||||||
|
|
||||||
for argument in startup_arguments["disk_arguments"]:
|
|
||||||
disk_coordinators[argument] = SystemMonitorDiskCoordinator(
|
|
||||||
hass, psutil_wrapper, f"Disk {argument} coordinator", argument
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER.debug("Setup from options %s", entry.options)
|
_LOGGER.debug("Setup from options %s", entry.options)
|
||||||
|
|
||||||
for _type, sensor_description in SENSOR_TYPES.items():
|
for _type, sensor_description in SENSOR_TYPES.items():
|
||||||
|
@ -556,7 +549,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
disk_coordinators[argument],
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -573,7 +566,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
net_addr_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -588,7 +581,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
boot_time_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -603,7 +596,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
system_load_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -618,7 +611,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
memory_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -634,7 +627,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
net_io_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -649,7 +642,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
process_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -678,7 +671,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
processor_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -696,7 +689,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
cpu_temp_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -711,7 +704,7 @@ async def async_setup_entry( # noqa: C901
|
||||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
swap_coordinator,
|
coordinator,
|
||||||
sensor_description,
|
sensor_description,
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -735,13 +728,9 @@ async def async_setup_entry( # noqa: C901
|
||||||
_type = resource[:split_index]
|
_type = resource[:split_index]
|
||||||
argument = resource[split_index + 1 :]
|
argument = resource[split_index + 1 :]
|
||||||
_LOGGER.debug("Loading legacy %s with argument %s", _type, argument)
|
_LOGGER.debug("Loading legacy %s with argument %s", _type, argument)
|
||||||
if not disk_coordinators.get(argument):
|
|
||||||
disk_coordinators[argument] = SystemMonitorDiskCoordinator(
|
|
||||||
hass, psutil_wrapper, f"Disk {argument} coordinator", argument
|
|
||||||
)
|
|
||||||
entities.append(
|
entities.append(
|
||||||
SystemMonitorSensor(
|
SystemMonitorSensor(
|
||||||
disk_coordinators[argument],
|
coordinator,
|
||||||
SENSOR_TYPES[_type],
|
SENSOR_TYPES[_type],
|
||||||
entry.entry_id,
|
entry.entry_id,
|
||||||
argument,
|
argument,
|
||||||
|
@ -749,23 +738,6 @@ async def async_setup_entry( # noqa: C901
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.data[DOMAIN_COORDINATORS] = {}
|
|
||||||
# No gathering to avoid swamping the executor
|
|
||||||
for argument, coordinator in disk_coordinators.items():
|
|
||||||
hass.data[DOMAIN_COORDINATORS][f"disk_{argument}"] = coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["boot_time"] = boot_time_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["cpu_temp"] = cpu_temp_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["memory"] = memory_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["net_addr"] = net_addr_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["net_io"] = net_io_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["process"] = process_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["processor"] = processor_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["swap"] = swap_coordinator
|
|
||||||
hass.data[DOMAIN_COORDINATORS]["system_load"] = system_load_coordinator
|
|
||||||
|
|
||||||
for coordinator in hass.data[DOMAIN_COORDINATORS].values():
|
|
||||||
await coordinator.async_request_refresh()
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def clean_obsolete_entities() -> None:
|
def clean_obsolete_entities() -> None:
|
||||||
"""Remove entities which are disabled and not supported from setup."""
|
"""Remove entities which are disabled and not supported from setup."""
|
||||||
|
@ -790,17 +762,18 @@ async def async_setup_entry( # noqa: C901
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class SystemMonitorSensor(CoordinatorEntity[MonitorCoordinator[dataT]], SensorEntity):
|
class SystemMonitorSensor(CoordinatorEntity[SystemMonitorCoordinator], SensorEntity):
|
||||||
"""Implementation of a system monitor sensor."""
|
"""Implementation of a system monitor sensor."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
entity_description: SysMonitorSensorEntityDescription[dataT]
|
entity_description: SysMonitorSensorEntityDescription
|
||||||
|
argument: str
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: MonitorCoordinator[dataT],
|
coordinator: SystemMonitorCoordinator,
|
||||||
sensor_description: SysMonitorSensorEntityDescription[dataT],
|
sensor_description: SysMonitorSensorEntityDescription,
|
||||||
entry_id: str,
|
entry_id: str,
|
||||||
argument: str,
|
argument: str,
|
||||||
legacy_enabled: bool = False,
|
legacy_enabled: bool = False,
|
||||||
|
@ -824,7 +797,31 @@ class SystemMonitorSensor(CoordinatorEntity[MonitorCoordinator[dataT]], SensorEn
|
||||||
self.value: int | None = None
|
self.value: int | None = None
|
||||||
self.update_time: float | None = None
|
self.update_time: float | None = None
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""When added to hass."""
|
||||||
|
self.coordinator.update_subscribers[
|
||||||
|
self.entity_description.add_to_update(self)
|
||||||
|
].add(self.entity_id)
|
||||||
|
return await super().async_added_to_hass()
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
|
"""When removed from hass."""
|
||||||
|
self.coordinator.update_subscribers[
|
||||||
|
self.entity_description.add_to_update(self)
|
||||||
|
].remove(self.entity_id)
|
||||||
|
return await super().async_will_remove_from_hass()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType | datetime:
|
def native_value(self) -> StateType | datetime:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
return self.entity_description.value_fn(self)
|
return self.entity_description.value_fn(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return if entity is available."""
|
||||||
|
if self.entity_description.none_is_unavailable:
|
||||||
|
return bool(
|
||||||
|
self.coordinator.last_update_success is True
|
||||||
|
and self.native_value is not None
|
||||||
|
)
|
||||||
|
return super().available
|
||||||
|
|
|
@ -2,54 +2,33 @@
|
||||||
# name: test_diagnostics
|
# name: test_diagnostics
|
||||||
dict({
|
dict({
|
||||||
'coordinators': dict({
|
'coordinators': dict({
|
||||||
'boot_time': dict({
|
'data': dict({
|
||||||
'data': '2024-02-24 15:00:00+00:00',
|
'addresses': dict({
|
||||||
'last_update_success': True,
|
'eth0': "[snicaddr(family=<AddressFamily.AF_INET: 2>, address='192.168.1.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]",
|
||||||
}),
|
'eth1': "[snicaddr(family=<AddressFamily.AF_INET: 2>, address='192.168.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]",
|
||||||
'cpu_temp': dict({
|
'vethxyzxyz': "[snicaddr(family=<AddressFamily.AF_INET: 2>, address='172.16.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]",
|
||||||
'data': "{'cpu0-thermal': [shwtemp(label='cpu0-thermal', current=50.0, high=60.0, critical=70.0)]}",
|
}),
|
||||||
'last_update_success': True,
|
'boot_time': '2024-02-24 15:00:00+00:00',
|
||||||
}),
|
'cpu_percent': '10.0',
|
||||||
'disk_/': dict({
|
'disk_usage': dict({
|
||||||
'data': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
'/': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
||||||
'last_update_success': True,
|
'/home/notexist/': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
||||||
}),
|
'/media/share': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
||||||
'disk_/home/notexist/': dict({
|
}),
|
||||||
'data': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
'io_counters': dict({
|
||||||
'last_update_success': True,
|
'eth0': 'snetio(bytes_sent=104857600, bytes_recv=104857600, packets_sent=50, packets_recv=50, errin=0, errout=0, dropin=0, dropout=0)',
|
||||||
}),
|
'eth1': 'snetio(bytes_sent=209715200, bytes_recv=209715200, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0)',
|
||||||
'disk_/media/share': dict({
|
'vethxyzxyz': 'snetio(bytes_sent=314572800, bytes_recv=314572800, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0)',
|
||||||
'data': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
}),
|
||||||
'last_update_success': True,
|
'load': '(1, 2, 3)',
|
||||||
}),
|
'memory': 'VirtualMemory(total=104857600, available=41943040, percent=40.0, used=62914560, free=31457280)',
|
||||||
'memory': dict({
|
'processes': "[tests.components.systemmonitor.conftest.MockProcess(pid=1, name='python3', status='sleeping', started='2024-02-23 15:00:00'), tests.components.systemmonitor.conftest.MockProcess(pid=1, name='pip', status='sleeping', started='2024-02-23 15:00:00')]",
|
||||||
'data': 'VirtualMemory(total=104857600, available=41943040, percent=40.0, used=62914560, free=31457280)',
|
'swap': 'sswap(total=104857600, used=62914560, free=41943040, percent=60.0, sin=1, sout=1)',
|
||||||
'last_update_success': True,
|
'temperatures': dict({
|
||||||
}),
|
'cpu0-thermal': "[shwtemp(label='cpu0-thermal', current=50.0, high=60.0, critical=70.0)]",
|
||||||
'net_addr': dict({
|
}),
|
||||||
'data': "{'eth0': [snicaddr(family=<AddressFamily.AF_INET: 2>, address='192.168.1.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)], 'eth1': [snicaddr(family=<AddressFamily.AF_INET: 2>, address='192.168.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)], 'vethxyzxyz': [snicaddr(family=<AddressFamily.AF_INET: 2>, address='172.16.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]}",
|
|
||||||
'last_update_success': True,
|
|
||||||
}),
|
|
||||||
'net_io': dict({
|
|
||||||
'data': "{'eth0': snetio(bytes_sent=104857600, bytes_recv=104857600, packets_sent=50, packets_recv=50, errin=0, errout=0, dropin=0, dropout=0), 'eth1': snetio(bytes_sent=209715200, bytes_recv=209715200, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0), 'vethxyzxyz': snetio(bytes_sent=314572800, bytes_recv=314572800, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0)}",
|
|
||||||
'last_update_success': True,
|
|
||||||
}),
|
|
||||||
'process': dict({
|
|
||||||
'data': "[tests.components.systemmonitor.conftest.MockProcess(pid=1, name='python3', status='sleeping', started='2024-02-23 15:00:00'), tests.components.systemmonitor.conftest.MockProcess(pid=1, name='pip', status='sleeping', started='2024-02-23 15:00:00')]",
|
|
||||||
'last_update_success': True,
|
|
||||||
}),
|
|
||||||
'processor': dict({
|
|
||||||
'data': '10.0',
|
|
||||||
'last_update_success': True,
|
|
||||||
}),
|
|
||||||
'swap': dict({
|
|
||||||
'data': 'sswap(total=104857600, used=62914560, free=41943040, percent=60.0, sin=1, sout=1)',
|
|
||||||
'last_update_success': True,
|
|
||||||
}),
|
|
||||||
'system_load': dict({
|
|
||||||
'data': '(1, 2, 3)',
|
|
||||||
'last_update_success': True,
|
|
||||||
}),
|
}),
|
||||||
|
'last_update_success': True,
|
||||||
}),
|
}),
|
||||||
'entry': dict({
|
'entry': dict({
|
||||||
'data': dict({
|
'data': dict({
|
||||||
|
|
|
@ -473,10 +473,7 @@ async def test_exception_handling_disk_sensor(
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert (
|
assert "OS error for /" in caplog.text
|
||||||
"Error fetching System Monitor Disk / coordinator data: OS error for /"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
disk_sensor = hass.states.get("sensor.system_monitor_disk_free")
|
disk_sensor = hass.states.get("sensor.system_monitor_disk_free")
|
||||||
assert disk_sensor is not None
|
assert disk_sensor is not None
|
||||||
|
@ -489,10 +486,7 @@ async def test_exception_handling_disk_sensor(
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert (
|
assert "OS error for /" in caplog.text
|
||||||
"Error fetching System Monitor Disk / coordinator data: OS error for /"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
disk_sensor = hass.states.get("sensor.system_monitor_disk_free")
|
disk_sensor = hass.states.get("sensor.system_monitor_disk_free")
|
||||||
assert disk_sensor is not None
|
assert disk_sensor is not None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue