Support availability for ISY994 devices (#85928)
This commit is contained in:
parent
b81453cb6b
commit
dfc33f858a
5 changed files with 111 additions and 23 deletions
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue