"""Binary sensor to read Proxmox VE data.""" from collections.abc import Callable from dataclasses import dataclass from typing import Any from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, BinarySensorEntityDescription, ) from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import ( NODE_ONLINE, STATUS_OK, STORAGE_ACTIVE, STORAGE_ENABLED, STORAGE_SHARED, VM_CONTAINER_RUNNING, ) from .coordinator import ProxmoxConfigEntry, ProxmoxNodeData from .entity import ( ProxmoxContainerEntity, ProxmoxNodeEntity, ProxmoxStorageEntity, ProxmoxVMEntity, ) PARALLEL_UPDATES = 0 @dataclass(frozen=True, kw_only=True) class ProxmoxContainerBinarySensorEntityDescription(BinarySensorEntityDescription): """Class to hold Proxmox container binary sensor description.""" state_fn: Callable[[dict[str, Any]], bool | None] @dataclass(frozen=True, kw_only=True) class ProxmoxVMBinarySensorEntityDescription(BinarySensorEntityDescription): """Class to hold Proxmox endpoint binary sensor description.""" state_fn: Callable[[dict[str, Any]], bool | None] @dataclass(frozen=True, kw_only=True) class ProxmoxNodeBinarySensorEntityDescription(BinarySensorEntityDescription): """Class to hold Proxmox node binary sensor description.""" state_fn: Callable[[ProxmoxNodeData], bool | None] @dataclass(frozen=True, kw_only=True) class ProxmoxStorageBinarySensorEntityDescription(BinarySensorEntityDescription): """Class to hold Proxmox storage binary sensor description.""" state_fn: Callable[[dict[str, Any]], bool | None] NODE_SENSORS: tuple[ProxmoxNodeBinarySensorEntityDescription, ...] = ( ProxmoxNodeBinarySensorEntityDescription( key="status", translation_key="status", state_fn=lambda data: data.node["status"] == NODE_ONLINE, device_class=BinarySensorDeviceClass.RUNNING, entity_category=EntityCategory.DIAGNOSTIC, ), ProxmoxNodeBinarySensorEntityDescription( key="node_backup_status", translation_key="node_backup_status", state_fn=lambda data: bool( data.backups and data.backups[0]["status"] != STATUS_OK ), device_class=BinarySensorDeviceClass.PROBLEM, entity_category=EntityCategory.DIAGNOSTIC, ), ) CONTAINER_SENSORS: tuple[ProxmoxContainerBinarySensorEntityDescription, ...] = ( ProxmoxContainerBinarySensorEntityDescription( key="status", translation_key="status", state_fn=lambda data: data["status"] == VM_CONTAINER_RUNNING, device_class=BinarySensorDeviceClass.RUNNING, entity_category=EntityCategory.DIAGNOSTIC, ), ) VM_SENSORS: tuple[ProxmoxVMBinarySensorEntityDescription, ...] = ( ProxmoxVMBinarySensorEntityDescription( key="status", translation_key="status", state_fn=lambda data: data["status"] == VM_CONTAINER_RUNNING, device_class=BinarySensorDeviceClass.RUNNING, entity_category=EntityCategory.DIAGNOSTIC, ), ) STORAGE_SENSORS: tuple[ProxmoxStorageBinarySensorEntityDescription, ...] = ( ProxmoxStorageBinarySensorEntityDescription( key="storage_active", translation_key="storage_active", state_fn=lambda data: data["active"] == STORAGE_ACTIVE, entity_category=EntityCategory.DIAGNOSTIC, ), ProxmoxStorageBinarySensorEntityDescription( key="storage_enabled", translation_key="storage_enabled", state_fn=lambda data: data["enabled"] == STORAGE_ENABLED, entity_category=EntityCategory.DIAGNOSTIC, ), ProxmoxStorageBinarySensorEntityDescription( key="storage_shared", translation_key="storage_shared", state_fn=lambda data: data["shared"] == STORAGE_SHARED, entity_category=EntityCategory.DIAGNOSTIC, ), ) async def async_setup_entry( hass: HomeAssistant, entry: ProxmoxConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Proxmox VE binary sensors.""" coordinator = entry.runtime_data def _async_add_new_nodes(nodes: list[ProxmoxNodeData]) -> None: """Add new node binary sensors.""" async_add_entities( ProxmoxNodeBinarySensor(coordinator, entity_description, node) for node in nodes for entity_description in NODE_SENSORS ) def _async_add_new_vms( vms: list[tuple[ProxmoxNodeData, dict[str, Any]]], ) -> None: """Add new VM binary sensors.""" async_add_entities( ProxmoxVMBinarySensor(coordinator, entity_description, vm, node_data) for (node_data, vm) in vms for entity_description in VM_SENSORS ) def _async_add_new_containers( containers: list[tuple[ProxmoxNodeData, dict[str, Any]]], ) -> None: """Add new container binary sensors.""" async_add_entities( ProxmoxContainerBinarySensor( coordinator, entity_description, container, node_data ) for (node_data, container) in containers for entity_description in CONTAINER_SENSORS ) def _async_add_new_storages( storages: list[tuple[ProxmoxNodeData, dict[str, Any]]], ) -> None: """Add new storage binary sensors.""" async_add_entities( ProxmoxStorageBinarySensor( coordinator, entity_description, storage, node_data ) for (node_data, storage) in storages for entity_description in STORAGE_SENSORS ) coordinator.new_nodes_callbacks.append(_async_add_new_nodes) coordinator.new_vms_callbacks.append(_async_add_new_vms) coordinator.new_containers_callbacks.append(_async_add_new_containers) coordinator.new_storages_callbacks.append(_async_add_new_storages) _async_add_new_nodes( [ node_data for node_data in coordinator.data.values() if node_data.node["node"] in coordinator.known_nodes ] ) _async_add_new_vms( [ (node_data, vm_data) for node_data in coordinator.data.values() for vmid, vm_data in node_data.vms.items() if (node_data.node["node"], vmid) in coordinator.known_vms ] ) _async_add_new_containers( [ (node_data, container_data) for node_data in coordinator.data.values() for vmid, container_data in node_data.containers.items() if (node_data.node["node"], vmid) in coordinator.known_containers ] ) _async_add_new_storages( [ (node_data, storage_data) for node_data in coordinator.data.values() for storage_id, storage_data in node_data.storages.items() if (node_data.node["node"], storage_id) in coordinator.known_storages ] ) class ProxmoxNodeBinarySensor(ProxmoxNodeEntity, BinarySensorEntity): """A binary sensor for reading Proxmox VE node data.""" entity_description: ProxmoxNodeBinarySensorEntityDescription @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" return self.entity_description.state_fn(self.coordinator.data[self.device_name]) class ProxmoxVMBinarySensor(ProxmoxVMEntity, BinarySensorEntity): """Representation of a Proxmox VM binary sensor.""" entity_description: ProxmoxVMBinarySensorEntityDescription @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" return self.entity_description.state_fn(self.vm_data) class ProxmoxContainerBinarySensor(ProxmoxContainerEntity, BinarySensorEntity): """Representation of a Proxmox Container binary sensor.""" entity_description: ProxmoxContainerBinarySensorEntityDescription @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" return self.entity_description.state_fn(self.container_data) class ProxmoxStorageBinarySensor(ProxmoxStorageEntity, BinarySensorEntity): """Representation of a Proxmox Storage binary sensor.""" entity_description: ProxmoxStorageBinarySensorEntityDescription @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" return self.entity_description.state_fn(self.storage_data)