From 3aad1539138f7bd38054d5ab2826d7e9cfd753aa Mon Sep 17 00:00:00 2001 From: shbatm Date: Mon, 16 Jan 2023 13:33:55 -0600 Subject: [PATCH] Add enable/disable config switch for ISY994 devices (#85975) Co-authored-by: J. Nick Koston --- homeassistant/components/isy994/const.py | 7 +- homeassistant/components/isy994/helpers.py | 3 + homeassistant/components/isy994/switch.py | 117 +++++++++++++++++++-- 3 files changed, 116 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index f70065cd7bc..211939f8eb6 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -87,7 +87,12 @@ NODE_PLATFORMS = [ Platform.SENSOR, Platform.SWITCH, ] -NODE_AUX_PROP_PLATFORMS = [Platform.SELECT, Platform.SENSOR, Platform.NUMBER] +NODE_AUX_PROP_PLATFORMS = [ + Platform.NUMBER, + Platform.SELECT, + Platform.SENSOR, + Platform.SWITCH, +] PROGRAM_PLATFORMS = [ Platform.BINARY_SENSOR, Platform.COVER, diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index e3c0ce909fa..e3e21dbfd4f 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -16,6 +16,7 @@ from pyisy.constants import ( PROTO_INSTEON, PROTO_PROGRAM, PROTO_ZWAVE, + TAG_ENABLED, TAG_FOLDER, UOM_INDEX, ) @@ -349,6 +350,8 @@ def _categorize_nodes( isy_data.aux_properties[Platform.SENSOR].append((node, control)) platform = NODE_AUX_FILTERS[control] isy_data.aux_properties[platform].append((node, control)) + if hasattr(node, TAG_ENABLED): + isy_data.aux_properties[Platform.SWITCH].append((node, TAG_ENABLED)) _add_backlight_if_supported(isy_data, node) if node.protocol == PROTO_GROUP: diff --git a/homeassistant/components/isy994/switch.py b/homeassistant/components/isy994/switch.py index d8688487c37..c5fba9cb54b 100644 --- a/homeassistant/components/isy994/switch.py +++ b/homeassistant/components/isy994/switch.py @@ -3,16 +3,30 @@ from __future__ import annotations from typing import Any -from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_GROUP +from pyisy.constants import ( + ATTR_ACTION, + ISY_VALUE_UNKNOWN, + NC_NODE_ENABLED, + PROTO_GROUP, + TAG_ADDRESS, +) +from pyisy.helpers import EventListener +from pyisy.nodes import Node, NodeChangedEvent -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity import DeviceInfo, EntityCategory, EntityDescription from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import _LOGGER, DOMAIN -from .entity import ISYNodeEntity, ISYProgramEntity +from .const import DOMAIN +from .entity import ISYAuxControlEntity, ISYNodeEntity, ISYProgramEntity from .models import IsyData @@ -21,7 +35,9 @@ async def async_setup_entry( ) -> None: """Set up the ISY switch platform.""" isy_data: IsyData = hass.data[DOMAIN][entry.entry_id] - entities: list[ISYSwitchProgramEntity | ISYSwitchEntity] = [] + entities: list[ + ISYSwitchProgramEntity | ISYSwitchEntity | ISYEnableSwitchEntity + ] = [] device_info = isy_data.devices for node in isy_data.nodes[Platform.SWITCH]: primary = node.primary_node @@ -34,6 +50,24 @@ async def async_setup_entry( for name, status, actions in isy_data.programs[Platform.SWITCH]: entities.append(ISYSwitchProgramEntity(name, status, actions)) + for node, control in isy_data.aux_properties[Platform.SWITCH]: + # Currently only used for enable switches, will need to be updated for NS support + # by making sure control == TAG_ENABLED + description = SwitchEntityDescription( + key=control, + device_class=SwitchDeviceClass.SWITCH, + name=control.title(), + entity_category=EntityCategory.CONFIG, + ) + entities.append( + ISYEnableSwitchEntity( + node=node, + control=control, + unique_id=f"{isy_data.uid_base(node)}_{control}", + description=description, + device_info=device_info.get(node.primary_node), + ) + ) async_add_entities(entities) @@ -50,12 +84,12 @@ class ISYSwitchEntity(ISYNodeEntity, SwitchEntity): async def async_turn_off(self, **kwargs: Any) -> None: """Send the turn off command to the ISY switch.""" if not await self._node.turn_off(): - _LOGGER.debug("Unable to turn off switch") + HomeAssistantError(f"Unable to turn off switch {self._node.address}") async def async_turn_on(self, **kwargs: Any) -> None: """Send the turn on command to the ISY switch.""" if not await self._node.turn_on(): - _LOGGER.debug("Unable to turn on switch") + HomeAssistantError(f"Unable to turn on switch {self._node.address}") @property def icon(self) -> str | None: @@ -76,14 +110,77 @@ class ISYSwitchProgramEntity(ISYProgramEntity, SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Send the turn on command to the ISY switch program.""" if not await self._actions.run_then(): - _LOGGER.error("Unable to turn on switch") + HomeAssistantError( + f"Unable to run 'then' clause on program switch {self._actions.address}" + ) async def async_turn_off(self, **kwargs: Any) -> None: """Send the turn off command to the ISY switch program.""" if not await self._actions.run_else(): - _LOGGER.error("Unable to turn off switch") + HomeAssistantError( + f"Unable to run 'else' clause on program switch {self._actions.address}" + ) @property def icon(self) -> str: """Get the icon for programs.""" return "mdi:script-text-outline" # Matches isy program icon + + +class ISYEnableSwitchEntity(ISYAuxControlEntity, SwitchEntity): + """A representation of an ISY enable/disable switch.""" + + def __init__( + self, + node: Node, + control: str, + unique_id: str, + description: EntityDescription, + device_info: DeviceInfo | None, + ) -> None: + """Initialize the ISY Aux Control Number entity.""" + super().__init__( + node=node, + control=control, + unique_id=unique_id, + description=description, + device_info=device_info, + ) + self._attr_name = description.name # Override super + self._change_handler: EventListener = None + + async def async_added_to_hass(self) -> None: + """Subscribe to the node control change events.""" + self._change_handler = self._node.isy.nodes.status_events.subscribe( + self.async_on_update, + event_filter={ + TAG_ADDRESS: self._node.address, + ATTR_ACTION: NC_NODE_ENABLED, + }, + key=self.unique_id, + ) + + @callback + def async_on_update(self, event: NodeChangedEvent, key: str) -> None: + """Handle a control event from the ISY Node.""" + self.async_write_ha_state() + + @property + def available(self) -> bool: + """Return entity availability.""" + return True # Enable switch is always available + + @property + def is_on(self) -> bool | None: + """Get whether the ISY device is in the on state.""" + return bool(self._node.enabled) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Send the turn off command to the ISY switch.""" + if not await self._node.disable(): + raise HomeAssistantError(f"Unable to disable device {self._node.address}") + + async def async_turn_on(self, **kwargs: Any) -> None: + """Send the turn on command to the ISY switch.""" + if not await self._node.enable(): + raise HomeAssistantError(f"Unable to enable device {self._node.address}")