Support availability for ISY994 devices (#85928)

This commit is contained in:
shbatm 2023-01-15 23:18:51 -06:00 committed by GitHub
parent b81453cb6b
commit dfc33f858a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 23 deletions

View file

@ -2,14 +2,21 @@
from __future__ import annotations from __future__ import annotations
from pyisy import ISY from pyisy import ISY
from pyisy.constants import PROTO_INSTEON from pyisy.constants import (
ATTR_ACTION,
NC_NODE_ENABLED,
PROTO_INSTEON,
TAG_ADDRESS,
TAG_ENABLED,
)
from pyisy.helpers import EventListener, NodeProperty
from pyisy.networking import NetworkCommand from pyisy.networking import NetworkCommand
from pyisy.nodes import Node from pyisy.nodes import Node
from homeassistant.components.button import ButtonEntity from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -98,6 +105,34 @@ class ISYNodeButtonEntity(ButtonEntity):
self._attr_entity_category = entity_category self._attr_entity_category = entity_category
self._attr_unique_id = unique_id self._attr_unique_id = unique_id
self._attr_device_info = device_info self._attr_device_info = device_info
self._node_enabled = getattr(node, TAG_ENABLED, True)
self._availability_handler: EventListener | None = None
@property
def available(self) -> bool:
"""Return entity availability."""
return self._node_enabled
async def async_added_to_hass(self) -> None:
"""Subscribe to the node change events."""
# No status for NetworkResources or ISY Query buttons
if not hasattr(self._node, "status_events") or not hasattr(self._node, "isy"):
return
self._availability_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: NodeProperty, key: str) -> None:
"""Handle the update event from the ISY Node."""
# Watch for node availability/enabled changes only
self._node_enabled = getattr(self._node, TAG_ENABLED, True)
self.async_write_ha_state()
class ISYNodeQueryButtonEntity(ISYNodeButtonEntity): class ISYNodeQueryButtonEntity(ISYNodeButtonEntity):

View file

