Skip to content
113 changes: 113 additions & 0 deletions zha/application/platforms/number/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"}
)
Expand Down
112 changes: 112 additions & 0 deletions zha/application/platforms/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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."""

Expand Down Expand Up @@ -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."""

Expand Down
2 changes: 2 additions & 0 deletions zha/zigbee/cluster_handlers/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions zha/zigbee/cluster_handlers/manufacturerspecific.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from datetime import datetime
import functools
import logging
from typing import TYPE_CHECKING, Any

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
Loading