Skip to content

Commit

Permalink
Bump zigpy to 0.44.1 and zha-quirks to 0.0.69 (#68921)
Browse files Browse the repository at this point in the history
* Make unit tests pass

* Flip response type check to not rely on it being a list
zigpy/zigpy#716 (comment)

* Bump zigpy and quirks versions to ZCLR8 releases

* Fix renamed zigpy cluster attributes

* Handle the default response for ZLL `get_group_identifiers`

* Add more error context to `stage failed` errors

* Fix unit test returning lists as ZCL request responses

* Always load quirks when testing ZHA

* Bump zha-quirks to 0.0.69
  • Loading branch information
puddly authored and balloob committed Mar 31, 2022
1 parent 78d8bc6 commit a8ad329
Show file tree
Hide file tree
Showing 31 changed files with 248 additions and 122 deletions.
6 changes: 3 additions & 3 deletions homeassistant/components/zha/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ async def websocket_device_cluster_attributes(
)
if attributes is not None:
for attr_id, attr in attributes.items():
cluster_attributes.append({ID: attr_id, ATTR_NAME: attr[0]})
cluster_attributes.append({ID: attr_id, ATTR_NAME: attr.name})
_LOGGER.debug(
"Requested attributes for: %s: %s, %s: '%s', %s: %s, %s: %s",
ATTR_CLUSTER_ID,
Expand Down Expand Up @@ -700,15 +700,15 @@ async def websocket_device_cluster_commands(
{
TYPE: CLIENT,
ID: cmd_id,
ATTR_NAME: cmd[0],
ATTR_NAME: cmd.name,
}
)
for cmd_id, cmd in commands[CLUSTER_COMMANDS_SERVER].items():
cluster_commands.append(
{
TYPE: CLUSTER_COMMAND_SERVER,
ID: cmd_id,
ATTR_NAME: cmd[0],
ATTR_NAME: cmd.name,
}
)
_LOGGER.debug(
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/zha/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ def __init__(self, unique_id, zha_device, channels, **kwargs):
@property
def current_temperature(self):
"""Return the current temperature."""
if self._thrm.local_temp is None:
if self._thrm.local_temperature is None:
return None
return self._thrm.local_temp / ZCL_TEMP
return self._thrm.local_temperature / ZCL_TEMP

@property
def extra_state_attributes(self):
Expand Down Expand Up @@ -272,7 +272,7 @@ def hvac_mode(self) -> str | None:
@property
def hvac_modes(self) -> tuple[str, ...]:
"""Return the list of available HVAC operation modes."""
return SEQ_OF_OPERATION.get(self._thrm.ctrl_seqe_of_oper, (HVAC_MODE_OFF,))
return SEQ_OF_OPERATION.get(self._thrm.ctrl_sequence_of_oper, (HVAC_MODE_OFF,))

@property
def precision(self):
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/zha/core/channels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,9 @@ async def _throttle(coro: Coroutine[Any, Any, None]) -> None:
results = await asyncio.gather(*tasks, return_exceptions=True)
for channel, outcome in zip(channels, results):
if isinstance(outcome, Exception):
channel.warning("'%s' stage failed: %s", func_name, str(outcome))
channel.warning(
"'%s' stage failed: %s", func_name, str(outcome), exc_info=outcome
)
continue
channel.debug("'%s' stage succeeded", func_name)

Expand Down
34 changes: 22 additions & 12 deletions homeassistant/components/zha/core/channels/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any

import zigpy.exceptions
from zigpy.zcl.foundation import Status
from zigpy.zcl.foundation import ConfigureReportingResponseRecord, Status

from homeassistant.const import ATTR_COMMAND
from homeassistant.core import callback
Expand Down Expand Up @@ -111,7 +111,7 @@ def __init__(
if not hasattr(self, "_value_attribute") and self.REPORT_CONFIG:
attr = self.REPORT_CONFIG[0].get("attr")
if isinstance(attr, str):
self.value_attribute = self.cluster.attridx.get(attr)
self.value_attribute = self.cluster.attributes_by_name.get(attr)
else:
self.value_attribute = attr
self._status = ChannelStatus.CREATED
Expand Down Expand Up @@ -260,7 +260,7 @@ def _configure_reporting_status(
self, attrs: dict[int | str, tuple], res: list | tuple
) -> None:
"""Parse configure reporting result."""
if not isinstance(res, list):
if isinstance(res, (Exception, ConfigureReportingResponseRecord)):
# assume default response
self.debug(
"attr reporting for '%s' on '%s': %s",
Expand Down Expand Up @@ -345,7 +345,7 @@ def attribute_updated(self, attrid, value):
self.async_send_signal(
f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}",
attrid,
self.cluster.attributes.get(attrid, [attrid])[0],
self._get_attribute_name(attrid),
value,
)

Expand All @@ -368,6 +368,12 @@ def zha_send_event(self, command: str, args: int | dict) -> None:
async def async_update(self):
"""Retrieve latest state from cluster."""

def _get_attribute_name(self, attrid: int) -> str | int:
if attrid not in self.cluster.attributes:
return attrid

return self.cluster.attributes[attrid].name

async def get_attribute_value(self, attribute, from_cache=True):
"""Get the value for an attribute."""
manufacturer = None
Expand Down Expand Up @@ -421,11 +427,11 @@ async def _get_attributes(

get_attributes = partialmethod(_get_attributes, False)

def log(self, level, msg, *args):
def log(self, level, msg, *args, **kwargs):
"""Log a message."""
msg = f"[%s:%s]: {msg}"
args = (self._ch_pool.nwk, self._id) + args
_LOGGER.log(level, msg, *args)
_LOGGER.log(level, msg, *args, **kwargs)

def __getattr__(self, name):
"""Get attribute or a decorated cluster command."""
Expand Down Expand Up @@ -479,11 +485,11 @@ async def async_configure(self):
"""Configure channel."""
self._status = ChannelStatus.CONFIGURED

def log(self, level, msg, *args):
def log(self, level, msg, *args, **kwargs):
"""Log a message."""
msg = f"[%s:ZDO](%s): {msg}"
args = (self._zha_device.nwk, self._zha_device.model) + args
_LOGGER.log(level, msg, *args)
_LOGGER.log(level, msg, *args, **kwargs)


class ClientChannel(ZigbeeChannel):
Expand All @@ -492,13 +498,17 @@ class ClientChannel(ZigbeeChannel):
@callback
def attribute_updated(self, attrid, value):
"""Handle an attribute updated on this cluster."""

try:
attr_name = self._cluster.attributes[attrid].name
except KeyError:
attr_name = "Unknown"

self.zha_send_event(
SIGNAL_ATTR_UPDATED,
{
ATTR_ATTRIBUTE_ID: attrid,
ATTR_ATTRIBUTE_NAME: self._cluster.attributes.get(attrid, ["Unknown"])[
0
],
ATTR_ATTRIBUTE_NAME: attr_name,
ATTR_VALUE: value,
},
)
Expand All @@ -510,4 +520,4 @@ def cluster_command(self, tsn, command_id, args):
self._cluster.server_commands is not None
and self._cluster.server_commands.get(command_id) is not None
):
self.zha_send_event(self._cluster.server_commands.get(command_id)[0], args)
self.zha_send_event(self._cluster.server_commands[command_id].name, args)
7 changes: 4 additions & 3 deletions homeassistant/components/zha/core/channels/closures.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def cluster_command(self, tsn, command_id, args):
):
return

command_name = self._cluster.client_commands.get(command_id, [command_id])[0]
command_name = self._cluster.client_commands[command_id].name

if command_name == "operation_event_notification":
self.zha_send_event(
command_name,
Expand All @@ -47,7 +48,7 @@ def cluster_command(self, tsn, command_id, args):
@callback
def attribute_updated(self, attrid, value):
"""Handle attribute update from lock cluster."""
attr_name = self.cluster.attributes.get(attrid, [attrid])[0]
attr_name = self._get_attribute_name(attrid)
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
Expand Down Expand Up @@ -140,7 +141,7 @@ async def async_update(self):
@callback
def attribute_updated(self, attrid, value):
"""Handle attribute update from window_covering cluster."""
attr_name = self.cluster.attributes.get(attrid, [attrid])[0]
attr_name = self._get_attribute_name(attrid)
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
Expand Down
14 changes: 11 additions & 3 deletions homeassistant/components/zha/core/channels/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async def async_set_present_value(self, value: float) -> bool:
except zigpy.exceptions.ZigbeeException as ex:
self.error("Could not set value: %s", ex)
return False
if isinstance(res, list) and all(
if not isinstance(res, Exception) and all(
record.status == Status.SUCCESS for record in res[0]
):
return True
Expand Down Expand Up @@ -380,7 +380,11 @@ def cluster_command(
self, tsn: int, command_id: int, args: list[Any] | None
) -> None:
"""Handle OTA commands."""
cmd_name = self.cluster.server_commands.get(command_id, [command_id])[0]
if command_id in self.cluster.server_commands:
cmd_name = self.cluster.server_commands[command_id].name
else:
cmd_name = command_id

signal_id = self._ch_pool.unique_id.split("-")[0]
if cmd_name == "query_next_image":
self.async_send_signal(SIGNAL_UPDATE_DEVICE.format(signal_id), args[3])
Expand Down Expand Up @@ -418,7 +422,11 @@ def cluster_command(
self, tsn: int, command_id: int, args: list[Any] | None
) -> None:
"""Handle commands received to this cluster."""
cmd_name = self.cluster.client_commands.get(command_id, [command_id])[0]
if command_id in self.cluster.client_commands:
cmd_name = self.cluster.client_commands[command_id].name
else:
cmd_name = command_id

self.debug("Received %s tsn command '%s': %s", tsn, cmd_name, args)
self.zha_send_event(cmd_name, args)
if cmd_name == "checkin":
Expand Down
18 changes: 9 additions & 9 deletions homeassistant/components/zha/core/channels/hvac.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async def async_update(self) -> None:
@callback
def attribute_updated(self, attrid: int, value: Any) -> None:
"""Handle attribute update from fan cluster."""
attr_name = self.cluster.attributes.get(attrid, [attrid])[0]
attr_name = self._get_attribute_name(attrid)
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
Expand All @@ -90,7 +90,7 @@ class ThermostatChannel(ZigbeeChannel):
"""Thermostat channel."""

REPORT_CONFIG = (
{"attr": "local_temp", "config": REPORT_CONFIG_CLIMATE},
{"attr": "local_temperature", "config": REPORT_CONFIG_CLIMATE},
{"attr": "occupied_cooling_setpoint", "config": REPORT_CONFIG_CLIMATE},
{"attr": "occupied_heating_setpoint", "config": REPORT_CONFIG_CLIMATE},
{"attr": "unoccupied_cooling_setpoint", "config": REPORT_CONFIG_CLIMATE},
Expand All @@ -107,7 +107,7 @@ class ThermostatChannel(ZigbeeChannel):
"abs_max_heat_setpoint_limit": True,
"abs_min_cool_setpoint_limit": True,
"abs_max_cool_setpoint_limit": True,
"ctrl_seqe_of_oper": False,
"ctrl_sequence_of_oper": False,
"max_cool_setpoint_limit": True,
"max_heat_setpoint_limit": True,
"min_cool_setpoint_limit": True,
Expand Down Expand Up @@ -135,9 +135,9 @@ def abs_min_heat_setpoint_limit(self) -> int:
return self.cluster.get("abs_min_heat_setpoint_limit", 700)

@property
def ctrl_seqe_of_oper(self) -> int:
def ctrl_sequence_of_oper(self) -> int:
"""Control Sequence of operations attribute."""
return self.cluster.get("ctrl_seqe_of_oper", 0xFF)
return self.cluster.get("ctrl_sequence_of_oper", 0xFF)

@property
def max_cool_setpoint_limit(self) -> int:
Expand Down Expand Up @@ -172,9 +172,9 @@ def min_heat_setpoint_limit(self) -> int:
return sp_limit

@property
def local_temp(self) -> int | None:
def local_temperature(self) -> int | None:
"""Thermostat temperature."""
return self.cluster.get("local_temp")
return self.cluster.get("local_temperature")

@property
def occupancy(self) -> int | None:
Expand Down Expand Up @@ -229,7 +229,7 @@ def unoccupied_heating_setpoint(self) -> int | None:
@callback
def attribute_updated(self, attrid, value):
"""Handle attribute update cluster."""
attr_name = self.cluster.attributes.get(attrid, [attrid])[0]
attr_name = self._get_attribute_name(attrid)
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
Expand Down Expand Up @@ -300,7 +300,7 @@ async def write_attributes(self, data, **kwargs):
@staticmethod
def check_result(res: list) -> bool:
"""Normalize the result."""
if not isinstance(res, list):
if isinstance(res, Exception):
return False

return all(record.status == Status.SUCCESS for record in res[0])
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/zha/core/channels/lightlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import zigpy.exceptions
from zigpy.zcl.clusters import lightlink
from zigpy.zcl.foundation import GENERAL_COMMANDS, GeneralCommand

from .. import registries
from .base import ChannelStatus, ZigbeeChannel
Expand Down Expand Up @@ -30,11 +31,16 @@ async def async_configure(self) -> None:
return

try:
_, _, groups = await self.cluster.get_group_identifiers(0)
rsp = await self.cluster.get_group_identifiers(0)
except (zigpy.exceptions.ZigbeeException, asyncio.TimeoutError) as exc:
self.warning("Couldn't get list of groups: %s", str(exc))
return

if isinstance(rsp, GENERAL_COMMANDS[GeneralCommand.Default_Response].schema):
groups = []
else:
groups = rsp.group_info_records

if groups:
for group in groups:
self.debug("Adding coordinator to 0x%04x group id", group.group_id)
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/zha/core/channels/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def __init__(
def cluster_command(self, tsn, command_id, args) -> None:
"""Handle commands received to this cluster."""
self.warning(
"received command %s", self._cluster.server_commands.get(command_id)[NAME]
"received command %s", self._cluster.server_commands[command_id].name
)
self.command_map[command_id](*args)

Expand All @@ -94,7 +94,7 @@ def arm(self, arm_mode: int, code: str, zone_id: int):
mode = AceCluster.ArmMode(arm_mode)

self.zha_send_event(
self._cluster.server_commands.get(IAS_ACE_ARM)[NAME],
self._cluster.server_commands[IAS_ACE_ARM].name,
{
"arm_mode": mode.value,
"arm_mode_description": mode.name,
Expand Down Expand Up @@ -190,7 +190,7 @@ def _handle_arm(
def _bypass(self, zone_list, code) -> None:
"""Handle the IAS ACE bypass command."""
self.zha_send_event(
self._cluster.server_commands.get(IAS_ACE_BYPASS)[NAME],
self._cluster.server_commands[IAS_ACE_BYPASS].name,
{"zone_list": zone_list, "code": code},
)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/zha/core/channels/smartenergy.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class Metering(ZigbeeChannel):
"divisor": True,
"metering_device_type": True,
"multiplier": True,
"summa_formatting": True,
"summation_formatting": True,
"unit_of_measure": True,
}

Expand Down Expand Up @@ -159,7 +159,7 @@ async def async_initialize_channel_specific(self, from_cache: bool) -> None:
self._format_spec = self.get_formatting(fmting)

fmting = self.cluster.get(
"summa_formatting", 0xF9
"summation_formatting", 0xF9
) # 1 digit to the right, 15 digits to the left
self._summa_format = self.get_formatting(fmting)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/zha/core/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,8 +783,8 @@ async def _async_group_binding_operation(
fmt = f"{log_msg[1]} completed: %s"
zdo.debug(fmt, *(log_msg[2] + (outcome,)))

def log(self, level: int, msg: str, *args: Any) -> None:
def log(self, level: int, msg: str, *args: Any, **kwargs: dict) -> None:
"""Log a message."""
msg = f"[%s](%s): {msg}"
args = (self.nwk, self.model) + args
_LOGGER.log(level, msg, *args)
_LOGGER.log(level, msg, *args, **kwargs)
Loading

0 comments on commit a8ad329

Please sign in to comment.