Better ZHA device reconfiguration (#49672)
* initial take * cleanup * fix mock for configure_reporting
This commit is contained in:
parent
6bc0fb2e42
commit
b91d2be00b
5 changed files with 76 additions and 16 deletions
|
@ -54,6 +54,7 @@ from .core.const import (
|
||||||
WARNING_DEVICE_SQUAWK_MODE_ARMED,
|
WARNING_DEVICE_SQUAWK_MODE_ARMED,
|
||||||
WARNING_DEVICE_STROBE_HIGH,
|
WARNING_DEVICE_STROBE_HIGH,
|
||||||
WARNING_DEVICE_STROBE_YES,
|
WARNING_DEVICE_STROBE_YES,
|
||||||
|
ZHA_CHANNEL_MSG,
|
||||||
ZHA_CONFIG_SCHEMAS,
|
ZHA_CONFIG_SCHEMAS,
|
||||||
)
|
)
|
||||||
from .core.group import GroupMember
|
from .core.group import GroupMember
|
||||||
|
@ -468,34 +469,21 @@ async def websocket_reconfigure_node(hass, connection, msg):
|
||||||
zha_gateway: ZhaGatewayType = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
|
zha_gateway: ZhaGatewayType = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
|
||||||
ieee = msg[ATTR_IEEE]
|
ieee = msg[ATTR_IEEE]
|
||||||
device: ZhaDeviceType = zha_gateway.get_device(ieee)
|
device: ZhaDeviceType = zha_gateway.get_device(ieee)
|
||||||
ieee_str = str(device.ieee)
|
|
||||||
nwk_str = device.nwk.__repr__()
|
|
||||||
|
|
||||||
class DeviceLogFilterer(logging.Filter):
|
|
||||||
"""Log filterer that limits messages to the specified device."""
|
|
||||||
|
|
||||||
def filter(self, record):
|
|
||||||
message = record.getMessage()
|
|
||||||
return nwk_str in message or ieee_str in message
|
|
||||||
|
|
||||||
filterer = DeviceLogFilterer()
|
|
||||||
|
|
||||||
async def forward_messages(data):
|
async def forward_messages(data):
|
||||||
"""Forward events to websocket."""
|
"""Forward events to websocket."""
|
||||||
connection.send_message(websocket_api.event_message(msg["id"], data))
|
connection.send_message(websocket_api.event_message(msg["id"], data))
|
||||||
|
|
||||||
remove_dispatcher_function = async_dispatcher_connect(
|
remove_dispatcher_function = async_dispatcher_connect(
|
||||||
hass, "zha_gateway_message", forward_messages
|
hass, ZHA_CHANNEL_MSG, forward_messages
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_cleanup() -> None:
|
def async_cleanup() -> None:
|
||||||
"""Remove signal listener and turn off debug mode."""
|
"""Remove signal listener."""
|
||||||
zha_gateway.async_disable_debug_mode(filterer=filterer)
|
|
||||||
remove_dispatcher_function()
|
remove_dispatcher_function()
|
||||||
|
|
||||||
connection.subscriptions[msg["id"]] = async_cleanup
|
connection.subscriptions[msg["id"]] = async_cleanup
|
||||||
zha_gateway.async_enable_debug_mode(filterer=filterer)
|
|
||||||
|
|
||||||
_LOGGER.debug("Reconfiguring node with ieee_address: %s", ieee)
|
_LOGGER.debug("Reconfiguring node with ieee_address: %s", ieee)
|
||||||
hass.async_create_task(device.async_configure())
|
hass.async_create_task(device.async_configure())
|
||||||
|
|
|
@ -130,6 +130,13 @@ class Channels:
|
||||||
await self.zdo_channel.async_configure()
|
await self.zdo_channel.async_configure()
|
||||||
self.zdo_channel.debug("'async_configure' stage succeeded")
|
self.zdo_channel.debug("'async_configure' stage succeeded")
|
||||||
await asyncio.gather(*(pool.async_configure() for pool in self.pools))
|
await asyncio.gather(*(pool.async_configure() for pool in self.pools))
|
||||||
|
async_dispatcher_send(
|
||||||
|
self.zha_device.hass,
|
||||||
|
const.ZHA_CHANNEL_MSG,
|
||||||
|
{
|
||||||
|
const.ATTR_TYPE: const.ZHA_CHANNEL_CFG_DONE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_new_entity(
|
def async_new_entity(
|
||||||
|
|
|
@ -11,6 +11,7 @@ import zigpy.exceptions
|
||||||
|
|
||||||
from homeassistant.const import ATTR_COMMAND
|
from homeassistant.const import ATTR_COMMAND
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
from .. import typing as zha_typing
|
from .. import typing as zha_typing
|
||||||
from ..const import (
|
from ..const import (
|
||||||
|
@ -18,10 +19,15 @@ from ..const import (
|
||||||
ATTR_ATTRIBUTE_ID,
|
ATTR_ATTRIBUTE_ID,
|
||||||
ATTR_ATTRIBUTE_NAME,
|
ATTR_ATTRIBUTE_NAME,
|
||||||
ATTR_CLUSTER_ID,
|
ATTR_CLUSTER_ID,
|
||||||
|
ATTR_TYPE,
|
||||||
ATTR_UNIQUE_ID,
|
ATTR_UNIQUE_ID,
|
||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
CHANNEL_ZDO,
|
CHANNEL_ZDO,
|
||||||
SIGNAL_ATTR_UPDATED,
|
SIGNAL_ATTR_UPDATED,
|
||||||
|
ZHA_CHANNEL_MSG,
|
||||||
|
ZHA_CHANNEL_MSG_BIND,
|
||||||
|
ZHA_CHANNEL_MSG_CFG_RPT,
|
||||||
|
ZHA_CHANNEL_MSG_DATA,
|
||||||
)
|
)
|
||||||
from ..helpers import LogMixin, safe_read
|
from ..helpers import LogMixin, safe_read
|
||||||
|
|
||||||
|
@ -148,10 +154,34 @@ class ZigbeeChannel(LogMixin):
|
||||||
try:
|
try:
|
||||||
res = await self.cluster.bind()
|
res = await self.cluster.bind()
|
||||||
self.debug("bound '%s' cluster: %s", self.cluster.ep_attribute, res[0])
|
self.debug("bound '%s' cluster: %s", self.cluster.ep_attribute, res[0])
|
||||||
|
async_dispatcher_send(
|
||||||
|
self._ch_pool.hass,
|
||||||
|
ZHA_CHANNEL_MSG,
|
||||||
|
{
|
||||||
|
ATTR_TYPE: ZHA_CHANNEL_MSG_BIND,
|
||||||
|
ZHA_CHANNEL_MSG_DATA: {
|
||||||
|
"cluster_name": self.cluster.name,
|
||||||
|
"cluster_id": self.cluster.cluster_id,
|
||||||
|
"success": res[0] == 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
except (zigpy.exceptions.ZigbeeException, asyncio.TimeoutError) as ex:
|
except (zigpy.exceptions.ZigbeeException, asyncio.TimeoutError) as ex:
|
||||||
self.debug(
|
self.debug(
|
||||||
"Failed to bind '%s' cluster: %s", self.cluster.ep_attribute, str(ex)
|
"Failed to bind '%s' cluster: %s", self.cluster.ep_attribute, str(ex)
|
||||||
)
|
)
|
||||||
|
async_dispatcher_send(
|
||||||
|
self._ch_pool.hass,
|
||||||
|
ZHA_CHANNEL_MSG,
|
||||||
|
{
|
||||||
|
ATTR_TYPE: ZHA_CHANNEL_MSG_BIND,
|
||||||
|
ZHA_CHANNEL_MSG_DATA: {
|
||||||
|
"cluster_name": self.cluster.name,
|
||||||
|
"cluster_id": self.cluster.cluster_id,
|
||||||
|
"success": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async def configure_reporting(self) -> None:
|
async def configure_reporting(self) -> None:
|
||||||
"""Configure attribute reporting for a cluster.
|
"""Configure attribute reporting for a cluster.
|
||||||
|
@ -159,6 +189,7 @@ class ZigbeeChannel(LogMixin):
|
||||||
This also swallows ZigbeeException exceptions that are thrown when
|
This also swallows ZigbeeException exceptions that are thrown when
|
||||||
devices are unreachable.
|
devices are unreachable.
|
||||||
"""
|
"""
|
||||||
|
event_data = {}
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if self.cluster.cluster_id >= 0xFC00 and self._ch_pool.manufacturer_code:
|
if self.cluster.cluster_id >= 0xFC00 and self._ch_pool.manufacturer_code:
|
||||||
kwargs["manufacturer"] = self._ch_pool.manufacturer_code
|
kwargs["manufacturer"] = self._ch_pool.manufacturer_code
|
||||||
|
@ -167,6 +198,14 @@ class ZigbeeChannel(LogMixin):
|
||||||
attr = report["attr"]
|
attr = report["attr"]
|
||||||
attr_name = self.cluster.attributes.get(attr, [attr])[0]
|
attr_name = self.cluster.attributes.get(attr, [attr])[0]
|
||||||
min_report_int, max_report_int, reportable_change = report["config"]
|
min_report_int, max_report_int, reportable_change = report["config"]
|
||||||
|
event_data[attr_name] = {
|
||||||
|
"min": min_report_int,
|
||||||
|
"max": max_report_int,
|
||||||
|
"id": attr,
|
||||||
|
"name": attr_name,
|
||||||
|
"change": reportable_change,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = await self.cluster.configure_reporting(
|
res = await self.cluster.configure_reporting(
|
||||||
attr, min_report_int, max_report_int, reportable_change, **kwargs
|
attr, min_report_int, max_report_int, reportable_change, **kwargs
|
||||||
|
@ -180,6 +219,9 @@ class ZigbeeChannel(LogMixin):
|
||||||
reportable_change,
|
reportable_change,
|
||||||
res,
|
res,
|
||||||
)
|
)
|
||||||
|
event_data[attr_name]["success"] = (
|
||||||
|
res[0][0].status == 0 or res[0][0].status == 134
|
||||||
|
)
|
||||||
except (zigpy.exceptions.ZigbeeException, asyncio.TimeoutError) as ex:
|
except (zigpy.exceptions.ZigbeeException, asyncio.TimeoutError) as ex:
|
||||||
self.debug(
|
self.debug(
|
||||||
"failed to set reporting for '%s' attr on '%s' cluster: %s",
|
"failed to set reporting for '%s' attr on '%s' cluster: %s",
|
||||||
|
@ -187,6 +229,20 @@ class ZigbeeChannel(LogMixin):
|
||||||
self.cluster.ep_attribute,
|
self.cluster.ep_attribute,
|
||||||
str(ex),
|
str(ex),
|
||||||
)
|
)
|
||||||
|
event_data[attr_name]["success"] = False
|
||||||
|
|
||||||
|
async_dispatcher_send(
|
||||||
|
self._ch_pool.hass,
|
||||||
|
ZHA_CHANNEL_MSG,
|
||||||
|
{
|
||||||
|
ATTR_TYPE: ZHA_CHANNEL_MSG_CFG_RPT,
|
||||||
|
ZHA_CHANNEL_MSG_DATA: {
|
||||||
|
"cluster_name": self.cluster.name,
|
||||||
|
"cluster_id": self.cluster.cluster_id,
|
||||||
|
"attributes": event_data,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async def async_configure(self) -> None:
|
async def async_configure(self) -> None:
|
||||||
"""Set cluster binding and attribute reporting."""
|
"""Set cluster binding and attribute reporting."""
|
||||||
|
|
|
@ -339,6 +339,11 @@ WARNING_DEVICE_SQUAWK_MODE_ARMED = 0
|
||||||
WARNING_DEVICE_SQUAWK_MODE_DISARMED = 1
|
WARNING_DEVICE_SQUAWK_MODE_DISARMED = 1
|
||||||
|
|
||||||
ZHA_DISCOVERY_NEW = "zha_discovery_new_{}"
|
ZHA_DISCOVERY_NEW = "zha_discovery_new_{}"
|
||||||
|
ZHA_CHANNEL_MSG = "zha_channel_message"
|
||||||
|
ZHA_CHANNEL_MSG_BIND = "zha_channel_bind"
|
||||||
|
ZHA_CHANNEL_MSG_CFG_RPT = "zha_channel_configure_reporting"
|
||||||
|
ZHA_CHANNEL_MSG_DATA = "zha_channel_msg_data"
|
||||||
|
ZHA_CHANNEL_CFG_DONE = "zha_channel_cfg_done"
|
||||||
ZHA_GW_MSG = "zha_gateway_message"
|
ZHA_GW_MSG = "zha_gateway_message"
|
||||||
ZHA_GW_MSG_DEVICE_FULL_INIT = "device_fully_initialized"
|
ZHA_GW_MSG_DEVICE_FULL_INIT = "device_fully_initialized"
|
||||||
ZHA_GW_MSG_DEVICE_INFO = "device_info"
|
ZHA_GW_MSG_DEVICE_INFO = "device_info"
|
||||||
|
|
|
@ -93,7 +93,11 @@ def patch_cluster(cluster):
|
||||||
return (result,)
|
return (result,)
|
||||||
|
|
||||||
cluster.bind = AsyncMock(return_value=[0])
|
cluster.bind = AsyncMock(return_value=[0])
|
||||||
cluster.configure_reporting = AsyncMock(return_value=[0])
|
cluster.configure_reporting = AsyncMock(
|
||||||
|
return_value=[
|
||||||
|
[zcl_f.ConfigureReportingResponseRecord(zcl_f.Status.SUCCESS, 0x00, 0xAABB)]
|
||||||
|
]
|
||||||
|
)
|
||||||
cluster.deserialize = Mock()
|
cluster.deserialize = Mock()
|
||||||
cluster.handle_cluster_request = Mock()
|
cluster.handle_cluster_request = Mock()
|
||||||
cluster.read_attributes = AsyncMock(wraps=cluster.read_attributes)
|
cluster.read_attributes = AsyncMock(wraps=cluster.read_attributes)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue