Add configuration entities and device actions for Inovelli Blue Series switch to ZHA (#79106)
* Add Inovelli configutation entities to ZHA * add device actions * fix attribute name collision * add device action tests * disable remote protection per Inovelli request * expect_reply to false * update test for expect_reply change * inovelli feedback * translation keys and strings * clean up numbers * prevent double events * remove individual LED defaults per inovelli * redundant check * update testpull/79222/head
parent
7042d6d35b
commit
2ed48a9b28
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from zigpy import types
|
||||
from zigpy.exceptions import ZigbeeException
|
||||
import zigpy.zcl
|
||||
|
||||
|
@ -126,12 +127,135 @@ class SmartThingsAcceleration(ZigbeeChannel):
|
|||
)
|
||||
|
||||
|
||||
@registries.CHANNEL_ONLY_CLUSTERS.register(0xFC31)
|
||||
@registries.CLIENT_CHANNELS_REGISTRY.register(0xFC31)
|
||||
class InovelliCluster(ClientChannel):
|
||||
"""Inovelli Button Press Event channel."""
|
||||
class InovelliNotificationChannel(ClientChannel):
|
||||
"""Inovelli Notification channel."""
|
||||
|
||||
@callback
|
||||
def attribute_updated(self, attrid, value):
|
||||
"""Handle an attribute updated on this cluster."""
|
||||
|
||||
@callback
|
||||
def cluster_command(self, tsn, command_id, args):
|
||||
"""Handle a cluster command received on this cluster."""
|
||||
|
||||
|
||||
@registries.ZIGBEE_CHANNEL_REGISTRY.register(0xFC31)
|
||||
class InovelliConfigEntityChannel(ZigbeeChannel):
|
||||
"""Inovelli Configuration Entity channel."""
|
||||
|
||||
class LEDEffectType(types.enum8):
|
||||
"""Effect type for Inovelli Blue Series switch."""
|
||||
|
||||
Off = 0x00
|
||||
Solid = 0x01
|
||||
Fast_Blink = 0x02
|
||||
Slow_Blink = 0x03
|
||||
Pulse = 0x04
|
||||
Chase = 0x05
|
||||
Open_Close = 0x06
|
||||
Small_To_Big = 0x07
|
||||
Clear = 0xFF
|
||||
|
||||
REPORT_CONFIG = ()
|
||||
ZCL_INIT_ATTRS = { # pylint: disable=invalid-name
|
||||
"dimming_speed_up_remote": False,
|
||||
"dimming_speed_up_local": False,
|
||||
"ramp_rate_off_to_on_local": False,
|
||||
"ramp_rate_off_to_on_remote": False,
|
||||
"dimming_speed_down_remote": False,
|
||||
"dimming_speed_down_local": False,
|
||||
"ramp_rate_on_to_off_local": False,
|
||||
"ramp_rate_on_to_off_remote": False,
|
||||
"minimum_level": False,
|
||||
"maximum_level": False,
|
||||
"invert_switch": False,
|
||||
"auto_off_timer": False,
|
||||
"default_level_local": False,
|
||||
"default_level_remote": False,
|
||||
"state_after_power_restored": False,
|
||||
"load_level_indicator_timeout": False,
|
||||
"active_power_reports": False,
|
||||
"periodic_power_and_energy_reports": False,
|
||||
"active_energy_reports": False,
|
||||
"power_type": False,
|
||||
"switch_type": False,
|
||||
"button_delay": False,
|
||||
"device_bind_number": False,
|
||||
"smart_bulb_mode": False,
|
||||
"double_tap_up_for_full_brightness": False,
|
||||
"default_led1_strip_color_when_on": False,
|
||||
"default_led1_strip_color_when_off": False,
|
||||
"default_led1_strip_intensity_when_on": False,
|
||||
"default_led1_strip_intensity_when_off": False,
|
||||
"default_led2_strip_color_when_on": False,
|
||||
"default_led2_strip_color_when_off": False,
|
||||
"default_led2_strip_intensity_when_on": False,
|
||||
"default_led2_strip_intensity_when_off": False,
|
||||
"default_led3_strip_color_when_on": False,
|
||||
"default_led3_strip_color_when_off": False,
|
||||
"default_led3_strip_intensity_when_on": False,
|
||||
"default_led3_strip_intensity_when_off": False,
|
||||
"default_led4_strip_color_when_on": False,
|
||||
"default_led4_strip_color_when_off": False,
|
||||
"default_led4_strip_intensity_when_on": False,
|
||||
"default_led4_strip_intensity_when_off": False,
|
||||
"default_led5_strip_color_when_on": False,
|
||||
"default_led5_strip_color_when_off": False,
|
||||
"default_led5_strip_intensity_when_on": False,
|
||||
"default_led5_strip_intensity_when_off": False,
|
||||
"default_led6_strip_color_when_on": False,
|
||||
"default_led6_strip_color_when_off": False,
|
||||
"default_led6_strip_intensity_when_on": False,
|
||||
"default_led6_strip_intensity_when_off": False,
|
||||
"default_led7_strip_color_when_on": False,
|
||||
"default_led7_strip_color_when_off": False,
|
||||
"default_led7_strip_intensity_when_on": False,
|
||||
"default_led7_strip_intensity_when_off": False,
|
||||
"led_color_when_on": False,
|
||||
"led_color_when_off": False,
|
||||
"led_intensity_when_on": False,
|
||||
"led_intensity_when_off": False,
|
||||
"local_protection": False,
|
||||
"remote_protection": False,
|
||||
"output_mode": False,
|
||||
"on_off_led_mode": False,
|
||||
"firmware_progress_led": False,
|
||||
"relay_click_in_on_off_mode": False,
|
||||
}
|
||||
|
||||
async def issue_all_led_effect(
|
||||
self,
|
||||
effect_type: LEDEffectType | int = LEDEffectType.Fast_Blink,
|
||||
color: int = 200,
|
||||
level: int = 100,
|
||||
duration: int = 3,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Issue all LED effect command.
|
||||
|
||||
This command is used to issue an LED effect to all LEDs on the device.
|
||||
"""
|
||||
|
||||
await self.led_effect(effect_type, color, level, duration, expect_reply=False)
|
||||
|
||||
async def issue_individual_led_effect(
|
||||
self,
|
||||
led_number: int = 1,
|
||||
effect_type: LEDEffectType | int = LEDEffectType.Fast_Blink,
|
||||
color: int = 200,
|
||||
level: int = 100,
|
||||
duration: int = 3,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Issue individual LED effect command.
|
||||
|
||||
This command is used to issue an LED effect to the specified LED on the device.
|
||||
"""
|
||||
|
||||
await self.individual_led_effect(
|
||||
led_number, effect_type, color, level, duration, expect_reply=False
|
||||
)
|
||||
|
||||
|
||||
@registries.CHANNEL_ONLY_CLUSTERS.register(registries.IKEA_AIR_PURIFIER_CLUSTER)
|
||||
|
|
|
@ -95,6 +95,7 @@ CHANNEL_TEMPERATURE = "temperature"
|
|||
CHANNEL_THERMOSTAT = "thermostat"
|
||||
CHANNEL_ZDO = "zdo"
|
||||
CHANNEL_ZONE = ZONE = "ias_zone"
|
||||
CHANNEL_INOVELLI = "inovelli_vzm31sn_cluster"
|
||||
|
||||
CLUSTER_COMMAND_SERVER = "server"
|
||||
CLUSTER_COMMANDS_CLIENT = "client_commands"
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import Any
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import InvalidDeviceAutomationConfig
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_TYPE
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
@ -12,7 +13,8 @@ from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
|||
|
||||
from . import DOMAIN
|
||||
from .api import SERVICE_WARNING_DEVICE_SQUAWK, SERVICE_WARNING_DEVICE_WARN
|
||||
from .core.const import CHANNEL_IAS_WD
|
||||
from .core.channels.manufacturerspecific import InovelliConfigEntityChannel
|
||||
from .core.const import CHANNEL_IAS_WD, CHANNEL_INOVELLI
|
||||
from .core.helpers import async_get_zha_device
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
@ -23,21 +25,83 @@ ATTR_DATA = "data"
|
|||
ATTR_IEEE = "ieee"
|
||||
CONF_ZHA_ACTION_TYPE = "zha_action_type"
|
||||
ZHA_ACTION_TYPE_SERVICE_CALL = "service_call"
|
||||
ZHA_ACTION_TYPE_CHANNEL_COMMAND = "channel_command"
|
||||
INOVELLI_ALL_LED_EFFECT = "issue_all_led_effect"
|
||||
INOVELLI_INDIVIDUAL_LED_EFFECT = "issue_individual_led_effect"
|
||||
|
||||
ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{vol.Required(CONF_DOMAIN): DOMAIN, vol.Required(CONF_TYPE): str}
|
||||
DEFAULT_ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_DOMAIN): DOMAIN,
|
||||
vol.Required(CONF_TYPE): vol.In({ACTION_SQUAWK, ACTION_WARN}),
|
||||
}
|
||||
)
|
||||
|
||||
INOVELLI_ALL_LED_EFFECT_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): INOVELLI_ALL_LED_EFFECT,
|
||||
vol.Required(CONF_DOMAIN): DOMAIN,
|
||||
vol.Required(
|
||||
"effect_type"
|
||||
): InovelliConfigEntityChannel.LEDEffectType.__getitem__,
|
||||
vol.Required("color"): vol.All(vol.Coerce(int), vol.Range(0, 255)),
|
||||
vol.Required("level"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
||||
vol.Required("duration"): vol.All(vol.Coerce(int), vol.Range(1, 255)),
|
||||
}
|
||||
)
|
||||
|
||||
INOVELLI_INDIVIDUAL_LED_EFFECT_SCHEMA = INOVELLI_ALL_LED_EFFECT_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): INOVELLI_INDIVIDUAL_LED_EFFECT,
|
||||
vol.Required("led_number"): vol.All(vol.Coerce(int), vol.Range(1, 7)),
|
||||
}
|
||||
)
|
||||
|
||||
ACTION_SCHEMA = vol.Any(
|
||||
INOVELLI_ALL_LED_EFFECT_SCHEMA,
|
||||
INOVELLI_INDIVIDUAL_LED_EFFECT_SCHEMA,
|
||||
DEFAULT_ACTION_SCHEMA,
|
||||
)
|
||||
|
||||
DEVICE_ACTIONS = {
|
||||
CHANNEL_IAS_WD: [
|
||||
{CONF_TYPE: ACTION_SQUAWK, CONF_DOMAIN: DOMAIN},
|
||||
{CONF_TYPE: ACTION_WARN, CONF_DOMAIN: DOMAIN},
|
||||
]
|
||||
],
|
||||
CHANNEL_INOVELLI: [
|
||||
{CONF_TYPE: INOVELLI_ALL_LED_EFFECT, CONF_DOMAIN: DOMAIN},
|
||||
{CONF_TYPE: INOVELLI_INDIVIDUAL_LED_EFFECT, CONF_DOMAIN: DOMAIN},
|
||||
],
|
||||
}
|
||||
|
||||
DEVICE_ACTION_TYPES = {
|
||||
ACTION_SQUAWK: ZHA_ACTION_TYPE_SERVICE_CALL,
|
||||
ACTION_WARN: ZHA_ACTION_TYPE_SERVICE_CALL,
|
||||
INOVELLI_ALL_LED_EFFECT: ZHA_ACTION_TYPE_CHANNEL_COMMAND,
|
||||
INOVELLI_INDIVIDUAL_LED_EFFECT: ZHA_ACTION_TYPE_CHANNEL_COMMAND,
|
||||
}
|
||||
|
||||
DEVICE_ACTION_SCHEMAS = {
|
||||
INOVELLI_ALL_LED_EFFECT: vol.Schema(
|
||||
{
|
||||
vol.Required("effect_type"): vol.In(
|
||||
InovelliConfigEntityChannel.LEDEffectType.__members__.keys()
|
||||
),
|
||||
vol.Required("color"): vol.All(vol.Coerce(int), vol.Range(0, 255)),
|
||||
vol.Required("level"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
||||
vol.Required("duration"): vol.All(vol.Coerce(int), vol.Range(1, 255)),
|
||||
}
|
||||
),
|
||||
INOVELLI_INDIVIDUAL_LED_EFFECT: vol.Schema(
|
||||
{
|
||||
vol.Required("led_number"): vol.All(vol.Coerce(int), vol.Range(1, 7)),
|
||||
vol.Required("effect_type"): vol.In(
|
||||
InovelliConfigEntityChannel.LEDEffectType.__members__.keys()
|
||||
),
|
||||
vol.Required("color"): vol.All(vol.Coerce(int), vol.Range(0, 255)),
|
||||
vol.Required("level"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
||||
vol.Required("duration"): vol.All(vol.Coerce(int), vol.Range(1, 255)),
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
SERVICE_NAMES = {
|
||||
|
@ -45,6 +109,11 @@ SERVICE_NAMES = {
|
|||
ACTION_WARN: SERVICE_WARNING_DEVICE_WARN,
|
||||
}
|
||||
|
||||
CHANNEL_MAPPINGS = {
|
||||
INOVELLI_ALL_LED_EFFECT: CHANNEL_INOVELLI,
|
||||
INOVELLI_INDIVIDUAL_LED_EFFECT: CHANNEL_INOVELLI,
|
||||
}
|
||||
|
||||
|
||||
async def async_call_action_from_config(
|
||||
hass: HomeAssistant,
|
||||
|
@ -82,6 +151,14 @@ async def async_get_actions(
|
|||
return actions
|
||||
|
||||
|
||||
async def async_get_action_capabilities(
|
||||
hass: HomeAssistant, config: ConfigType
|
||||
) -> dict[str, vol.Schema]:
|
||||
"""List action capabilities."""
|
||||
|
||||
return {"extra_fields": DEVICE_ACTION_SCHEMAS.get(config[CONF_TYPE], {})}
|
||||
|
||||
|
||||
async def _execute_service_based_action(
|
||||
hass: HomeAssistant,
|
||||
config: dict[str, Any],
|
||||
|
@ -102,4 +179,40 @@ async def _execute_service_based_action(
|
|||
)
|
||||
|
||||
|
||||
ZHA_ACTION_TYPES = {ZHA_ACTION_TYPE_SERVICE_CALL: _execute_service_based_action}
|
||||
async def _execute_channel_command_based_action(
|
||||
hass: HomeAssistant,
|
||||
config: dict[str, Any],
|
||||
variables: TemplateVarsType,
|
||||
context: Context | None,
|
||||
) -> None:
|
||||
action_type = config[CONF_TYPE]
|
||||
channel_name = CHANNEL_MAPPINGS[action_type]
|
||||
try:
|
||||
zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID])
|
||||
except (KeyError, AttributeError):
|
||||
return
|
||||
|
||||
action_channel = None
|
||||
for pool in zha_device.channels.pools:
|
||||
for channel in pool.all_channels.values():
|
||||
if channel.name == channel_name:
|
||||
action_channel = channel
|
||||
break
|
||||
|
||||
if action_channel is None:
|
||||
raise InvalidDeviceAutomationConfig(
|
||||
f"Unable to execute channel action - channel: {channel_name} action: {action_type}"
|
||||
)
|
||||
|
||||
if not hasattr(action_channel, action_type):
|
||||
raise InvalidDeviceAutomationConfig(
|
||||
f"Unable to execute channel action - channel: {channel_name} action: {action_type}"
|
||||
)
|
||||
|
||||
await getattr(action_channel, action_type)(**config)
|
||||
|
||||
|
||||
ZHA_ACTION_TYPES = {
|
||||
ZHA_ACTION_TYPE_SERVICE_CALL: _execute_service_based_action,
|
||||
ZHA_ACTION_TYPE_CHANNEL_COMMAND: _execute_channel_command_based_action,
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ class BaseZhaEntity(LogMixin, entity.Entity):
|
|||
@property
|
||||
def name(self) -> str:
|
||||
"""Return Entity's default name."""
|
||||
if hasattr(self, "_attr_name") and self._attr_name is not None:
|
||||
return self._attr_name
|
||||
return self._name
|
||||
|
||||
@property
|
||||
|
|
|
@ -19,6 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
from .core import discovery
|
||||
from .core.const import (
|
||||
CHANNEL_ANALOG_OUTPUT,
|
||||
CHANNEL_INOVELLI,
|
||||
CHANNEL_LEVEL,
|
||||
DATA_ZHA,
|
||||
SIGNAL_ADD_ENTITIES,
|
||||
|
@ -251,6 +252,8 @@ ICONS = {
|
|||
12: "mdi:counter",
|
||||
13: "mdi:thermometer-lines",
|
||||
14: "mdi:timer",
|
||||
15: "mdi:palette",
|
||||
16: "mdi:brightness-percent",
|
||||
}
|
||||
|
||||
|
||||
|
@ -545,3 +548,252 @@ class FilterLifeTime(ZHANumberConfigurationEntity, id_suffix="filter_life_time")
|
|||
_attr_native_max_value: float = 0xFFFFFFFF
|
||||
_attr_native_unit_of_measurement: str | None = UNITS[72]
|
||||
_zcl_attribute: str = "filter_life_time"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliRemoteDimmingUpSpeed(
|
||||
ZHANumberConfigurationEntity, id_suffix="dimming_speed_up_remote"
|
||||
):
|
||||
"""Inovelli remote dimming up speed configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 126
|
||||
_zcl_attribute: str = "dimming_speed_up_remote"
|
||||
_attr_name: str = "Remote dimming up speed"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliButtonDelay(ZHANumberConfigurationEntity, id_suffix="button_delay"):
|
||||
"""Inovelli button delay configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 9
|
||||
_zcl_attribute: str = "button_delay"
|
||||
_attr_name: str = "Button delay"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliDeviceBindNumber(
|
||||
ZHANumberConfigurationEntity, id_suffix="device_bind_number"
|
||||
):
|
||||
"""Inovelli device bind number configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 255
|
||||
_zcl_attribute: str = "device_bind_number"
|
||||
_attr_name: str = "Device bind number"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliLocalDimmingUpSpeed(
|
||||
ZHANumberConfigurationEntity, id_suffix="dimming_speed_up_local"
|
||||
):
|
||||
"""Inovelli local dimming up speed configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 127
|
||||
_zcl_attribute: str = "dimming_speed_up_local"
|
||||
_attr_name: str = "Local dimming up speed"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliLocalRampRateOffToOn(
|
||||
ZHANumberConfigurationEntity, id_suffix="ramp_rate_off_to_on_local"
|
||||
):
|
||||
"""Inovelli off to on local ramp rate configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 127
|
||||
_zcl_attribute: str = "ramp_rate_off_to_on_local"
|
||||
_attr_name: str = "Local ramp rate off to on"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliRemoteDimmingSpeedOffToOn(
|
||||
ZHANumberConfigurationEntity, id_suffix="ramp_rate_off_to_on_remote"
|
||||
):
|
||||
"""Inovelli off to on remote ramp rate configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 127
|
||||
_zcl_attribute: str = "ramp_rate_off_to_on_remote"
|
||||
_attr_name: str = "Remote ramp rate off to on"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliRemoteDimmingDownSpeed(
|
||||
ZHANumberConfigurationEntity, id_suffix="dimming_speed_down_remote"
|
||||
):
|
||||
"""Inovelli remote dimming down speed configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 127
|
||||
_zcl_attribute: str = "dimming_speed_down_remote"
|
||||
_attr_name: str = "Remote dimming down speed"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliLocalDimmingDownSpeed(
|
||||
ZHANumberConfigurationEntity, id_suffix="dimming_speed_down_local"
|
||||
):
|
||||
"""Inovelli local dimming down speed configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 127
|
||||
_zcl_attribute: str = "dimming_speed_down_local"
|
||||
_attr_name: str = "Local dimming down speed"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliLocalRampRateOnToOff(
|
||||
ZHANumberConfigurationEntity, id_suffix="ramp_rate_on_to_off_local"
|
||||
):
|
||||
"""Inovelli local on to off ramp rate configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 127
|
||||
_zcl_attribute: str = "ramp_rate_on_to_off_local"
|
||||
_attr_name: str = "Local ramp rate on to off"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliRemoteDimmingSpeedOnToOff(
|
||||
ZHANumberConfigurationEntity, id_suffix="ramp_rate_on_to_off_remote"
|
||||
):
|
||||
"""Inovelli remote on to off ramp rate configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[3]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 127
|
||||
_zcl_attribute: str = "ramp_rate_on_to_off_remote"
|
||||
_attr_name: str = "Remote ramp rate on to off"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliMinimumLoadDimmingLevel(
|
||||
ZHANumberConfigurationEntity, id_suffix="minimum_level"
|
||||
):
|
||||
"""Inovelli minimum load dimming level configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[16]
|
||||
_attr_native_min_value: float = 1
|
||||
_attr_native_max_value: float = 254
|
||||
_zcl_attribute: str = "minimum_level"
|
||||
_attr_name: str = "Minimum load dimming level"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliMaximumLoadDimmingLevel(
|
||||
ZHANumberConfigurationEntity, id_suffix="maximum_level"
|
||||
):
|
||||
"""Inovelli maximum load dimming level configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[16]
|
||||
_attr_native_min_value: float = 2
|
||||
_attr_native_max_value: float = 255
|
||||
_zcl_attribute: str = "maximum_level"
|
||||
_attr_name: str = "Maximum load dimming level"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliAutoShutoffTimer(
|
||||
ZHANumberConfigurationEntity, id_suffix="auto_off_timer"
|
||||
):
|
||||
"""Inovelli automatic switch shutoff timer configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[14]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 32767
|
||||
_zcl_attribute: str = "auto_off_timer"
|
||||
_attr_name: str = "Automatic switch shutoff timer"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliLoadLevelIndicatorTimeout(
|
||||
ZHANumberConfigurationEntity, id_suffix="load_level_indicator_timeout"
|
||||
):
|
||||
"""Inovelli load level indicator timeout configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[14]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 11
|
||||
_zcl_attribute: str = "load_level_indicator_timeout"
|
||||
_attr_name: str = "Load level indicator timeout"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliDefaultAllLEDOnColor(
|
||||
ZHANumberConfigurationEntity, id_suffix="led_color_when_on"
|
||||
):
|
||||
"""Inovelli default all led color when on configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[15]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 255
|
||||
_zcl_attribute: str = "led_color_when_on"
|
||||
_attr_name: str = "Default all LED on color"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliDefaultAllLEDOffColor(
|
||||
ZHANumberConfigurationEntity, id_suffix="led_color_when_off"
|
||||
):
|
||||
"""Inovelli default all led color when off configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[15]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 255
|
||||
_zcl_attribute: str = "led_color_when_off"
|
||||
_attr_name: str = "Default all LED off color"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliDefaultAllLEDOnIntensity(
|
||||
ZHANumberConfigurationEntity, id_suffix="led_intensity_when_on"
|
||||
):
|
||||
"""Inovelli default all led intensity when on configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[16]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 100
|
||||
_zcl_attribute: str = "led_intensity_when_on"
|
||||
_attr_name: str = "Default all LED on intensity"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_INOVELLI)
|
||||
class InovelliDefaultAllLEDOffIntensity(
|
||||
ZHANumberConfigurationEntity, id_suffix="led_intensity_when_off"
|
||||
):
|
||||
"""Inovelli default all led intensity when off configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon: str = ICONS[16]
|
||||
_attr_native_min_value: float = 0
|
||||
_attr_native_max_value: float = 100
|
||||
_zcl_attribute: str = "led_intensity_when_off"
|
||||
_attr_name: str = "Default all LED off intensity"
|
||||
|
|
|
@ -21,6 +21,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
from .core import discovery
|
||||
from .core.const import (
|
||||
CHANNEL_IAS_WD,
|
||||
CHANNEL_INOVELLI,
|
||||
CHANNEL_ON_OFF,
|
||||
DATA_ZHA,
|
||||
SIGNAL_ADD_ENTITIES,
|
||||
|
@ -68,7 +69,7 @@ class ZHAEnumSelectEntity(ZhaEntity, SelectEntity):
|
|||
"""Representation of a ZHA select entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_name: str
|
||||
_attribute: str
|
||||
_enum: type[Enum]
|
||||
|
||||
def __init__(
|
||||
|
@ -79,7 +80,7 @@ class ZHAEnumSelectEntity(ZhaEntity, SelectEntity):
|
|||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Init this select entity."""
|
||||
self._attr_name = self._enum.__name__
|
||||
self._attribute = self._enum.__name__
|
||||
self._attr_options = [entry.name.replace("_", " ") for entry in self._enum]
|
||||
self._channel: ZigbeeChannel = channels[0]
|
||||
super().__init__(unique_id, zha_device, channels, **kwargs)
|
||||
|
@ -87,21 +88,21 @@ class ZHAEnumSelectEntity(ZhaEntity, SelectEntity):
|
|||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the selected entity option to represent the entity state."""
|
||||
option = self._channel.data_cache.get(self._attr_name)
|
||||
option = self._channel.data_cache.get(self._attribute)
|
||||
if option is None:
|
||||
return None
|
||||
return option.name.replace("_", " ")
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
self._channel.data_cache[self._attr_name] = self._enum[option.replace(" ", "_")]
|
||||
self._channel.data_cache[self._attribute] = self._enum[option.replace(" ", "_")]
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def async_restore_last_state(self, last_state) -> None:
|
||||
"""Restore previous state."""
|
||||
if last_state.state and last_state.state != STATE_UNKNOWN:
|
||||
self._channel.data_cache[self._attr_name] = self._enum[
|
||||
self._channel.data_cache[self._attribute] = self._enum[
|
||||
last_state.state.replace(" ", "_")
|
||||
]
|
||||
|
||||
|
@ -285,3 +286,40 @@ class AqaraCurtainMode(ZCLEnumSelectEntity, id_suffix="window_covering_mode"):
|
|||
|
||||
_select_attr = "window_covering_mode"
|
||||
_enum = AqaraE1ReverseDirection
|
||||
|
||||
|
||||
class InovelliOutputMode(types.enum1):
|
||||
"""Inovelli output mode."""
|
||||
|
||||
Dimmer = 0x00
|
||||
OnOff = 0x01
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliOutputModeEntity(ZCLEnumSelectEntity, id_suffix="output_mode"):
|
||||
"""Inovelli output mode control."""
|
||||
|
||||
_select_attr = "output_mode"
|
||||
_enum = InovelliOutputMode
|
||||
_attr_name: str = "Output mode"
|
||||
|
||||
|
||||
class InovelliSwitchType(types.enum8):
|
||||
"""Inovelli output mode."""
|
||||
|
||||
Load_Only = 0x00
|
||||
Three_Way_Dumb = 0x01
|
||||
Three_Way_AUX = 0x02
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliSwitchTypeEntity(ZCLEnumSelectEntity, id_suffix="switch_type"):
|
||||
"""Inovelli switch type control."""
|
||||
|
||||
_select_attr = "switch_type"
|
||||
_enum = InovelliSwitchType
|
||||
_attr_name: str = "Switch type"
|
||||
|
|
|
@ -160,7 +160,12 @@
|
|||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"action_type": { "squawk": "Squawk", "warn": "Warn" },
|
||||
"action_type": {
|
||||
"squawk": "Squawk",
|
||||
"warn": "Warn",
|
||||
"issue_all_led_effect": "Issue effect for all LEDs",
|
||||
"issue_individual_led_effect": "Issue effect for individual LED"
|
||||
},
|
||||
"trigger_type": {
|
||||
"remote_button_short_press": "\"{subtype}\" button pressed",
|
||||
"remote_button_short_release": "\"{subtype}\" button released",
|
||||
|
|
|
@ -19,6 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
|
||||
from .core import discovery
|
||||
from .core.const import (
|
||||
CHANNEL_INOVELLI,
|
||||
CHANNEL_ON_OFF,
|
||||
DATA_ZHA,
|
||||
SIGNAL_ADD_ENTITIES,
|
||||
|
@ -309,3 +310,81 @@ class DisableLed(ZHASwitchConfigurationEntity, id_suffix="disable_led"):
|
|||
"""ZHA BinarySensor."""
|
||||
|
||||
_zcl_attribute: str = "disable_led"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliInvertSwitch(ZHASwitchConfigurationEntity, id_suffix="invert_switch"):
|
||||
"""Inovelli invert switch control."""
|
||||
|
||||
_zcl_attribute: str = "invert_switch"
|
||||
_attr_name: str = "Invert switch"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliSmartBulbMode(ZHASwitchConfigurationEntity, id_suffix="smart_bulb_mode"):
|
||||
"""Inovelli smart bulb mode control."""
|
||||
|
||||
_zcl_attribute: str = "smart_bulb_mode"
|
||||
_attr_name: str = "Smart bulb mode"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliDoubleTapForFullBrightness(
|
||||
ZHASwitchConfigurationEntity, id_suffix="double_tap_up_for_full_brightness"
|
||||
):
|
||||
"""Inovelli double tap for full brightness control."""
|
||||
|
||||
_zcl_attribute: str = "double_tap_up_for_full_brightness"
|
||||
_attr_name: str = "Double tap full brightness"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliLocalProtection(
|
||||
ZHASwitchConfigurationEntity, id_suffix="local_protection"
|
||||
):
|
||||
"""Inovelli local protection control."""
|
||||
|
||||
_zcl_attribute: str = "local_protection"
|
||||
_attr_name: str = "Local protection"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliOnOffLEDMode(ZHASwitchConfigurationEntity, id_suffix="on_off_led_mode"):
|
||||
"""Inovelli only 1 LED mode control."""
|
||||
|
||||
_zcl_attribute: str = "on_off_led_mode"
|
||||
_attr_name: str = "Only 1 LED mode"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliFirmwareProgressLED(
|
||||
ZHASwitchConfigurationEntity, id_suffix="firmware_progress_led"
|
||||
):
|
||||
"""Inovelli firmware progress LED control."""
|
||||
|
||||
_zcl_attribute: str = "firmware_progress_led"
|
||||
_attr_name: str = "Firmware progress LED"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
channel_names=CHANNEL_INOVELLI,
|
||||
)
|
||||
class InovelliRelayClickInOnOffMode(
|
||||
ZHASwitchConfigurationEntity, id_suffix="relay_click_in_on_off_mode"
|
||||
):
|
||||
"""Inovelli relay click in on off mode control."""
|
||||
|
||||
_zcl_attribute: str = "relay_click_in_on_off_mode"
|
||||
_attr_name: str = "Disable relay click in on off mode"
|
||||
|
|
|
@ -117,7 +117,9 @@
|
|||
"device_automation": {
|
||||
"action_type": {
|
||||
"squawk": "Squawk",
|
||||
"warn": "Warn"
|
||||
"warn": "Warn",
|
||||
"issue_all_led_effect": "Issue effect for all LEDs",
|
||||
"issue_individual_led_effect": "Issue effect for individual LED"
|
||||
},
|
||||
"trigger_subtype": {
|
||||
"both_buttons": "Both buttons",
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
"""The test for zha device automation actions."""
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import call, patch
|
||||
|
||||
import pytest
|
||||
from zhaquirks.inovelli.VZM31SN import InovelliVZM31SNv11
|
||||
import zigpy.profiles.zha
|
||||
import zigpy.zcl.clusters.general as general
|
||||
import zigpy.zcl.clusters.security as security
|
||||
|
@ -16,7 +17,12 @@ from homeassistant.setup import async_setup_component
|
|||
|
||||
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_TYPE
|
||||
|
||||
from tests.common import async_get_device_automations, async_mock_service, mock_coro
|
||||
from tests.common import (
|
||||
assert_lists_same,
|
||||
async_get_device_automations,
|
||||
async_mock_service,
|
||||
mock_coro,
|
||||
)
|
||||
from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401
|
||||
|
||||
SHORT_PRESS = "remote_button_short_press"
|
||||
|
@ -31,10 +37,13 @@ def required_platforms_only():
|
|||
"homeassistant.components.zha.PLATFORMS",
|
||||
(
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.DEVICE_TRACKER,
|
||||
Platform.LIGHT,
|
||||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.SIREN,
|
||||
),
|
||||
):
|
||||
|
@ -62,6 +71,36 @@ async def device_ias(hass, zigpy_device_mock, zha_device_joined_restored):
|
|||
return zigpy_device, zha_device
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def device_inovelli(hass, zigpy_device_mock, zha_device_joined):
|
||||
"""Inovelli device fixture."""
|
||||
|
||||
zigpy_device = zigpy_device_mock(
|
||||
{
|
||||
1: {
|
||||
SIG_EP_INPUT: [
|
||||
general.Basic.cluster_id,
|
||||
general.Identify.cluster_id,
|
||||
general.OnOff.cluster_id,
|
||||
general.LevelControl.cluster_id,
|
||||
0xFC31,
|
||||
],
|
||||
SIG_EP_OUTPUT: [],
|
||||
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.DIMMABLE_LIGHT,
|
||||
}
|
||||
},
|
||||
ieee="00:1d:8f:08:0c:90:69:6b",
|
||||
manufacturer="Inovelli",
|
||||
model="VZM31-SN",
|
||||
quirk=InovelliVZM31SNv11,
|
||||
)
|
||||
|
||||
zha_device = await zha_device_joined(zigpy_device)
|
||||
zha_device.update_available(True)
|
||||
await hass.async_block_till_done()
|
||||
return zigpy_device, zha_device
|
||||
|
||||
|
||||
async def test_get_actions(hass, device_ias):
|
||||
"""Test we get the expected actions from a zha device."""
|
||||
|
||||
|
@ -112,21 +151,108 @@ async def test_get_actions(hass, device_ias):
|
|||
},
|
||||
]
|
||||
|
||||
assert actions == expected_actions
|
||||
assert_lists_same(actions, expected_actions)
|
||||
|
||||
|
||||
async def test_action(hass, device_ias):
|
||||
async def test_get_inovelli_actions(hass, device_inovelli):
|
||||
"""Test we get the expected actions from a zha device."""
|
||||
|
||||
inovelli_ieee_address = str(device_inovelli[0].ieee)
|
||||
ha_device_registry = dr.async_get(hass)
|
||||
inovelli_reg_device = ha_device_registry.async_get_device(
|
||||
{(DOMAIN, inovelli_ieee_address)}
|
||||
)
|
||||
|
||||
actions = await async_get_device_automations(
|
||||
hass, DeviceAutomationType.ACTION, inovelli_reg_device.id
|
||||
)
|
||||
|
||||
expected_actions = [
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": DOMAIN,
|
||||
"metadata": {},
|
||||
"type": "issue_all_led_effect",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": DOMAIN,
|
||||
"metadata": {},
|
||||
"type": "issue_individual_led_effect",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": Platform.BUTTON,
|
||||
"entity_id": "button.inovelli_vzm31_sn_identifybutton",
|
||||
"metadata": {"secondary": True},
|
||||
"type": "press",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": Platform.LIGHT,
|
||||
"entity_id": "light.inovelli_vzm31_sn_light",
|
||||
"metadata": {"secondary": False},
|
||||
"type": "turn_off",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": Platform.LIGHT,
|
||||
"entity_id": "light.inovelli_vzm31_sn_light",
|
||||
"metadata": {"secondary": False},
|
||||
"type": "turn_on",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": Platform.LIGHT,
|
||||
"entity_id": "light.inovelli_vzm31_sn_light",
|
||||
"metadata": {"secondary": False},
|
||||
"type": "toggle",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": Platform.LIGHT,
|
||||
"entity_id": "light.inovelli_vzm31_sn_light",
|
||||
"metadata": {"secondary": False},
|
||||
"type": "brightness_increase",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": Platform.LIGHT,
|
||||
"entity_id": "light.inovelli_vzm31_sn_light",
|
||||
"metadata": {"secondary": False},
|
||||
"type": "brightness_decrease",
|
||||
},
|
||||
{
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"domain": Platform.LIGHT,
|
||||
"entity_id": "light.inovelli_vzm31_sn_light",
|
||||
"metadata": {"secondary": False},
|
||||
"type": "flash",
|
||||
},
|
||||
]
|
||||
|
||||
assert_lists_same(actions, expected_actions)
|
||||
|
||||
|
||||
async def test_action(hass, device_ias, device_inovelli):
|
||||
"""Test for executing a zha device action."""
|
||||
zigpy_device, zha_device = device_ias
|
||||
inovelli_zigpy_device, inovelli_zha_device = device_inovelli
|
||||
|
||||
zigpy_device.device_automation_triggers = {
|
||||
(SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE}
|
||||
}
|
||||
|
||||
ieee_address = str(zha_device.ieee)
|
||||
inovelli_ieee_address = str(inovelli_zha_device.ieee)
|
||||
|
||||
ha_device_registry = dr.async_get(hass)
|
||||
reg_device = ha_device_registry.async_get_device({(DOMAIN, ieee_address)})
|
||||
inovelli_reg_device = ha_device_registry.async_get_device(
|
||||
{(DOMAIN, inovelli_ieee_address)}
|
||||
)
|
||||
|
||||
cluster = inovelli_zigpy_device.endpoints[1].in_clusters[0xFC31]
|
||||
|
||||
with patch(
|
||||
"zigpy.zcl.Cluster.request",
|
||||
|
@ -145,11 +271,32 @@ async def test_action(hass, device_ias):
|
|||
"type": SHORT_PRESS,
|
||||
"subtype": SHORT_PRESS,
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"device_id": reg_device.id,
|
||||
"type": "warn",
|
||||
},
|
||||
"action": [
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"device_id": reg_device.id,
|
||||
"type": "warn",
|
||||
},
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"type": "issue_all_led_effect",
|
||||
"effect_type": "Open_Close",
|
||||
"duration": 5,
|
||||
"level": 10,
|
||||
"color": 41,
|
||||
},
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"device_id": inovelli_reg_device.id,
|
||||
"type": "issue_individual_led_effect",
|
||||
"effect_type": "Open_Close",
|
||||
"led_number": 1,
|
||||
"duration": 5,
|
||||
"level": 10,
|
||||
"color": 41,
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -167,6 +314,41 @@ async def test_action(hass, device_ias):
|
|||
assert calls[0].service == "warning_device_warn"
|
||||
assert calls[0].data["ieee"] == ieee_address
|
||||
|
||||
assert len(cluster.request.mock_calls) == 2
|
||||
assert (
|
||||
call(
|
||||
False,
|
||||
cluster.commands_by_name["led_effect"].id,
|
||||
cluster.commands_by_name["led_effect"].schema,
|
||||
6,
|
||||
41,
|
||||
10,
|
||||
5,
|
||||
expect_reply=False,
|
||||
manufacturer=4151,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
in cluster.request.call_args_list
|
||||
)
|
||||
assert (
|
||||
call(
|
||||
False,
|
||||
cluster.commands_by_name["individual_led_effect"].id,
|
||||
cluster.commands_by_name["individual_led_effect"].schema,
|
||||
1,
|
||||
6,
|
||||
41,
|
||||
10,
|
||||
5,
|
||||
expect_reply=False,
|
||||
manufacturer=4151,
|
||||
tries=1,
|
||||
tsn=None,
|
||||
)
|
||||
in cluster.request.call_args_list
|
||||
)
|
||||
|
||||
|
||||
async def test_invalid_zha_event_type(hass, device_ias):
|
||||
"""Test that unexpected types are not passed to `zha_send_event`."""
|
||||
|
|
Loading…
Reference in New Issue