548 lines
20 KiB
Python
548 lines
20 KiB
Python
"""Support for System Bridge sensors."""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Final, cast
|
|
|
|
from homeassistant.components.sensor import (
|
|
SensorDeviceClass,
|
|
SensorEntity,
|
|
SensorEntityDescription,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import (
|
|
CONF_PORT,
|
|
PERCENTAGE,
|
|
REVOLUTIONS_PER_MINUTE,
|
|
UnitOfElectricPotential,
|
|
UnitOfFrequency,
|
|
UnitOfInformation,
|
|
UnitOfPower,
|
|
UnitOfTemperature,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.typing import StateType
|
|
from homeassistant.util.dt import utcnow
|
|
|
|
from . import SystemBridgeEntity
|
|
from .const import DOMAIN
|
|
from .coordinator import SystemBridgeCoordinatorData, SystemBridgeDataUpdateCoordinator
|
|
|
|
ATTR_AVAILABLE: Final = "available"
|
|
ATTR_FILESYSTEM: Final = "filesystem"
|
|
ATTR_MOUNT: Final = "mount"
|
|
ATTR_SIZE: Final = "size"
|
|
ATTR_TYPE: Final = "type"
|
|
ATTR_USED: Final = "used"
|
|
|
|
PIXELS: Final = "px"
|
|
|
|
|
|
@dataclass
|
|
class SystemBridgeSensorEntityDescription(SensorEntityDescription):
|
|
"""Class describing System Bridge sensor entities."""
|
|
|
|
value: Callable = round
|
|
|
|
|
|
def battery_time_remaining(data: SystemBridgeCoordinatorData) -> datetime | None:
|
|
"""Return the battery time remaining."""
|
|
if (value := getattr(data.battery, "sensors_secsleft", None)) is not None:
|
|
return utcnow() + timedelta(seconds=value)
|
|
return None
|
|
|
|
|
|
def cpu_speed(data: SystemBridgeCoordinatorData) -> float | None:
|
|
"""Return the CPU speed."""
|
|
if data.cpu.frequency_current is not None:
|
|
return round(data.cpu.frequency_current / 1000, 2)
|
|
return None
|
|
|
|
|
|
def gpu_core_clock_speed(data: SystemBridgeCoordinatorData, key: str) -> float | None:
|
|
"""Return the GPU core clock speed."""
|
|
if (value := getattr(data.gpu, f"{key}_core_clock", None)) is not None:
|
|
return round(value)
|
|
return None
|
|
|
|
|
|
def gpu_memory_clock_speed(data: SystemBridgeCoordinatorData, key: str) -> float | None:
|
|
"""Return the GPU memory clock speed."""
|
|
if (value := getattr(data.gpu, f"{key}_memory_clock", None)) is not None:
|
|
return round(value)
|
|
return None
|
|
|
|
|
|
def gpu_memory_free(data: SystemBridgeCoordinatorData, key: str) -> float | None:
|
|
"""Return the free GPU memory."""
|
|
if (value := getattr(data.gpu, f"{key}_memory_free", None)) is not None:
|
|
return round(value)
|
|
return None
|
|
|
|
|
|
def gpu_memory_used(data: SystemBridgeCoordinatorData, key: str) -> float | None:
|
|
"""Return the used GPU memory."""
|
|
if (value := getattr(data.gpu, f"{key}_memory_used", None)) is not None:
|
|
return round(value)
|
|
return None
|
|
|
|
|
|
def gpu_memory_used_percentage(
|
|
data: SystemBridgeCoordinatorData, key: str
|
|
) -> float | None:
|
|
"""Return the used GPU memory percentage."""
|
|
if ((used := getattr(data.gpu, f"{key}_memory_used", None)) is not None) and (
|
|
(total := getattr(data.gpu, f"{key}_memory_total", None)) is not None
|
|
):
|
|
return round(
|
|
used / total * 100,
|
|
2,
|
|
)
|
|
return None
|
|
|
|
|
|
def memory_free(data: SystemBridgeCoordinatorData) -> float | None:
|
|
"""Return the free memory."""
|
|
if data.memory.virtual_free is not None:
|
|
return round(data.memory.virtual_free / 1000**3, 2)
|
|
return None
|
|
|
|
|
|
def memory_used(data: SystemBridgeCoordinatorData) -> float | None:
|
|
"""Return the used memory."""
|
|
if data.memory.virtual_used is not None:
|
|
return round(data.memory.virtual_used / 1000**3, 2)
|
|
return None
|
|
|
|
|
|
BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = (
|
|
SystemBridgeSensorEntityDescription(
|
|
key="boot_time",
|
|
name="Boot time",
|
|
device_class=SensorDeviceClass.TIMESTAMP,
|
|
icon="mdi:av-timer",
|
|
value=lambda data: datetime.fromtimestamp(
|
|
data.system.boot_time, tz=timezone.utc
|
|
),
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="cpu_speed",
|
|
name="CPU speed",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfFrequency.GIGAHERTZ,
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
icon="mdi:speedometer",
|
|
value=cpu_speed,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="cpu_temperature",
|
|
name="CPU temperature",
|
|
entity_registry_enabled_default=False,
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
value=lambda data: data.cpu.temperature,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="cpu_voltage",
|
|
name="CPU voltage",
|
|
entity_registry_enabled_default=False,
|
|
device_class=SensorDeviceClass.VOLTAGE,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
|
value=lambda data: data.cpu.voltage,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="kernel",
|
|
name="Kernel",
|
|
icon="mdi:devices",
|
|
value=lambda data: data.system.platform,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="memory_free",
|
|
name="Memory free",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:memory",
|
|
value=memory_free,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="memory_used_percentage",
|
|
name="Memory used %",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
icon="mdi:memory",
|
|
value=lambda data: data.memory.virtual_percent,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="memory_used",
|
|
name="Memory used",
|
|
entity_registry_enabled_default=False,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:memory",
|
|
value=memory_used,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="os",
|
|
name="Operating system",
|
|
icon="mdi:devices",
|
|
value=lambda data: f"{data.system.platform} {data.system.platform_version}",
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="processes_load",
|
|
name="Load",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
icon="mdi:percent",
|
|
value=lambda data: data.cpu.usage,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="version",
|
|
name="Version",
|
|
icon="mdi:counter",
|
|
value=lambda data: data.system.version,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="version_latest",
|
|
name="Latest version",
|
|
icon="mdi:counter",
|
|
value=lambda data: data.system.version_latest,
|
|
),
|
|
)
|
|
|
|
BATTERY_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = (
|
|
SystemBridgeSensorEntityDescription(
|
|
key="battery",
|
|
name="Battery",
|
|
device_class=SensorDeviceClass.BATTERY,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
value=lambda data: data.battery.percentage,
|
|
),
|
|
SystemBridgeSensorEntityDescription(
|
|
key="battery_time_remaining",
|
|
name="Battery time remaining",
|
|
device_class=SensorDeviceClass.TIMESTAMP,
|
|
value=battery_time_remaining,
|
|
),
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up System Bridge sensor based on a config entry."""
|
|
coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
|
|
|
entities = []
|
|
for description in BASE_SENSOR_TYPES:
|
|
entities.append(
|
|
SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT])
|
|
)
|
|
|
|
for partition in coordinator.data.disk.partitions:
|
|
entities.append(
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"filesystem_{partition.replace(':', '')}",
|
|
name=f"{partition} space used",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
icon="mdi:harddisk",
|
|
value=lambda data, p=partition: getattr(
|
|
data.disk, f"usage_{p}_percent", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
)
|
|
)
|
|
|
|
if (
|
|
coordinator.data.battery
|
|
and coordinator.data.battery.percentage
|
|
and coordinator.data.battery.percentage > -1
|
|
):
|
|
for description in BATTERY_SENSOR_TYPES:
|
|
entities.append(
|
|
SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT])
|
|
)
|
|
|
|
displays: list[dict[str, str]] = []
|
|
if coordinator.data.display.displays is not None:
|
|
displays.extend(
|
|
{
|
|
"key": display,
|
|
"name": getattr(coordinator.data.display, f"{display}_name").replace(
|
|
"Display ", ""
|
|
),
|
|
}
|
|
for display in coordinator.data.display.displays
|
|
if hasattr(coordinator.data.display, f"{display}_name")
|
|
)
|
|
display_count = len(displays)
|
|
|
|
entities.append(
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key="displays_connected",
|
|
name="Displays connected",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
icon="mdi:monitor",
|
|
value=lambda _, count=display_count: count,
|
|
),
|
|
entry.data[CONF_PORT],
|
|
)
|
|
)
|
|
|
|
for _, display in enumerate(displays):
|
|
entities = [
|
|
*entities,
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"display_{display['name']}_resolution_x",
|
|
name=f"Display {display['name']} resolution x",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PIXELS,
|
|
icon="mdi:monitor",
|
|
value=lambda data, k=display["key"]: getattr(
|
|
data.display, f"{k}_resolution_horizontal", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"display_{display['name']}_resolution_y",
|
|
name=f"Display {display['name']} resolution y",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PIXELS,
|
|
icon="mdi:monitor",
|
|
value=lambda data, k=display["key"]: getattr(
|
|
data.display, f"{k}_resolution_vertical", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"display_{display['name']}_refresh_rate",
|
|
name=f"Display {display['name']} refresh rate",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfFrequency.HERTZ,
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
icon="mdi:monitor",
|
|
value=lambda data, k=display["key"]: getattr(
|
|
data.display, f"{k}_refresh_rate", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
]
|
|
|
|
gpus: list[dict[str, str]] = []
|
|
if coordinator.data.gpu.gpus is not None:
|
|
gpus.extend(
|
|
{
|
|
"key": gpu,
|
|
"name": getattr(coordinator.data.gpu, f"{gpu}_name"),
|
|
}
|
|
for gpu in coordinator.data.gpu.gpus
|
|
if hasattr(coordinator.data.gpu, f"{gpu}_name")
|
|
)
|
|
|
|
for index, gpu in enumerate(gpus):
|
|
entities = [
|
|
*entities,
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_core_clock_speed",
|
|
name=f"{gpu['name']} clock speed",
|
|
entity_registry_enabled_default=False,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
icon="mdi:speedometer",
|
|
value=lambda data, k=gpu["key"]: gpu_core_clock_speed(data, k),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_memory_clock_speed",
|
|
name=f"{gpu['name']} memory clock speed",
|
|
entity_registry_enabled_default=False,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
icon="mdi:speedometer",
|
|
value=lambda data, k=gpu["key"]: gpu_memory_clock_speed(data, k),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_memory_free",
|
|
name=f"{gpu['name']} memory free",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:memory",
|
|
value=lambda data, k=gpu["key"]: gpu_memory_free(data, k),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_memory_used_percentage",
|
|
name=f"{gpu['name']} memory used %",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
icon="mdi:memory",
|
|
value=lambda data, k=gpu["key"]: gpu_memory_used_percentage(
|
|
data, k
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_memory_used",
|
|
name=f"{gpu['name']} memory used",
|
|
entity_registry_enabled_default=False,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:memory",
|
|
value=lambda data, k=gpu["key"]: gpu_memory_used(data, k),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_fan_speed",
|
|
name=f"{gpu['name']} fan speed",
|
|
entity_registry_enabled_default=False,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
|
|
icon="mdi:fan",
|
|
value=lambda data, k=gpu["key"]: getattr(
|
|
data.gpu, f"{k}_fan_speed", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_power_usage",
|
|
name=f"{gpu['name']} power usage",
|
|
entity_registry_enabled_default=False,
|
|
device_class=SensorDeviceClass.POWER,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfPower.WATT,
|
|
value=lambda data, k=gpu["key"]: getattr(
|
|
data.gpu, f"{k}_power", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_temperature",
|
|
name=f"{gpu['name']} temperature",
|
|
entity_registry_enabled_default=False,
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
value=lambda data, k=gpu["key"]: getattr(
|
|
data.gpu, f"{k}_temperature", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"gpu_{index}_usage_percentage",
|
|
name=f"{gpu['name']} usage %",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
icon="mdi:percent",
|
|
value=lambda data, k=gpu["key"]: getattr(
|
|
data.gpu, f"{k}_core_load", None
|
|
),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
]
|
|
|
|
for index in range(coordinator.data.cpu.count):
|
|
entities = [
|
|
*entities,
|
|
SystemBridgeSensor(
|
|
coordinator,
|
|
SystemBridgeSensorEntityDescription(
|
|
key=f"processes_load_cpu_{index}",
|
|
name=f"Load CPU {index}",
|
|
entity_registry_enabled_default=False,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
icon="mdi:percent",
|
|
value=lambda data, k=index: getattr(data.cpu, f"usage_{k}", None),
|
|
),
|
|
entry.data[CONF_PORT],
|
|
),
|
|
]
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
class SystemBridgeSensor(SystemBridgeEntity, SensorEntity):
|
|
"""Define a System Bridge sensor."""
|
|
|
|
entity_description: SystemBridgeSensorEntityDescription
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: SystemBridgeDataUpdateCoordinator,
|
|
description: SystemBridgeSensorEntityDescription,
|
|
api_port: int,
|
|
) -> None:
|
|
"""Initialize."""
|
|
super().__init__(
|
|
coordinator,
|
|
api_port,
|
|
description.key,
|
|
description.name,
|
|
)
|
|
self.entity_description = description
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return the state."""
|
|
try:
|
|
return cast(StateType, self.entity_description.value(self.coordinator.data))
|
|
except TypeError:
|
|
return None
|