Skip to content

Commit

Permalink
Fix attribute reporting config failures in ZHA (home-assistant#91403)
Browse files Browse the repository at this point in the history
  • Loading branch information
puddly authored Apr 15, 2023
1 parent adc8a13 commit a9db39a
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 17 deletions.
37 changes: 20 additions & 17 deletions homeassistant/components/zha/core/channels/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
class AttrReportConfig(TypedDict, total=True):
"""Configuration to report for the attributes."""

# Could be either an attribute name or attribute id
attr: str | int
# An attribute name
attr: str
# The config for the attribute reporting configuration consists of a tuple for
# (minimum_reported_time_interval_s, maximum_reported_time_interval_s, value_delta)
config: tuple[int, int, int | float]
Expand Down Expand Up @@ -130,15 +130,13 @@ def __init__(self, cluster: zigpy.zcl.Cluster, ch_pool: ChannelPool) -> None:
unique_id = ch_pool.unique_id.replace("-", ":")
self._unique_id = f"{unique_id}:0x{cluster.cluster_id:04x}"
if not hasattr(self, "_value_attribute") and self.REPORT_CONFIG:
attr = self.REPORT_CONFIG[0].get("attr")
if isinstance(attr, str):
attribute: ZCLAttributeDef = self.cluster.attributes_by_name.get(attr)
if attribute is not None:
self.value_attribute = attribute.id
else:
self.value_attribute = None
attr_def: ZCLAttributeDef | None = self.cluster.attributes_by_name.get(
self.REPORT_CONFIG[0]["attr"]
)
if attr_def is not None:
self.value_attribute = attr_def.id
else:
self.value_attribute = attr
self.value_attribute = None
self._status = ChannelStatus.CREATED
self._cluster.add_listener(self)
self.data_cache: dict[str, Enum] = {}
Expand Down Expand Up @@ -233,7 +231,12 @@ async def configure_reporting(self) -> None:

for attr_report in self.REPORT_CONFIG:
attr, config = attr_report["attr"], attr_report["config"]
attr_name = self.cluster.attributes.get(attr, [attr])[0]

try:
attr_name = self.cluster.find_attribute(attr).name
except KeyError:
attr_name = attr

event_data[attr_name] = {
"min": config[0],
"max": config[1],
Expand Down Expand Up @@ -282,7 +285,7 @@ async def configure_reporting(self) -> None:
)

def _configure_reporting_status(
self, attrs: dict[int | str, tuple[int, int, float | int]], res: list | tuple
self, attrs: dict[str, tuple[int, int, float | int]], res: list | tuple
) -> None:
"""Parse configure reporting result."""
if isinstance(res, (Exception, ConfigureReportingResponseRecord)):
Expand All @@ -304,14 +307,14 @@ def _configure_reporting_status(
return

failed = [
self.cluster.attributes.get(r.attrid, [r.attrid])[0]
for r in res
if r.status != Status.SUCCESS
self.cluster.find_attribute(record.attrid).name
for record in res
if record.status != Status.SUCCESS
]
attributes = {self.cluster.attributes.get(r, [r])[0] for r in attrs}

self.debug(
"Successfully configured reporting for '%s' on '%s' cluster",
attributes - set(failed),
set(attrs) - set(failed),
self.name,
)
self.debug(
Expand Down
56 changes: 56 additions & 0 deletions tests/components/zha/test_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
from unittest.mock import AsyncMock, patch

import pytest
import zigpy.endpoint
import zigpy.profiles.zha
import zigpy.types as t
from zigpy.zcl import foundation
import zigpy.zcl.clusters
import zigpy.zdo.types as zdo_t

import homeassistant.components.zha.core.channels as zha_channels
import homeassistant.components.zha.core.channels.base as base_channels
Expand Down Expand Up @@ -726,3 +729,56 @@ async def test_cluster_no_ep_attribute(m1, zha_device_mock) -> None:
pools = {pool.id: pool for pool in channels.pools}
assert "1:0x042e" in pools[1].all_channels
assert pools[1].all_channels["1:0x042e"].name


async def test_configure_reporting(hass: HomeAssistant) -> None:
"""Test setting up a channel and configuring attribute reporting in two batches."""

class TestZigbeeChannel(base_channels.ZigbeeChannel):
BIND = True
REPORT_CONFIG = (
# By name
base_channels.AttrReportConfig(attr="current_x", config=(1, 60, 1)),
base_channels.AttrReportConfig(attr="current_hue", config=(1, 60, 2)),
base_channels.AttrReportConfig(attr="color_temperature", config=(1, 60, 3)),
base_channels.AttrReportConfig(attr="current_y", config=(1, 60, 4)),
)

mock_ep = mock.AsyncMock(spec_set=zigpy.endpoint.Endpoint)
mock_ep.device.zdo = AsyncMock()

cluster = zigpy.zcl.clusters.lighting.Color(mock_ep)
cluster.bind = AsyncMock(
spec_set=cluster.bind,
return_value=[zdo_t.Status.SUCCESS], # ZDOCmd.Bind_rsp
)
cluster.configure_reporting_multiple = AsyncMock(
spec_set=cluster.configure_reporting_multiple,
return_value=[
foundation.ConfigureReportingResponseRecord(
status=foundation.Status.SUCCESS
)
],
)

ch_pool = mock.AsyncMock(spec_set=zha_channels.ChannelPool)
ch_pool.skip_configuration = False

channel = TestZigbeeChannel(cluster, ch_pool)
await channel.async_configure()

# Since we request reporting for five attributes, we need to make two calls (3 + 1)
assert cluster.configure_reporting_multiple.mock_calls == [
mock.call(
{
"current_x": (1, 60, 1),
"current_hue": (1, 60, 2),
"color_temperature": (1, 60, 3),
}
),
mock.call(
{
"current_y": (1, 60, 4),
}
),
]

0 comments on commit a9db39a

Please sign in to comment.