"""Support for Netgear switches.""" from collections.abc import Callable from dataclasses import dataclass from datetime import timedelta import logging from typing import Any, override from pynetgear import ALLOW, BLOCK from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .coordinator import NetgearConfigEntry, NetgearTrackerCoordinator from .entity import NetgearDeviceEntity, NetgearRouterEntity from .router import NetgearRouter _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=300) SWITCH_TYPES = [ SwitchEntityDescription( key="allow_or_block", translation_key="allowed_on_network", entity_category=EntityCategory.CONFIG, ) ] @dataclass(frozen=True) class NetgearSwitchEntityDescriptionRequired: """Required attributes of NetgearSwitchEntityDescription.""" @dataclass(frozen=True, kw_only=True) class NetgearSwitchEntityDescription(SwitchEntityDescription): """Class describing Netgear Switch entities.""" update: Callable[[NetgearRouter], Callable[[], bool | None]] action: Callable[[NetgearRouter], Callable[[bool], bool]] ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="access_control", translation_key="access_control", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_block_device_enable_status, action=lambda router: router.api.set_block_device_enable, ), NetgearSwitchEntityDescription( key="traffic_meter", translation_key="traffic_meter", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_traffic_meter_enabled, action=lambda router: router.api.enable_traffic_meter, ), NetgearSwitchEntityDescription( key="parental_control", translation_key="parental_control", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_parental_control_enable_status, action=lambda router: router.api.enable_parental_control, ), NetgearSwitchEntityDescription( key="qos", translation_key="quality_of_service", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_qos_enable_status, action=lambda router: router.api.set_qos_enable_status, ), NetgearSwitchEntityDescription( key="2g_guest_wifi", translation_key="2g_guest_wifi", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_2g_guest_access_enabled, action=lambda router: router.api.set_2g_guest_access_enabled, ), NetgearSwitchEntityDescription( key="5g_guest_wifi", translation_key="5g_guest_wifi", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_5g_guest_access_enabled, action=lambda router: router.api.set_5g_guest_access_enabled, ), NetgearSwitchEntityDescription( key="smart_connect", translation_key="smart_connect", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_smart_connect_enabled, action=lambda router: router.api.set_smart_connect_enabled, ), ] async def async_setup_entry( hass: HomeAssistant, entry: NetgearConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up switches for Netgear component.""" router = entry.runtime_data.router async_add_entities( NetgearRouterSwitchEntity(router, description) for description in ROUTER_SWITCH_TYPES ) # Entities per network device coordinator_tracker = entry.runtime_data.coordinator_tracker tracked = set() @callback def new_device_callback() -> None: """Add new devices if needed.""" new_entities = [] if not coordinator_tracker.data: return for mac, device in router.devices.items(): if mac in tracked: continue new_entities.extend( [ NetgearAllowBlock(coordinator_tracker, device, entity_description) for entity_description in SWITCH_TYPES ] ) tracked.add(mac) async_add_entities(new_entities) entry.async_on_unload(coordinator_tracker.async_add_listener(new_device_callback)) coordinator_tracker.data = True new_device_callback() class NetgearAllowBlock(NetgearDeviceEntity, SwitchEntity): """Allow or Block a device from the network.""" _attr_entity_registry_enabled_default = False def __init__( self, coordinator: NetgearTrackerCoordinator, device: dict, entity_description: SwitchEntityDescription, ) -> None: """Initialize a Netgear device.""" super().__init__(coordinator, device) self.entity_description = entity_description self._attr_unique_id = f"{self._mac}-{entity_description.key}" self.async_update_device() @override async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" await self._router.async_allow_block_device(self._mac, ALLOW) await self.coordinator.async_request_refresh() @override async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" await self._router.async_allow_block_device(self._mac, BLOCK) await self.coordinator.async_request_refresh() @callback @override def async_update_device(self) -> None: """Update the Netgear device.""" self._device = self._router.devices[self._mac] self._active = self._device["active"] if self._device[self.entity_description.key] is None: self._attr_is_on = None else: self._attr_is_on = self._device[self.entity_description.key] == "Allow" class NetgearRouterSwitchEntity(NetgearRouterEntity, SwitchEntity): """Representation of a Netgear router switch.""" _attr_entity_registry_enabled_default = False entity_description: NetgearSwitchEntityDescription def __init__( self, router: NetgearRouter, entity_description: NetgearSwitchEntityDescription, ) -> None: """Initialize a Netgear device.""" super().__init__(router) self.entity_description = entity_description self._attr_unique_id = f"{router.serial_number}-{entity_description.key}" self._attr_is_on = None self._attr_available = False @override async def async_added_to_hass(self) -> None: """Fetch state when entity is added.""" await self.async_update() await super().async_added_to_hass() async def async_update(self) -> None: """Poll the state of the switch.""" async with self._router.api_lock: response = await self.hass.async_add_executor_job( self.entity_description.update(self._router) ) if response is None: self._attr_available = False else: self._attr_is_on = response self._attr_available = True @override async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" async with self._router.api_lock: await self.hass.async_add_executor_job( self.entity_description.action(self._router), True ) @override async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" async with self._router.api_lock: await self.hass.async_add_executor_job( self.entity_description.action(self._router), False )