diff --git a/zha/application/platforms/number/__init__.py b/zha/application/platforms/number/__init__.py index fed5c8d95..8cbe64cce 100644 --- a/zha/application/platforms/number/__init__.py +++ b/zha/application/platforms/number/__init__.py @@ -25,6 +25,7 @@ CLUSTER_HANDLER_BASIC, CLUSTER_HANDLER_COLOR, CLUSTER_HANDLER_INOVELLI, + CLUSTER_HANDLER_INOVELLI_MMWAVE, CLUSTER_HANDLER_LEVEL, CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_THERMOSTAT, @@ -724,6 +725,118 @@ class InovelliDoubleTapDownLevel(NumberConfigurationEntity): _attr_translation_key: str = "double_tap_down_level" +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveZMin(NumberConfigurationEntity): + """Inovelli mmwave Z minimum configuration entity.""" + + _unique_id_suffix = "mmwave_z_min" + _attr_entity_category = EntityCategory.CONFIG + _attr_native_min_value: float = -600 + _attr_native_max_value: float = 600 + _attribute_name = "mmwave_z_min" + _attr_translation_key: str = "mmwave_z_min" + _attr_fallback_name = "mmWave Height Minimum (Floor)" + _attr_mode: NumberMode = NumberMode.BOX + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveZMax(NumberConfigurationEntity): + """Inovelli mmwave Z maximum configuration entity.""" + + _unique_id_suffix = "mmwave_z_max" + _attr_entity_category = EntityCategory.CONFIG + _attr_native_min_value: float = -600 + _attr_native_max_value: float = 600 + _attribute_name = "mmwave_z_max" + _attr_translation_key: str = "mmwave_z_max" + _attr_fallback_name = "mmWave Height Maximum (Ceiling)" + _attr_mode: NumberMode = NumberMode.BOX + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveXMin(NumberConfigurationEntity): + """Inovelli mmwave X minimum configuration entity.""" + + _unique_id_suffix = "mmwave_x_min" + _attr_entity_category = EntityCategory.CONFIG + _attr_native_min_value: float = -600 + _attr_native_max_value: float = 600 + _attribute_name = "mmwave_x_min" + _attr_translation_key: str = "mmwave_x_min" + _attr_fallback_name = "mmWave Width Minimum (Left)" + _attr_mode: NumberMode = NumberMode.BOX + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveXMax(NumberConfigurationEntity): + """Inovelli mmwave X maximum configuration entity.""" + + _unique_id_suffix = "mmwave_x_max" + _attr_entity_category = EntityCategory.CONFIG + _attr_native_min_value: float = -600 + _attr_native_max_value: float = 600 + _attribute_name = "mmwave_x_max" + _attr_translation_key: str = "mmwave_x_max" + _attr_fallback_name = "mmWave Width Maximum (Right)" + _attr_mode: NumberMode = NumberMode.BOX + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveYMin(NumberConfigurationEntity): + """Inovelli mmwave Y minimum configuration entity.""" + + _unique_id_suffix = "mmwave_y_min" + _attr_entity_category = EntityCategory.CONFIG + _attr_native_min_value: float = 0 + _attr_native_max_value: float = 600 + _attribute_name = "mmwave_y_min" + _attr_translation_key: str = "mmwave_y_min" + _attr_fallback_name = "mmWave Depth Minimum (Near)" + _attr_mode: NumberMode = NumberMode.BOX + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveYMax(NumberConfigurationEntity): + """Inovelli mmwave Y maximum configuration entity.""" + + _unique_id_suffix = "mmwave_y_max" + _attr_entity_category = EntityCategory.CONFIG + _attr_native_min_value: float = 0 + _attr_native_max_value: float = 600 + _attribute_name = "mmwave_y_max" + _attr_translation_key: str = "mmwave_y_max" + _attr_fallback_name = "mmWave Depth Maximum (Far)" + _attr_mode: NumberMode = NumberMode.BOX + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveHoldTime(NumberConfigurationEntity): + """Inovelli mmwave hold time configuration entity.""" + + _unique_id_suffix = "mmwave_hold_time" + _attr_entity_category = EntityCategory.CONFIG + _attr_native_min_value: float = 0 + _attr_native_max_value: float = 4294967296 + _attribute_name = "mmwave_hold_time" + _attr_translation_key: str = "mmwave_hold_time" + _attr_fallback_name = "mmWave Detection Timeout" + _attr_mode: NumberMode = NumberMode.BOX + + @CONFIG_DIAGNOSTIC_MATCH( cluster_handler_names="opple_cluster", models={"aqara.feeder.acn001"} ) diff --git a/zha/application/platforms/select.py b/zha/application/platforms/select.py index a943d5f82..99bd74021 100644 --- a/zha/application/platforms/select.py +++ b/zha/application/platforms/select.py @@ -31,6 +31,7 @@ CLUSTER_HANDLER_HUE_OCCUPANCY, CLUSTER_HANDLER_IAS_WD, CLUSTER_HANDLER_INOVELLI, + CLUSTER_HANDLER_INOVELLI_MMWAVE, CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_ON_OFF, CLUSTER_HANDLER_THERMOSTAT, @@ -544,6 +545,26 @@ class InovelliSwitchTypeEntity(ZCLEnumSelectEntity): _attr_translation_key: str = "switch_type" +class InovelliVZM32SwitchType(types.enum8): + """Inovelli VZM32-SN switch type.""" + + Single_Pole = 0x00 + Three_Way_AUX = 0x01 + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI, models={"VZM32-SN"} +) +class InovelliVZM32SwitchTypeEntity(ZCLEnumSelectEntity): + """Inovelli VZM32-SN switch type control.""" + + _unique_id_suffix = "switch_type" + _attribute_name = "switch_type" + _enum = InovelliVZM32SwitchType + _attr_translation_key: str = "switch_type" + _attr_fallback_name = "Switch Type" + + class InovelliFanSwitchType(types.enum1): """Inovelli fan switch mode.""" @@ -582,6 +603,97 @@ class InovelliLedScalingModeEntity(ZCLEnumSelectEntity): _attr_translation_key: str = "led_scaling_mode" +class InovelliMmwaveSensitivity(types.enum8): + """Inovelli mmwave sensitivity.""" + + Low = 0x00 + Medium = 0x01 + High = 0x02 + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveSensitivityEntity(ZCLEnumSelectEntity): + """Inovelli mmwave sensitivity control.""" + + _unique_id_suffix = "mmwave_detect_sensitivity" + _attribute_name = "mmwave_detect_sensitivity" + _enum = InovelliMmwaveSensitivity + _attr_translation_key: str = "mmwave_detect_sensitivity" + _attr_fallback_name = "mmWave Sensitivity" + + +class InovelliMmwaveTargetSpeed(types.enum8): + """Inovelli mmwave target speed.""" + + Low = 0x00 # 5s + Medium = 0x01 # 1s + Fast = 0x02 # 0.2s + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI_MMWAVE, models={"VZM32-SN"} +) +class InovelliMmwaveTargetSpeedEntity(ZCLEnumSelectEntity): + """Inovelli mmwave target speed control.""" + + _unique_id_suffix = "mmwave_detect_trigger" + _attribute_name = "mmwave_detect_trigger" + _enum = InovelliMmwaveTargetSpeed + _attr_translation_key: str = "mmwave_detect_trigger" + _attr_fallback_name = "mmWave Target Speed" + + +class InovelliMmwaveRoomSizePreset(types.enum8): + """Inovelli mmwave room size preset.""" + + Custom = 0x00 # User-defined + X_Small = 0x01 # X: -100 to 100, Y: 0 to 200, Z: -100 to 100 + Small = 0x02 # X: -160 to 160, Y: 0 to 280, Z: -100 to 100 + Medium = 0x03 # X: -210 to 210, Y: 0 to 360, Z: -100 to 100 + Large = 0x04 # X: -260 to 260, Y: 0 to 400, Z: -100 to 100 + X_Large = 0x05 # X: -310 to 310, Y: 0 to 460, Z: -100 to 100 + + +class InovelliLightOnPresenceBehavior(types.enum8): + """Inovelli light on presence behavior.""" + + Disabled = 0x00 + On_When_Occupied_Off_When_Unoccupied = 0x01 # Auto On/Off when occupied (default) + Off_When_Vacant = 0x02 # Auto Off when vacant + On_When_Occupied = 0x03 # Auto On when occupied + On_When_Vacant_Off_When_Occupied = 0x04 # Auto On/Off when Vacant + On_When_Vacant = 0x05 # Auto On when Vacant + Off_When_Occupied = 0x06 # Auto Off when Occupied + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI, models={"VZM32-SN"} +) +class InovelliLightOnPresenceBehaviorEntity(ZCLEnumSelectEntity): + """Inovelli light on presence behavior control.""" + + _unique_id_suffix = "light_on_presence_behavior" + _attribute_name = "light_on_presence_behavior" + _enum = InovelliLightOnPresenceBehavior + _attr_translation_key: str = "light_on_presence_behavior" + _attr_fallback_name = "Light On Presence Behavior" + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_INOVELLI, models={"VZM32-SN"} +) +class InovelliMmwaveRoomSizePresetEntity(ZCLEnumSelectEntity): + """Inovelli mmwave room size preset control.""" + + _unique_id_suffix = "mmwave_room_size_preset" + _attribute_name = "mmwave_room_size_preset" + _enum = InovelliMmwaveRoomSizePreset + _attr_translation_key: str = "mmwave_room_size_preset" + _attr_fallback_name = "Room Size Preset" + + class InovelliFanLedScalingMode(types.enum8): """Inovelli fan led mode.""" diff --git a/zha/zigbee/cluster_handlers/const.py b/zha/zigbee/cluster_handlers/const.py index 5cdae12c7..f3237735f 100644 --- a/zha/zigbee/cluster_handlers/const.py +++ b/zha/zigbee/cluster_handlers/const.py @@ -80,12 +80,14 @@ CLUSTER_HANDLER_ZONE: Final[str] = "ias_zone" ZONE: Final[str] = CLUSTER_HANDLER_ZONE CLUSTER_HANDLER_INOVELLI = "inovelli_vzm31sn_cluster" +CLUSTER_HANDLER_INOVELLI_MMWAVE: Final[str] = "inovelli_vzm32sn_cluster" AQARA_OPPLE_CLUSTER: Final[int] = 0xFCC0 IKEA_AIR_PURIFIER_CLUSTER: Final[int] = 0xFC7D IKEA_REMOTE_CLUSTER: Final[int] = 0xFC80 IKEA_SHORTCUT_V1_CLUSTER: Final[int] = 0xFC7F INOVELLI_CLUSTER: Final[int] = 0xFC31 +INOVELLI_MMWAVE_CLUSTER: Final[int] = 0xFC32 OSRAM_BUTTON_CLUSTER: Final[int] = 0xFD00 PHILIPS_CONTACT_CLUSTER: Final[int] = 0xFC06 PHILLIPS_REMOTE_CLUSTER: Final[int] = 0xFC00 diff --git a/zha/zigbee/cluster_handlers/manufacturerspecific.py b/zha/zigbee/cluster_handlers/manufacturerspecific.py index 89ef94e8d..8bbcf3db4 100644 --- a/zha/zigbee/cluster_handlers/manufacturerspecific.py +++ b/zha/zigbee/cluster_handlers/manufacturerspecific.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import datetime +import functools import logging from typing import TYPE_CHECKING, Any @@ -28,10 +29,13 @@ ATTRIBUTE_ID, ATTRIBUTE_NAME, ATTRIBUTE_VALUE, + CLUSTER_HANDLER_INOVELLI, + CLUSTER_HANDLER_INOVELLI_MMWAVE, IKEA_AIR_PURIFIER_CLUSTER, IKEA_REMOTE_CLUSTER, IKEA_SHORTCUT_V1_CLUSTER, INOVELLI_CLUSTER, + INOVELLI_MMWAVE_CLUSTER, LEGRAND_CABLE_OUTLET_CLUSTER, OSRAM_BUTTON_CLUSTER, PHILIPS_CONTACT_CLUSTER, @@ -358,6 +362,54 @@ def __init__(self, cluster: zigpy.zcl.Cluster, endpoint: Endpoint) -> None: "relay_click_in_on_off_mode": True, "disable_clear_notifications_double_tap": True, } + elif self.cluster.endpoint.model == "VZM32-SN": + self.ZCL_INIT_ATTRS = { + "dimming_speed_up_remote": True, + "dimming_speed_up_local": True, + "ramp_rate_off_to_on_remote": True, + "ramp_rate_off_to_on_local": True, + "dimming_speed_down_remote": True, + "dimming_speed_down_local": True, + "ramp_rate_on_to_off_remote": True, + "ramp_rate_on_to_off_local": True, + "minimum_level": True, + "maximum_level": True, + "invert_switch": True, + "auto_off_timer": True, + "default_level_local": True, + "default_level_remote": True, + "state_after_power_restored": True, + "load_level_indicator_timeout": True, + "active_power_reports": True, + "periodic_power_and_energy_reports": True, + "active_energy_reports": True, + "power_type": False, + "switch_type": True, + "increased_non_neutral_output": True, + "leading_or_trailing_edge": True, + "internal_temp_monitor": True, + "overheated": True, + "button_delay": False, + "smart_bulb_mode": False, + "double_tap_up_enabled": True, + "double_tap_down_enabled": True, + "double_tap_up_level": True, + "double_tap_down_level": True, + "led_color_when_on": True, + "led_color_when_off": True, + "led_intensity_when_on": True, + "led_intensity_when_off": True, + "led_scaling_mode": True, + "aux_switch_scenes": True, + "binding_off_to_on_sync_level": True, + "local_protection": False, + "output_mode": False, + "on_off_led_mode": True, + "firmware_progress_led": True, + "disable_clear_notifications_double_tap": True, + "light_on_presence_behavior": True, + "mmwave_room_size_preset": True, + } elif self.cluster.endpoint.model == "VZM35-SN": self.ZCL_INIT_ATTRS = { "dimming_speed_up_remote": True, @@ -432,6 +484,40 @@ async def issue_individual_led_effect( # pylint: disable=too-many-arguments,unu led_number, effect_type, color, level, duration, expect_reply=False ) + @functools.cached_property + def name(self) -> str: + """Return friendly name.""" + return CLUSTER_HANDLER_INOVELLI + + +@registries.CLUSTER_HANDLER_ONLY_CLUSTERS.register(INOVELLI_MMWAVE_CLUSTER) +@registries.CLUSTER_HANDLER_REGISTRY.register(INOVELLI_MMWAVE_CLUSTER) +class InovelliMmwaveClusterHandler(ClusterHandler): + """Inovelli mmwave cluster handler.""" + + REPORT_CONFIG = () + + def __init__(self, cluster: zigpy.zcl.Cluster, endpoint: Endpoint) -> None: + """Initialize Inovelli mmwave cluster handler.""" + super().__init__(cluster, endpoint) + if self.cluster.endpoint.model == "VZM32-SN": + self.ZCL_INIT_ATTRS = { + "mmwave_z_min": True, + "mmwave_z_max": True, + "mmwave_x_min": True, + "mmwave_x_max": True, + "mmwave_y_min": True, + "mmwave_y_max": True, + "mmwave_detect_sensitivity": True, + "mmwave_detect_trigger": True, + "mmwave_hold_time": True, + } + + @functools.cached_property + def name(self) -> str: + """Return friendly name.""" + return CLUSTER_HANDLER_INOVELLI_MMWAVE + @registries.CLUSTER_HANDLER_ONLY_CLUSTERS.register(IKEA_AIR_PURIFIER_CLUSTER) @registries.CLUSTER_HANDLER_REGISTRY.register(IKEA_AIR_PURIFIER_CLUSTER)