@ -1,17 +1,22 @@
"""Representation of ISYEntity Types.""" """Representation of ISYEntity Types."""
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any, cast
from pyisy.constants import ( from pyisy.constants import (
ATTR_ACTION,
ATTR_CONTROL,
COMMAND_FRIENDLY_NAME, COMMAND_FRIENDLY_NAME,
EMPTY_TIME, EMPTY_TIME,
EVENT_PROPS_IGNORED, EVENT_PROPS_IGNORED,
NC_NODE_ENABLED,
PROTO_INSTEON, PROTO_INSTEON,
PROTO_ZWAVE, PROTO_ZWAVE,
TAG_ADDRESS,
TAG_ENABLED,
) )
from pyisy.helpers import EventListener, NodeProperty from pyisy.helpers import EventListener, NodeProperty
from pyisy.nodes import Group, Node from pyisy.nodes import Group, Node, NodeChangedEvent
from pyisy.programs import Program from pyisy.programs import Program
from pyisy.variables import Variable from pyisy.variables import Variable
@ -35,7 +40,7 @@ class ISYEntity(Entity):
node: Node | Group | Variable | Program, node: Node | Group | Variable | Program,
device_info: DeviceInfo | None = None, device_info: DeviceInfo | None = None,
) -> None: ) -> None:
"""Initialize the insteon device.""" """Initialize the ISY/IoX entity."""
self._node = node self._node = node
self._attr_name = node.name self._attr_name = node.name
if device_info is None: if device_info is None:
@ -82,6 +87,22 @@ class ISYEntity(Entity):
class ISYNodeEntity(ISYEntity): class ISYNodeEntity(ISYEntity):
"""Representation of a ISY Nodebase (Node/Group) entity.""" """Representation of a ISY Nodebase (Node/Group) entity."""
def __init__(
self,
node: Node | Group | Variable | Program,
device_info: DeviceInfo | None = None,
) -> None:
"""Initialize the ISY/IoX node entity."""
super().__init__(node, device_info=device_info)
if node.address == node.primary_node:
self._attr_has_entity_name = True
self._attr_name = None
@property
def available(self) -> bool:
"""Return entity availability."""
return getattr(self._node, TAG_ENABLED, True)
@property @property
def extra_state_attributes(self) -> dict: def extra_state_attributes(self) -> dict:
"""Get the state attributes for the device. """Get the state attributes for the device.
@ -215,16 +236,31 @@ class ISYAuxControlEntity(Entity):
self._attr_has_entity_name = node.address == node.primary_node self._attr_has_entity_name = node.address == node.primary_node
self._attr_unique_id = unique_id self._attr_unique_id = unique_id
self._attr_device_info = device_info self._attr_device_info = device_info
self._change_handler: EventListener | None = None self._change_handler: EventListener = None
self._availability_handler: EventListener = None
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Subscribe to the node control change events.""" """Subscribe to the node control change events."""
self._change_handler = self._node.control_events.subscribe(self.async_on_update) self._change_handler = self._node.control_events.subscribe(
self.async_on_update,
event_filter={ATTR_CONTROL: self._control},
key=self.unique_id,
)
self._availability_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 @callback
def async_on_update(self, event: NodeProperty) -> None: def async_on_update(self, event: NodeProperty | NodeChangedEvent, key: str) -> None:
"""Handle a control event from the ISY Node.""" """Handle a control event from the ISY Node."""
# Only watch for our control changing or the node being enabled/disabled
if event.control != self._control:
return
self.async_write_ha_state() self.async_write_ha_state()
@property
def available(self) -> bool:
"""Return entity availability."""
return cast(bool, self._node.enabled)

View file

@ -41,7 +41,6 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up ISY/IoX select entities from config entry.""" """Set up ISY/IoX select entities from config entry."""
isy_data: IsyData = hass.data[DOMAIN][config_entry.entry_id] isy_data: IsyData = hass.data[DOMAIN][config_entry.entry_id]
isy = isy_data.root
device_info = isy_data.devices device_info = isy_data.devices
entities: list[ISYAuxControlIndexSelectEntity | ISYRampRateSelectEntity] = [] entities: list[ISYAuxControlIndexSelectEntity | ISYRampRateSelectEntity] = []
@ -68,7 +67,7 @@ async def async_setup_entry(
entity_detail = { entity_detail = {
"node": node, "node": node,
"control": control, "control": control,
"unique_id": f"{isy.uuid}_{node.address}_{control}", "unique_id": f"{isy_data.uid_base(node)}_{control}",
"description": description, "description": description,
"device_info": device_info.get(node.primary_node), "device_info": device_info.get(node.primary_node),
} }

View file

@ -4,8 +4,11 @@ from __future__ import annotations
from typing import Any, cast from typing import Any, cast
from pyisy.constants import ( from pyisy.constants import (
ATTR_ACTION,
ATTR_CONTROL,
COMMAND_FRIENDLY_NAME, COMMAND_FRIENDLY_NAME,
ISY_VALUE_UNKNOWN, ISY_VALUE_UNKNOWN,
NC_NODE_ENABLED,
PROP_BATTERY_LEVEL, PROP_BATTERY_LEVEL,
PROP_COMMS_ERROR, PROP_COMMS_ERROR,
PROP_ENERGY_MODE, PROP_ENERGY_MODE,
@ -15,9 +18,10 @@ from pyisy.constants import (
PROP_RAMP_RATE, PROP_RAMP_RATE,
PROP_STATUS, PROP_STATUS,
PROP_TEMPERATURE, PROP_TEMPERATURE,
TAG_ADDRESS,
) )
from pyisy.helpers import NodeProperty from pyisy.helpers import EventListener, NodeProperty
from pyisy.nodes import Node from pyisy.nodes import Node, NodeChangedEvent
from pyisy.variables import Variable from pyisy.variables import Variable
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
@ -242,6 +246,8 @@ class ISYAuxSensorEntity(ISYSensorEntity):
self._attr_device_class = ISY_CONTROL_TO_DEVICE_CLASS.get(control) self._attr_device_class = ISY_CONTROL_TO_DEVICE_CLASS.get(control)
self._attr_state_class = ISY_CONTROL_TO_STATE_CLASS.get(control) self._attr_state_class = ISY_CONTROL_TO_STATE_CLASS.get(control)
self._attr_unique_id = unique_id self._attr_unique_id = unique_id
self._change_handler: EventListener = None
self._availability_handler: EventListener = None
name = COMMAND_FRIENDLY_NAME.get(self._control, self._control) name = COMMAND_FRIENDLY_NAME.get(self._control, self._control)
self._attr_name = f"{node.name} {name.replace('_', ' ').title()}" self._attr_name = f"{node.name} {name.replace('_', ' ').title()}"
@ -266,15 +272,27 @@ class ISYAuxSensorEntity(ISYSensorEntity):
this control is changed on the device and prevent duplicate firing this control is changed on the device and prevent duplicate firing
of `isy994_control` events. of `isy994_control` events.
""" """
self._change_handler = self._node.control_events.subscribe(self.async_on_update) self._change_handler = self._node.control_events.subscribe(
self.async_on_update, event_filter={ATTR_CONTROL: self._control}
)
self._availability_handler = self._node.isy.nodes.status_events.subscribe(
self.async_on_update,
event_filter={
TAG_ADDRESS: self._node.address,
ATTR_ACTION: NC_NODE_ENABLED,
},
)
@callback @callback
def async_on_update(self, event: NodeProperty) -> None: def async_on_update(self, event: NodeProperty | NodeChangedEvent) -> None:
"""Handle a control event from the ISY Node.""" """Handle a control event from the ISY Node."""
if event.control != self._control:
return
self.async_write_ha_state() self.async_write_ha_state()
@property
def available(self) -> bool:
"""Return entity availability."""
return cast(bool, self._node.enabled)
class ISYSensorVariableEntity(ISYEntity, SensorEntity): class ISYSensorVariableEntity(ISYEntity, SensorEntity):
"""Representation of an ISY variable as a sensor device.""" """Representation of an ISY variable as a sensor device."""

View file

@ -9,27 +9,27 @@ from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import _LOGGER, DOMAIN from .const import _LOGGER, DOMAIN
from .entity import ISYNodeEntity, ISYProgramEntity from .entity import ISYNodeEntity, ISYProgramEntity
from .models import IsyData
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up the ISY switch platform.""" """Set up the ISY switch platform."""
isy_data = hass.data[DOMAIN][entry.entry_id] isy_data: IsyData = hass.data[DOMAIN][entry.entry_id]
entities: list[ISYSwitchProgramEntity | ISYSwitchEntity] = [] entities: list[ISYSwitchProgramEntity | ISYSwitchEntity] = []
devices: dict[str, DeviceInfo] = isy_data.devices device_info = isy_data.devices
for node in isy_data.nodes[Platform.SWITCH]: for node in isy_data.nodes[Platform.SWITCH]:
primary = node.primary_node primary = node.primary_node
if node.protocol == PROTO_GROUP and len(node.controllers) == 1: if node.protocol == PROTO_GROUP and len(node.controllers) == 1:
# If Group has only 1 Controller, link to that device instead of the hub # If Group has only 1 Controller, link to that device instead of the hub
primary = node.isy.nodes.get_by_id(node.controllers[0]).primary_node primary = node.isy.nodes.get_by_id(node.controllers[0]).primary_node
entities.append(ISYSwitchEntity(node, devices.get(primary))) entities.append(ISYSwitchEntity(node, device_info.get(primary)))
for name, status, actions in isy_data.programs[Platform.SWITCH]: for name, status, actions in isy_data.programs[Platform.SWITCH]:
entities.append(ISYSwitchProgramEntity(name, status, actions)) entities.append(ISYSwitchProgramEntity(name, status, actions))