"""AsusRouter switch module.""" from __future__ import annotations import logging from typing import Any from asusrouter.modules.parental_control import ParentalControlRule, PCRuleType from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( ASUSROUTER, CONF_DEFAULT_HIDE_PASSWORDS, CONF_HIDE_PASSWORDS, DOMAIN, ICON_INTERNET_ACCESS_OFF, ICON_INTERNET_ACCESS_ON, STATIC_SWITCHES, ) from .dataclass import ARSwitchDescription from .entity import ARBinaryEntity, async_setup_ar_entry from .helpers import to_unique_id from .router import ARDevice _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up AsusRouter switches.""" switches = STATIC_SWITCHES.copy() hide = [] if config_entry.options.get( CONF_HIDE_PASSWORDS, CONF_DEFAULT_HIDE_PASSWORDS ): hide.extend(["password", "private_key", "psk"]) await async_setup_ar_entry( hass, config_entry, async_add_entities, switches, ARSwitch, hide ) router = hass.data[DOMAIN][config_entry.entry_id][ASUSROUTER] tracked: set = set() @callback def update_router(): """Update the values of the router.""" add_entities(router, async_add_entities, tracked) router.async_on_close( async_dispatcher_connect( hass, router.signal_pc_rules_new, update_router ) ) update_router() @callback def add_entities( router: ARDevice, async_add_entities: AddEntitiesCallback, tracked: set[str], ) -> None: """Add new tracker entities from the router.""" new_tracked = [] for mac, rule in router.pc_rules.items(): if mac in tracked: continue new_tracked.append(ClientInternetSwitch(router, rule)) tracked.add(mac) if new_tracked: async_add_entities(new_tracked) class ARSwitch(ARBinaryEntity, SwitchEntity): """AsusRouter switch.""" def __init__( self, coordinator: DataUpdateCoordinator, router: ARDevice, description: ARSwitchDescription, ) -> None: """Initialize AsusRouter switch.""" super().__init__(coordinator, router, description) self.entity_description: ARSwitchDescription = description # State on self._state_on = description.state_on self._state_on_args = description.state_on_args # State off self._state_off = description.state_off self._state_off_args = description.state_off_args # Expect modify self._state_expect_modify = description.state_expect_modify async def async_turn_on( self, **kwargs: Any, ) -> None: """Turn on switch.""" kwargs = self._state_on_args if self._state_on_args is not None else {} await self._set_state( state=self._state_on, expect_modify=self._state_expect_modify, **kwargs, ) async def async_turn_off( self, **kwargs: Any, ) -> None: """Turn off switch.""" kwargs = ( self._state_off_args if self._state_off_args is not None else {} ) await self._set_state( state=self._state_off, expect_modify=self._state_expect_modify, **kwargs, ) class ClientInternetSwitch(SwitchEntity): """Client internet switch.""" def __init__( self, router: ARDevice, rule: ParentalControlRule, ): """Initialize client switch.""" self._router = router self._rule = rule self._mac = dr.format_mac(rule.mac) self._attr_unique_id = to_unique_id( f"{router.mac}_{self._mac}_block_internet" ) self._attr_name = f"{rule.name} Block Internet" # Assign device info if set up if router.create_devices is True: self._attr_device_info = self._compile_device_info() def _compile_device_info(self) -> DeviceInfo: """Compile device info.""" return DeviceInfo( connections={(dr.CONNECTION_NETWORK_MAC, self._mac)}, default_name=self._rule.name, via_device=(DOMAIN, self._router.mac), ) @property def is_on(self) -> bool | None: """Get the state.""" match self._rule.type: case PCRuleType.BLOCK: return True case PCRuleType.DISABLE: return False case _: return None @property def icon(self) -> str | None: """Get the icon.""" if self.is_on: return ICON_INTERNET_ACCESS_OFF return ICON_INTERNET_ACCESS_ON @property def extra_state_attributes(self) -> dict[str, Any]: """Return extra state attributes.""" return { "mac": self._mac, } async def _set_state( self, state: ParentalControlRule, **kwargs: Any, ) -> None: """Set state.""" try: _LOGGER.debug("Changing PC rule to %s", state) result = await self._router.bridge.api.async_set_state( state=state, **kwargs ) self._rule = state if not result: _LOGGER.debug("State was not set!") except Exception as ex: # noqa: BLE001 _LOGGER.error("Unable to set state with an exception: %s", ex) async def async_turn_on( self, **kwargs: Any, ) -> None: """Turn on block.""" await self._set_state( state=ParentalControlRule( mac=self._rule.mac, name=self._rule.name, type=PCRuleType.BLOCK, ), **kwargs, ) async def async_turn_off( self, **kwargs: Any, ) -> None: """Turn off block.""" await self._set_state( state=ParentalControlRule( mac=self._rule.mac, name=self._rule.name, type=PCRuleType.DISABLE, ), **kwargs, ) @callback def async_on_demand_update(self) -> None: """Update the state.""" if self._rule.mac in self._router.pc_rules: self._rule = self._router.pc_rules[self._rule.mac] self.async_write_ha_state() async def async_added_to_hass(self) -> None: """Register state update callback.""" self.async_on_remove( async_dispatcher_connect( self.hass, self._router.signal_pc_rules_update, self.async_on_demand_update, ) )