Bump zigpy to 0.44.1 and zha-quirks to 0.0.69 (#68921)
* Make unit tests pass * Flip response type check to not rely on it being a list https://github.com/zigpy/zigpy/pull/716#issuecomment-1025236190 * 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.69pull/68998/head
parent
398db35334
commit
0f6296e4b5
|
@ -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,
|
||||
|
@ -700,7 +700,7 @@ 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():
|
||||
|
@ -708,7 +708,7 @@ async def websocket_device_cluster_commands(
|
|||
{
|
||||
TYPE: CLUSTER_COMMAND_SERVER,
|
||||
ID: cmd_id,
|
||||
ATTR_NAME: cmd[0],
|
||||
ATTR_NAME: cmd.name,
|
||||
}
|
||||
)
|
||||
_LOGGER.debug(
|
||||
|
|
|
@ -161,9 +161,9 @@ class Thermostat(ZhaEntity, ClimateEntity):
|
|||
@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):
|
||||
|
@ -272,7 +272,7 @@ class Thermostat(ZhaEntity, ClimateEntity):
|
|||
@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):
|
||||
|
|
|
@ -346,7 +346,9 @@ class ChannelPool:
|
|||
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)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import logging
|
|||
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
|
||||
|
@ -111,7 +111,7 @@ class ZigbeeChannel(LogMixin):
|
|||
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
|
||||
|
@ -260,7 +260,7 @@ class ZigbeeChannel(LogMixin):
|
|||
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",
|
||||
|
@ -345,7 +345,7 @@ class ZigbeeChannel(LogMixin):
|
|||
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,
|
||||
)
|
||||
|
||||
|
@ -368,6 +368,12 @@ class ZigbeeChannel(LogMixin):
|
|||
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
|
||||
|
@ -421,11 +427,11 @@ class ZigbeeChannel(LogMixin):
|
|||
|
||||
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."""
|
||||
|
@ -479,11 +485,11 @@ class ZDOChannel(LogMixin):
|
|||
"""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):
|
||||
|
@ -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,
|
||||
},
|
||||
)
|
||||
|
@ -510,4 +520,4 @@ class ClientChannel(ZigbeeChannel):
|
|||
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)
|
||||
|
|
|
@ -33,7 +33,8 @@ class DoorLockChannel(ZigbeeChannel):
|
|||
):
|
||||
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,
|
||||
|
@ -47,7 +48,7 @@ class DoorLockChannel(ZigbeeChannel):
|
|||
@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
|
||||
)
|
||||
|
@ -140,7 +141,7 @@ class WindowCovering(ZigbeeChannel):
|
|||
@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
|
||||
)
|
||||
|
|
|
@ -103,7 +103,7 @@ class AnalogOutput(ZigbeeChannel):
|
|||
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
|
||||
|
@ -380,7 +380,11 @@ class Ota(ZigbeeChannel):
|
|||
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])
|
||||
|
@ -418,7 +422,11 @@ class PollControl(ZigbeeChannel):
|
|||
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":
|
||||
|
|
|
@ -70,7 +70,7 @@ class FanChannel(ZigbeeChannel):
|
|||
@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
|
||||
)
|
||||
|
@ -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},
|
||||
|
@ -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,
|
||||
|
@ -135,9 +135,9 @@ class ThermostatChannel(ZigbeeChannel):
|
|||
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:
|
||||
|
@ -172,9 +172,9 @@ class ThermostatChannel(ZigbeeChannel):
|
|||
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:
|
||||
|
@ -229,7 +229,7 @@ class ThermostatChannel(ZigbeeChannel):
|
|||
@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
|
||||
)
|
||||
|
@ -300,7 +300,7 @@ class ThermostatChannel(ZigbeeChannel):
|
|||
@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])
|
||||
|
|
|
@ -3,6 +3,7 @@ import asyncio
|
|||
|
||||
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
|
||||
|
@ -30,11 +31,16 @@ class LightLink(ZigbeeChannel):
|
|||
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)
|
||||
|
|
|
@ -85,7 +85,7 @@ class IasAce(ZigbeeChannel):
|
|||
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)
|
||||
|
||||
|
@ -94,7 +94,7 @@ class IasAce(ZigbeeChannel):
|
|||
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,
|
||||
|
@ -190,7 +190,7 @@ class IasAce(ZigbeeChannel):
|
|||
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},
|
||||
)
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,7 @@ class Metering(ZigbeeChannel):
|
|||
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)
|
||||
|
||||
|
|
|
@ -783,8 +783,8 @@ class ZHADevice(LogMixin):
|
|||
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)
|
||||
|
|
|
@ -108,11 +108,11 @@ class ZHAGroupMember(LogMixin):
|
|||
str(ex),
|
||||
)
|
||||
|
||||
def log(self, level: int, msg: str, *args: Any) -> None:
|
||||
def log(self, level: int, msg: str, *args: Any, **kwargs) -> None:
|
||||
"""Log a message."""
|
||||
msg = f"[%s](%s): {msg}"
|
||||
args = (f"0x{self._zha_group.group_id:04x}", self.endpoint_id) + args
|
||||
_LOGGER.log(level, msg, *args)
|
||||
_LOGGER.log(level, msg, *args, **kwargs)
|
||||
|
||||
|
||||
class ZHAGroup(LogMixin):
|
||||
|
@ -224,8 +224,8 @@ class ZHAGroup(LogMixin):
|
|||
group_info["members"] = [member.member_info for member in self.members]
|
||||
return group_info
|
||||
|
||||
def log(self, level: int, msg: str, *args: Any) -> None:
|
||||
def log(self, level: int, msg: str, *args: Any, **kwargs) -> None:
|
||||
"""Log a message."""
|
||||
msg = f"[%s](%s): {msg}"
|
||||
args = (self.name, self.group_id) + args
|
||||
_LOGGER.log(level, msg, *args)
|
||||
_LOGGER.log(level, msg, *args, **kwargs)
|
||||
|
|
|
@ -210,23 +210,23 @@ def reduce_attribute(
|
|||
class LogMixin:
|
||||
"""Log helper."""
|
||||
|
||||
def log(self, level, msg, *args):
|
||||
def log(self, level, msg, *args, **kwargs):
|
||||
"""Log with level."""
|
||||
raise NotImplementedError
|
||||
|
||||
def debug(self, msg, *args):
|
||||
def debug(self, msg, *args, **kwargs):
|
||||
"""Debug level log."""
|
||||
return self.log(logging.DEBUG, msg, *args)
|
||||
|
||||
def info(self, msg, *args):
|
||||
def info(self, msg, *args, **kwargs):
|
||||
"""Info level log."""
|
||||
return self.log(logging.INFO, msg, *args)
|
||||
|
||||
def warning(self, msg, *args):
|
||||
def warning(self, msg, *args, **kwargs):
|
||||
"""Warning method log."""
|
||||
return self.log(logging.WARNING, msg, *args)
|
||||
|
||||
def error(self, msg, *args):
|
||||
def error(self, msg, *args, **kwargs):
|
||||
"""Error level log."""
|
||||
return self.log(logging.ERROR, msg, *args)
|
||||
|
||||
|
|
|
@ -133,20 +133,20 @@ class ZhaCover(ZhaEntity, CoverEntity):
|
|||
async def async_open_cover(self, **kwargs):
|
||||
"""Open the window cover."""
|
||||
res = await self._cover_channel.up_open()
|
||||
if isinstance(res, list) and res[1] is Status.SUCCESS:
|
||||
if not isinstance(res, Exception) and res[1] is Status.SUCCESS:
|
||||
self.async_update_state(STATE_OPENING)
|
||||
|
||||
async def async_close_cover(self, **kwargs):
|
||||
"""Close the window cover."""
|
||||
res = await self._cover_channel.down_close()
|
||||
if isinstance(res, list) and res[1] is Status.SUCCESS:
|
||||
if not isinstance(res, Exception) and res[1] is Status.SUCCESS:
|
||||
self.async_update_state(STATE_CLOSING)
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the roller shutter to a specific position."""
|
||||
new_pos = kwargs[ATTR_POSITION]
|
||||
res = await self._cover_channel.go_to_lift_percentage(100 - new_pos)
|
||||
if isinstance(res, list) and res[1] is Status.SUCCESS:
|
||||
if not isinstance(res, Exception) and res[1] is Status.SUCCESS:
|
||||
self.async_update_state(
|
||||
STATE_CLOSING if new_pos < self._current_position else STATE_OPENING
|
||||
)
|
||||
|
@ -154,7 +154,7 @@ class ZhaCover(ZhaEntity, CoverEntity):
|
|||
async def async_stop_cover(self, **kwargs):
|
||||
"""Stop the window cover."""
|
||||
res = await self._cover_channel.stop()
|
||||
if isinstance(res, list) and res[1] is Status.SUCCESS:
|
||||
if not isinstance(res, Exception) and res[1] is Status.SUCCESS:
|
||||
self._state = STATE_OPEN if self._current_position > 0 else STATE_CLOSED
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
@ -250,7 +250,7 @@ class Shade(ZhaEntity, CoverEntity):
|
|||
async def async_open_cover(self, **kwargs):
|
||||
"""Open the window cover."""
|
||||
res = await self._on_off_channel.on()
|
||||
if not isinstance(res, list) or res[1] != Status.SUCCESS:
|
||||
if isinstance(res, Exception) or res[1] != Status.SUCCESS:
|
||||
self.debug("couldn't open cover: %s", res)
|
||||
return
|
||||
|
||||
|
@ -260,7 +260,7 @@ class Shade(ZhaEntity, CoverEntity):
|
|||
async def async_close_cover(self, **kwargs):
|
||||
"""Close the window cover."""
|
||||
res = await self._on_off_channel.off()
|
||||
if not isinstance(res, list) or res[1] != Status.SUCCESS:
|
||||
if isinstance(res, Exception) or res[1] != Status.SUCCESS:
|
||||
self.debug("couldn't open cover: %s", res)
|
||||
return
|
||||
|
||||
|
@ -274,7 +274,7 @@ class Shade(ZhaEntity, CoverEntity):
|
|||
new_pos * 255 / 100, 1
|
||||
)
|
||||
|
||||
if not isinstance(res, list) or res[1] != Status.SUCCESS:
|
||||
if isinstance(res, Exception) or res[1] != Status.SUCCESS:
|
||||
self.debug("couldn't set cover's position: %s", res)
|
||||
return
|
||||
|
||||
|
@ -284,7 +284,7 @@ class Shade(ZhaEntity, CoverEntity):
|
|||
async def async_stop_cover(self, **kwargs) -> None:
|
||||
"""Stop the cover."""
|
||||
res = await self._level_channel.stop()
|
||||
if not isinstance(res, list) or res[1] != Status.SUCCESS:
|
||||
if isinstance(res, Exception) or res[1] != Status.SUCCESS:
|
||||
self.debug("couldn't stop cover: %s", res)
|
||||
return
|
||||
|
||||
|
|
|
@ -139,11 +139,11 @@ class BaseZhaEntity(LogMixin, entity.Entity):
|
|||
)
|
||||
self._unsubs.append(unsub)
|
||||
|
||||
def log(self, level: int, msg: str, *args):
|
||||
def log(self, level: int, msg: str, *args, **kwargs):
|
||||
"""Log a message."""
|
||||
msg = f"%s: {msg}"
|
||||
args = (self.entity_id,) + args
|
||||
_LOGGER.log(level, msg, *args)
|
||||
_LOGGER.log(level, msg, *args, **kwargs)
|
||||
|
||||
|
||||
class ZhaEntity(BaseZhaEntity, RestoreEntity):
|
||||
|
|
|
@ -243,7 +243,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||
level, duration
|
||||
)
|
||||
t_log["move_to_level_with_on_off"] = result
|
||||
if not isinstance(result, list) or result[1] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._state = bool(level)
|
||||
|
@ -255,7 +255,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||
# we should call the on command on the on_off cluster if brightness is not 0.
|
||||
result = await self._on_off_channel.on()
|
||||
t_log["on_off"] = result
|
||||
if not isinstance(result, list) or result[1] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._state = True
|
||||
|
@ -266,7 +266,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||
temperature = kwargs[light.ATTR_COLOR_TEMP]
|
||||
result = await self._color_channel.move_to_color_temp(temperature, duration)
|
||||
t_log["move_to_color_temp"] = result
|
||||
if not isinstance(result, list) or result[1] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._color_temp = temperature
|
||||
|
@ -282,7 +282,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||
int(xy_color[0] * 65535), int(xy_color[1] * 65535), duration
|
||||
)
|
||||
t_log["move_to_color"] = result
|
||||
if not isinstance(result, list) or result[1] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._hs_color = hs_color
|
||||
|
@ -340,7 +340,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||
else:
|
||||
result = await self._on_off_channel.off()
|
||||
self.debug("turned off: %s", result)
|
||||
if not isinstance(result, list) or result[1] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||
return
|
||||
self._state = False
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ class ZhaDoorLock(ZhaEntity, LockEntity):
|
|||
async def async_lock(self, **kwargs):
|
||||
"""Lock the lock."""
|
||||
result = await self._doorlock_channel.lock_door()
|
||||
if not isinstance(result, list) or result[0] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[0] is not Status.SUCCESS:
|
||||
self.error("Error with lock_door: %s", result)
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
@ -130,7 +130,7 @@ class ZhaDoorLock(ZhaEntity, LockEntity):
|
|||
async def async_unlock(self, **kwargs):
|
||||
"""Unlock the lock."""
|
||||
result = await self._doorlock_channel.unlock_door()
|
||||
if not isinstance(result, list) or result[0] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[0] is not Status.SUCCESS:
|
||||
self.error("Error with unlock_door: %s", result)
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
"bellows==0.29.0",
|
||||
"pyserial==3.5",
|
||||
"pyserial-asyncio==0.6",
|
||||
"zha-quirks==0.0.67",
|
||||
"zha-quirks==0.0.69",
|
||||
"zigpy-deconz==0.14.0",
|
||||
"zigpy==0.43.0",
|
||||
"zigpy==0.44.1",
|
||||
"zigpy-xbee==0.14.0",
|
||||
"zigpy-zigate==0.8.0",
|
||||
"zigpy-znp==0.7.0"
|
||||
|
|
|
@ -65,7 +65,7 @@ class BaseSwitch(SwitchEntity):
|
|||
async def async_turn_on(self, **kwargs) -> None:
|
||||
"""Turn the entity on."""
|
||||
result = await self._on_off_channel.on()
|
||||
if not isinstance(result, list) or result[1] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||
return
|
||||
self._state = True
|
||||
self.async_write_ha_state()
|
||||
|
@ -73,7 +73,7 @@ class BaseSwitch(SwitchEntity):
|
|||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn the entity off."""
|
||||
result = await self._on_off_channel.off()
|
||||
if not isinstance(result, list) or result[1] is not Status.SUCCESS:
|
||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||
return
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
|
|
|
@ -2469,7 +2469,7 @@ zengge==0.2
|
|||
zeroconf==0.38.4
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==0.0.67
|
||||
zha-quirks==0.0.69
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong_hong_hvac==1.0.9
|
||||
|
@ -2490,7 +2490,7 @@ zigpy-zigate==0.8.0
|
|||
zigpy-znp==0.7.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy==0.43.0
|
||||
zigpy==0.44.1
|
||||
|
||||
# homeassistant.components.zoneminder
|
||||
zm-py==0.5.2
|
||||
|
|
|
@ -1595,7 +1595,7 @@ youless-api==0.16
|
|||
zeroconf==0.38.4
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==0.0.67
|
||||
zha-quirks==0.0.69
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-deconz==0.14.0
|
||||
|
@ -1610,7 +1610,7 @@ zigpy-zigate==0.8.0
|
|||
zigpy-znp==0.7.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy==0.43.0
|
||||
zigpy==0.44.1
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.35.2
|
||||
|
|
|
@ -20,8 +20,10 @@ def patch_cluster(cluster):
|
|||
value = cluster.PLUGGED_ATTR_READS.get(attr_id)
|
||||
if value is None:
|
||||
# try converting attr_id to attr_name and lookup the plugs again
|
||||
attr_name = cluster.attributes.get(attr_id)
|
||||
value = attr_name and cluster.PLUGGED_ATTR_READS.get(attr_name[0])
|
||||
attr = cluster.attributes.get(attr_id)
|
||||
|
||||
if attr is not None:
|
||||
value = cluster.PLUGGED_ATTR_READS.get(attr.name)
|
||||
if value is not None:
|
||||
result.append(
|
||||
zcl_f.ReadAttributeRecord(
|
||||
|
@ -58,14 +60,23 @@ def patch_cluster(cluster):
|
|||
|
||||
def update_attribute_cache(cluster):
|
||||
"""Update attribute cache based on plugged attributes."""
|
||||
if cluster.PLUGGED_ATTR_READS:
|
||||
attrs = [
|
||||
make_attribute(cluster.attridx.get(attr, attr), value)
|
||||
for attr, value in cluster.PLUGGED_ATTR_READS.items()
|
||||
]
|
||||
hdr = make_zcl_header(zcl_f.Command.Report_Attributes)
|
||||
hdr.frame_control.disable_default_response = True
|
||||
cluster.handle_message(hdr, [attrs])
|
||||
if not cluster.PLUGGED_ATTR_READS:
|
||||
return
|
||||
|
||||
attrs = []
|
||||
for attrid, value in cluster.PLUGGED_ATTR_READS.items():
|
||||
if isinstance(attrid, str):
|
||||
attrid = cluster.attributes_by_name[attrid].id
|
||||
else:
|
||||
attrid = zigpy.types.uint16_t(attrid)
|
||||
attrs.append(make_attribute(attrid, value))
|
||||
|
||||
hdr = make_zcl_header(zcl_f.Command.Report_Attributes)
|
||||
hdr.frame_control.disable_default_response = True
|
||||
msg = zcl_f.GENERAL_COMMANDS[zcl_f.GeneralCommand.Report_Attributes].schema(
|
||||
attribute_reports=attrs
|
||||
)
|
||||
cluster.handle_message(hdr, msg)
|
||||
|
||||
|
||||
def get_zha_gateway(hass):
|
||||
|
@ -96,13 +107,23 @@ async def send_attributes_report(hass, cluster: zigpy.zcl.Cluster, attributes: d
|
|||
This is to simulate the normal device communication that happens when a
|
||||
device is paired to the zigbee network.
|
||||
"""
|
||||
attrs = [
|
||||
make_attribute(cluster.attridx.get(attr, attr), value)
|
||||
for attr, value in attributes.items()
|
||||
]
|
||||
hdr = make_zcl_header(zcl_f.Command.Report_Attributes)
|
||||
attrs = []
|
||||
|
||||
for attrid, value in attributes.items():
|
||||
if isinstance(attrid, str):
|
||||
attrid = cluster.attributes_by_name[attrid].id
|
||||
else:
|
||||
attrid = zigpy.types.uint16_t(attrid)
|
||||
|
||||
attrs.append(make_attribute(attrid, value))
|
||||
|
||||
msg = zcl_f.GENERAL_COMMANDS[zcl_f.GeneralCommand.Report_Attributes].schema(
|
||||
attribute_reports=attrs
|
||||
)
|
||||
|
||||
hdr = make_zcl_header(zcl_f.GeneralCommand.Report_Attributes)
|
||||
hdr.frame_control.disable_default_response = True
|
||||
cluster.handle_message(hdr, [attrs])
|
||||
cluster.handle_message(hdr, msg)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,20 @@ FIXTURE_GRP_ID = 0x1001
|
|||
FIXTURE_GRP_NAME = "fixture group"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def globally_load_quirks():
|
||||
"""Load quirks automatically so that ZHA tests run deterministically in isolation.
|
||||
|
||||
If portions of the ZHA test suite that do not happen to load quirks are run
|
||||
independently, bugs can emerge that will show up only when more of the test suite is
|
||||
run.
|
||||
"""
|
||||
|
||||
import zhaquirks
|
||||
|
||||
zhaquirks.setup()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def zigpy_app_controller():
|
||||
"""Zigpy ApplicationController fixture."""
|
||||
|
|
|
@ -145,7 +145,7 @@ async def test_device_cluster_attributes(zha_client):
|
|||
msg = await zha_client.receive_json()
|
||||
|
||||
attributes = msg["result"]
|
||||
assert len(attributes) == 5
|
||||
assert len(attributes) == 7
|
||||
|
||||
for attribute in attributes:
|
||||
assert attribute[ID] is not None
|
||||
|
|
|
@ -130,7 +130,7 @@ async def poll_control_device(zha_device_restored, zigpy_device_mock):
|
|||
0x0201,
|
||||
1,
|
||||
{
|
||||
"local_temp",
|
||||
"local_temperature",
|
||||
"occupied_cooling_setpoint",
|
||||
"occupied_heating_setpoint",
|
||||
"unoccupied_cooling_setpoint",
|
||||
|
@ -586,13 +586,23 @@ async def test_zll_device_groups(
|
|||
cluster = zigpy_zll_device.endpoints[1].lightlink
|
||||
channel = zha_channels.lightlink.LightLink(cluster, channel_pool)
|
||||
|
||||
get_group_identifiers_rsp = zigpy.zcl.clusters.lightlink.LightLink.commands_by_name[
|
||||
"get_group_identifiers_rsp"
|
||||
].schema
|
||||
|
||||
with patch.object(
|
||||
cluster, "command", AsyncMock(return_value=[1, 0, []])
|
||||
cluster,
|
||||
"command",
|
||||
AsyncMock(
|
||||
return_value=get_group_identifiers_rsp(
|
||||
total=0, start_index=0, group_info_records=[]
|
||||
)
|
||||
),
|
||||
) as cmd_mock:
|
||||
await channel.async_configure()
|
||||
assert cmd_mock.await_count == 1
|
||||
assert (
|
||||
cluster.server_commands[cmd_mock.await_args[0][0]][0]
|
||||
cluster.server_commands[cmd_mock.await_args[0][0]].name
|
||||
== "get_group_identifiers"
|
||||
)
|
||||
assert cluster.bind.call_count == 0
|
||||
|
@ -603,12 +613,18 @@ async def test_zll_device_groups(
|
|||
group_1 = zigpy.zcl.clusters.lightlink.GroupInfoRecord(0xABCD, 0x00)
|
||||
group_2 = zigpy.zcl.clusters.lightlink.GroupInfoRecord(0xAABB, 0x00)
|
||||
with patch.object(
|
||||
cluster, "command", AsyncMock(return_value=[1, 0, [group_1, group_2]])
|
||||
cluster,
|
||||
"command",
|
||||
AsyncMock(
|
||||
return_value=get_group_identifiers_rsp(
|
||||
total=2, start_index=0, group_info_records=[group_1, group_2]
|
||||
)
|
||||
),
|
||||
) as cmd_mock:
|
||||
await channel.async_configure()
|
||||
assert cmd_mock.await_count == 1
|
||||
assert (
|
||||
cluster.server_commands[cmd_mock.await_args[0][0]][0]
|
||||
cluster.server_commands[cmd_mock.await_args[0][0]].name
|
||||
== "get_group_identifiers"
|
||||
)
|
||||
assert cluster.bind.call_count == 0
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
import zhaquirks.sinope.thermostat
|
||||
import zhaquirks.tuya.ts0601_trv
|
||||
import zigpy.profiles
|
||||
import zigpy.types
|
||||
import zigpy.zcl.clusters
|
||||
from zigpy.zcl.clusters.hvac import Thermostat
|
||||
import zigpy.zcl.foundation as zcl_f
|
||||
|
@ -162,8 +163,8 @@ ZCL_ATTR_PLUG = {
|
|||
"abs_max_heat_setpoint_limit": 3000,
|
||||
"abs_min_cool_setpoint_limit": 2000,
|
||||
"abs_max_cool_setpoint_limit": 4000,
|
||||
"ctrl_seqe_of_oper": Thermostat.ControlSequenceOfOperation.Cooling_and_Heating,
|
||||
"local_temp": None,
|
||||
"ctrl_sequence_of_oper": Thermostat.ControlSequenceOfOperation.Cooling_and_Heating,
|
||||
"local_temperature": None,
|
||||
"max_cool_setpoint_limit": 3900,
|
||||
"max_heat_setpoint_limit": 2900,
|
||||
"min_cool_setpoint_limit": 2100,
|
||||
|
@ -268,7 +269,7 @@ def test_sequence_mappings():
|
|||
assert Thermostat.SystemMode(HVAC_MODE_2_SYSTEM[hvac_mode]) is not None
|
||||
|
||||
|
||||
async def test_climate_local_temp(hass, device_climate):
|
||||
async def test_climate_local_temperature(hass, device_climate):
|
||||
"""Test local temperature."""
|
||||
|
||||
thrm_cluster = device_climate.device.endpoints[1].thermostat
|
||||
|
@ -517,7 +518,7 @@ async def test_hvac_modes(hass, device_climate_mock, seq_of_op, modes):
|
|||
"""Test HVAC modes from sequence of operations."""
|
||||
|
||||
device_climate = await device_climate_mock(
|
||||
CLIMATE, {"ctrl_seqe_of_oper": seq_of_op}
|
||||
CLIMATE, {"ctrl_sequence_of_oper": seq_of_op}
|
||||
)
|
||||
entity_id = await find_entity_id(Platform.CLIMATE, device_climate, hass)
|
||||
state = hass.states.get(entity_id)
|
||||
|
@ -1119,7 +1120,7 @@ async def test_occupancy_reset(hass, device_climate_sinope):
|
|||
assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY
|
||||
|
||||
await send_attributes_report(
|
||||
hass, thrm_cluster, {"occupied_heating_setpoint": 1950}
|
||||
hass, thrm_cluster, {"occupied_heating_setpoint": zigpy.types.uint16_t(1950)}
|
||||
)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE
|
||||
|
|
|
@ -146,7 +146,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device):
|
|||
assert cluster.request.call_count == 1
|
||||
assert cluster.request.call_args[0][0] is False
|
||||
assert cluster.request.call_args[0][1] == 0x01
|
||||
assert cluster.request.call_args[0][2] == ()
|
||||
assert cluster.request.call_args[0][2].command.name == "down_close"
|
||||
assert cluster.request.call_args[1]["expect_reply"] is True
|
||||
|
||||
# open from UI
|
||||
|
@ -159,7 +159,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device):
|
|||
assert cluster.request.call_count == 1
|
||||
assert cluster.request.call_args[0][0] is False
|
||||
assert cluster.request.call_args[0][1] == 0x00
|
||||
assert cluster.request.call_args[0][2] == ()
|
||||
assert cluster.request.call_args[0][2].command.name == "up_open"
|
||||
assert cluster.request.call_args[1]["expect_reply"] is True
|
||||
|
||||
# set position UI
|
||||
|
@ -175,7 +175,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device):
|
|||
assert cluster.request.call_count == 1
|
||||
assert cluster.request.call_args[0][0] is False
|
||||
assert cluster.request.call_args[0][1] == 0x05
|
||||
assert cluster.request.call_args[0][2] == (zigpy.types.uint8_t,)
|
||||
assert cluster.request.call_args[0][2].command.name == "go_to_lift_percentage"
|
||||
assert cluster.request.call_args[0][3] == 53
|
||||
assert cluster.request.call_args[1]["expect_reply"] is True
|
||||
|
||||
|
@ -189,7 +189,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device):
|
|||
assert cluster.request.call_count == 1
|
||||
assert cluster.request.call_args[0][0] is False
|
||||
assert cluster.request.call_args[0][1] == 0x02
|
||||
assert cluster.request.call_args[0][2] == ()
|
||||
assert cluster.request.call_args[0][2].command.name == "stop"
|
||||
assert cluster.request.call_args[1]["expect_reply"] is True
|
||||
|
||||
# test rejoin
|
||||
|
|
|
@ -120,7 +120,7 @@ async def test_devices(
|
|||
assert cluster_identify.request.call_args == mock.call(
|
||||
False,
|
||||
64,
|
||||
(zigpy.types.uint8_t, zigpy.types.uint8_t),
|
||||
cluster_identify.commands_by_name["trigger_effect"].schema,
|
||||
2,
|
||||
0,
|
||||
expect_reply=True,
|
||||
|
|
|
@ -4,7 +4,6 @@ from unittest.mock import AsyncMock, call, patch, sentinel
|
|||
|
||||
import pytest
|
||||
import zigpy.profiles.zha as zha
|
||||
import zigpy.types
|
||||
import zigpy.zcl.clusters.general as general
|
||||
import zigpy.zcl.clusters.lighting as lighting
|
||||
import zigpy.zcl.foundation as zcl_f
|
||||
|
@ -336,7 +335,13 @@ async def async_test_on_off_from_hass(hass, cluster, entity_id):
|
|||
assert cluster.request.call_count == 1
|
||||
assert cluster.request.await_count == 1
|
||||
assert cluster.request.call_args == call(
|
||||
False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
ON,
|
||||
cluster.commands_by_name["on"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
|
||||
await async_test_off_from_hass(hass, cluster, entity_id)
|
||||
|
@ -353,7 +358,13 @@ async def async_test_off_from_hass(hass, cluster, entity_id):
|
|||
assert cluster.request.call_count == 1
|
||||
assert cluster.request.await_count == 1
|
||||
assert cluster.request.call_args == call(
|
||||
False, OFF, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
OFF,
|
||||
cluster.commands_by_name["off"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
|
||||
|
||||
|
@ -373,7 +384,13 @@ async def async_test_level_on_off_from_hass(
|
|||
assert level_cluster.request.call_count == 0
|
||||
assert level_cluster.request.await_count == 0
|
||||
assert on_off_cluster.request.call_args == call(
|
||||
False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
ON,
|
||||
on_off_cluster.commands_by_name["on"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
on_off_cluster.request.reset_mock()
|
||||
level_cluster.request.reset_mock()
|
||||
|
@ -389,12 +406,18 @@ async def async_test_level_on_off_from_hass(
|
|||
assert level_cluster.request.call_count == 1
|
||||
assert level_cluster.request.await_count == 1
|
||||
assert on_off_cluster.request.call_args == call(
|
||||
False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
ON,
|
||||
on_off_cluster.commands_by_name["on"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
assert level_cluster.request.call_args == call(
|
||||
False,
|
||||
4,
|
||||
(zigpy.types.uint8_t, zigpy.types.uint16_t),
|
||||
level_cluster.commands_by_name["move_to_level_with_on_off"].schema,
|
||||
254,
|
||||
100.0,
|
||||
expect_reply=True,
|
||||
|
@ -419,7 +442,7 @@ async def async_test_level_on_off_from_hass(
|
|||
assert level_cluster.request.call_args == call(
|
||||
False,
|
||||
4,
|
||||
(zigpy.types.uint8_t, zigpy.types.uint16_t),
|
||||
level_cluster.commands_by_name["move_to_level_with_on_off"].schema,
|
||||
10,
|
||||
1,
|
||||
expect_reply=True,
|
||||
|
@ -462,7 +485,7 @@ async def async_test_flash_from_hass(hass, cluster, entity_id, flash):
|
|||
assert cluster.request.call_args == call(
|
||||
False,
|
||||
64,
|
||||
(zigpy.types.uint8_t, zigpy.types.uint8_t),
|
||||
cluster.commands_by_name["trigger_effect"].schema,
|
||||
FLASH_EFFECTS[flash],
|
||||
0,
|
||||
expect_reply=True,
|
||||
|
|
|
@ -307,7 +307,7 @@ async def async_test_device_temperature(hass, cluster, entity_id):
|
|||
"metering_device_type": 0x00,
|
||||
"multiplier": 1,
|
||||
"status": 0x00,
|
||||
"summa_formatting": 0b1_0111_010,
|
||||
"summation_formatting": 0b1_0111_010,
|
||||
"unit_of_measure": 0x01,
|
||||
},
|
||||
{"instaneneous_demand"},
|
||||
|
@ -814,7 +814,7 @@ async def test_se_summation_uom(
|
|||
"metering_device_type": 0x00,
|
||||
"multiplier": 1,
|
||||
"status": 0x00,
|
||||
"summa_formatting": 0b1_0111_010,
|
||||
"summation_formatting": 0b1_0111_010,
|
||||
"unit_of_measure": raw_uom,
|
||||
}
|
||||
await zha_device_joined(zigpy_device)
|
||||
|
|
|
@ -141,7 +141,13 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device):
|
|||
)
|
||||
assert len(cluster.request.mock_calls) == 1
|
||||
assert cluster.request.call_args == call(
|
||||
False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
ON,
|
||||
cluster.commands_by_name["on"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
|
||||
# turn off from HA
|
||||
|
@ -155,7 +161,13 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device):
|
|||
)
|
||||
assert len(cluster.request.mock_calls) == 1
|
||||
assert cluster.request.call_args == call(
|
||||
False, OFF, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
OFF,
|
||||
cluster.commands_by_name["off"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
|
||||
# test joining a new switch to the network and HA
|
||||
|
@ -224,7 +236,13 @@ async def test_zha_group_switch_entity(
|
|||
)
|
||||
assert len(group_cluster_on_off.request.mock_calls) == 1
|
||||
assert group_cluster_on_off.request.call_args == call(
|
||||
False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
ON,
|
||||
group_cluster_on_off.commands_by_name["on"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
assert hass.states.get(entity_id).state == STATE_ON
|
||||
|
||||
|
@ -239,7 +257,13 @@ async def test_zha_group_switch_entity(
|
|||
)
|
||||
assert len(group_cluster_on_off.request.mock_calls) == 1
|
||||
assert group_cluster_on_off.request.call_args == call(
|
||||
False, OFF, (), expect_reply=True, manufacturer=None, tries=1, tsn=None
|
||||
False,
|
||||
OFF,
|
||||
group_cluster_on_off.commands_by_name["off"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
assert hass.states.get(entity_id).state == STATE_OFF
|
||||
|
||||
|
|
Loading…
Reference in New Issue