"""Binary sensor platform for AdGuard Control Hub integration.""" import logging from typing import Any, Optional from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import AdGuardControlHubCoordinator from .api import AdGuardHomeAPI from .const import DOMAIN, MANUFACTURER, ICON_PROTECTION, ICON_PROTECTION_OFF _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up AdGuard Control Hub binary sensor platform.""" coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] api = hass.data[DOMAIN][config_entry.entry_id]["api"] entities = [ AdGuardProtectionBinarySensor(coordinator, api), AdGuardServerRunningBinarySensor(coordinator, api), AdGuardSafeBrowsingBinarySensor(coordinator, api), AdGuardParentalControlBinarySensor(coordinator, api), AdGuardSafeSearchBinarySensor(coordinator, api), ] # Add client-specific binary sensors for client_name in coordinator.clients.keys(): entities.extend([ AdGuardClientFilteringBinarySensor(coordinator, api, client_name), AdGuardClientSafeBrowsingBinarySensor(coordinator, api, client_name), ]) async_add_entities(entities, update_before_add=True) class AdGuardBaseBinarySensor(CoordinatorEntity, BinarySensorEntity): """Base class for AdGuard binary sensors.""" def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None: """Initialize the binary sensor.""" super().__init__(coordinator) self.api = api self._attr_device_info = { "identifiers": {(DOMAIN, f"{api.host}:{api.port}")}, "name": f"AdGuard Control Hub ({api.host})", "manufacturer": MANUFACTURER, "model": "AdGuard Home", "configuration_url": f"{'https' if api.ssl else 'http'}://{api.host}:{api.port}", } class AdGuardProtectionBinarySensor(AdGuardBaseBinarySensor): """Binary sensor to show AdGuard protection status.""" def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None: """Initialize the binary sensor.""" super().__init__(coordinator, api) self._attr_unique_id = f"{api.host}_{api.port}_protection_enabled" self._attr_name = "AdGuard Protection Status" self._attr_device_class = BinarySensorDeviceClass.RUNNING self._attr_entity_category = EntityCategory.DIAGNOSTIC @property def is_on(self) -> Optional[bool]: """Return true if protection is enabled.""" return self.coordinator.protection_status.get("protection_enabled", False) @property def icon(self) -> str: """Return the icon for the binary sensor.""" return ICON_PROTECTION if self.is_on else ICON_PROTECTION_OFF @property def available(self) -> bool: """Return if sensor is available.""" return self.coordinator.last_update_success and bool(self.coordinator.protection_status) @property def extra_state_attributes(self) -> dict[str, Any]: """Return additional state attributes.""" status = self.coordinator.protection_status return { "dns_port": status.get("dns_port", "N/A"), "version": status.get("version", "N/A"), "running": status.get("running", False), "dhcp_available": status.get("dhcp_available", False), } class AdGuardServerRunningBinarySensor(AdGuardBaseBinarySensor): """Binary sensor to show if AdGuard server is running.""" def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None: """Initialize the binary sensor.""" super().__init__(coordinator, api) self._attr_unique_id = f"{api.host}_{api.port}_server_running" self._attr_name = "AdGuard Server Running" self._attr_device_class = BinarySensorDeviceClass.RUNNING self._attr_entity_category = EntityCategory.DIAGNOSTIC @property def is_on(self) -> Optional[bool]: """Return true if server is running.""" return self.coordinator.protection_status.get("running", False) @property def icon(self) -> str: """Return the icon for the binary sensor.""" return "mdi:server" if self.is_on else "mdi:server-off" @property def available(self) -> bool: """Return if sensor is available.""" return self.coordinator.last_update_success and bool(self.coordinator.protection_status) class AdGuardSafeBrowsingBinarySensor(AdGuardBaseBinarySensor): """Binary sensor to show SafeBrowsing status.""" def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None: """Initialize the binary sensor.""" super().__init__(coordinator, api) self._attr_unique_id = f"{api.host}_{api.port}_safebrowsing_enabled" self._attr_name = "AdGuard SafeBrowsing" self._attr_device_class = BinarySensorDeviceClass.SAFETY self._attr_entity_category = EntityCategory.DIAGNOSTIC @property def is_on(self) -> Optional[bool]: """Return true if SafeBrowsing is enabled.""" return self.coordinator.protection_status.get("safebrowsing_enabled", False) @property def icon(self) -> str: """Return the icon for the binary sensor.""" return "mdi:shield-check" if self.is_on else "mdi:shield-off" @property def available(self) -> bool: """Return if sensor is available.""" return self.coordinator.last_update_success and bool(self.coordinator.protection_status) class AdGuardParentalControlBinarySensor(AdGuardBaseBinarySensor): """Binary sensor to show Parental Control status.""" def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None: """Initialize the binary sensor.""" super().__init__(coordinator, api) self._attr_unique_id = f"{api.host}_{api.port}_parental_enabled" self._attr_name = "AdGuard Parental Control" self._attr_device_class = BinarySensorDeviceClass.SAFETY self._attr_entity_category = EntityCategory.DIAGNOSTIC @property def is_on(self) -> Optional[bool]: """Return true if Parental Control is enabled.""" return self.coordinator.protection_status.get("parental_enabled", False) @property def icon(self) -> str: """Return the icon for the binary sensor.""" return "mdi:account-child" if self.is_on else "mdi:account-child-outline" @property def available(self) -> bool: """Return if sensor is available.""" return self.coordinator.last_update_success and bool(self.coordinator.protection_status) class AdGuardSafeSearchBinarySensor(AdGuardBaseBinarySensor): """Binary sensor to show Safe Search status.""" def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None: """Initialize the binary sensor.""" super().__init__(coordinator, api) self._attr_unique_id = f"{api.host}_{api.port}_safesearch_enabled" self._attr_name = "AdGuard Safe Search" self._attr_device_class = BinarySensorDeviceClass.SAFETY self._attr_entity_category = EntityCategory.DIAGNOSTIC @property def is_on(self) -> Optional[bool]: """Return true if Safe Search is enabled.""" return self.coordinator.protection_status.get("safesearch_enabled", False) @property def icon(self) -> str: """Return the icon for the binary sensor.""" return "mdi:shield-search" if self.is_on else "mdi:magnify" @property def available(self) -> bool: """Return if sensor is available.""" return self.coordinator.last_update_success and bool(self.coordinator.protection_status) class AdGuardClientFilteringBinarySensor(AdGuardBaseBinarySensor): """Binary sensor to show client-specific filtering status.""" def __init__( self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI, client_name: str, ) -> None: """Initialize the binary sensor.""" super().__init__(coordinator, api) self.client_name = client_name self._attr_unique_id = f"{api.host}_{api.port}_client_{client_name}_filtering" self._attr_name = f"AdGuard {client_name} Filtering" self._attr_device_class = BinarySensorDeviceClass.RUNNING self._attr_entity_category = EntityCategory.DIAGNOSTIC @property def is_on(self) -> Optional[bool]: """Return true if client filtering is enabled.""" client = self.coordinator.clients.get(self.client_name, {}) return client.get("filtering_enabled", True) @property def icon(self) -> str: """Return the icon for the binary sensor.""" return "mdi:filter" if self.is_on else "mdi:filter-off" @property def available(self) -> bool: """Return if sensor is available.""" return ( self.coordinator.last_update_success and self.client_name in self.coordinator.clients ) @property def extra_state_attributes(self) -> dict[str, Any]: """Return additional state attributes.""" client = self.coordinator.clients.get(self.client_name, {}) return { "client_ids": client.get("ids", []), "use_global_settings": client.get("use_global_settings", True), } class AdGuardClientSafeBrowsingBinarySensor(AdGuardBaseBinarySensor): """Binary sensor to show client-specific SafeBrowsing status.""" def __init__( self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI, client_name: str, ) -> None: """Initialize the binary sensor.""" super().__init__(coordinator, api) self.client_name = client_name self._attr_unique_id = f"{api.host}_{api.port}_client_{client_name}_safebrowsing" self._attr_name = f"AdGuard {client_name} SafeBrowsing" self._attr_device_class = BinarySensorDeviceClass.SAFETY self._attr_entity_category = EntityCategory.DIAGNOSTIC @property def is_on(self) -> Optional[bool]: """Return true if client SafeBrowsing is enabled.""" client = self.coordinator.clients.get(self.client_name, {}) return client.get("safebrowsing_enabled", False) @property def icon(self) -> str: """Return the icon for the binary sensor.""" return "mdi:shield-account" if self.is_on else "mdi:shield-account-outline" @property def available(self) -> bool: """Return if sensor is available.""" return ( self.coordinator.last_update_success and self.client_name in self.coordinator.clients ) @property def extra_state_attributes(self) -> dict[str, Any]: """Return additional state attributes.""" client = self.coordinator.clients.get(self.client_name, {}) return { "parental_enabled": client.get("parental_enabled", False), "safesearch_enabled": client.get("safesearch_enabled", False), }