Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions zhaquirks/elit/ihc_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Device handler for ELIT Scandinavia IHC Link, require firmware version 1.1.14 or later."""

import logging

from zigpy.quirks import CustomCluster
from zigpy.quirks.v2 import CustomDeviceV2, QuirkBuilder
from zigpy.zcl.clusters.general import MultistateInput, OnOff
from zigpy.zdo import ZDO

from zhaquirks.const import (
COMMAND,
COMMAND_DOUBLE,
COMMAND_HOLD,
COMMAND_RELEASE,
COMMAND_SINGLE,
COMMAND_TRIPLE,
DOUBLE_PRESS,
ENDPOINT_ID,
LONG_PRESS,
LONG_RELEASE,
SHORT_PRESS,
TRIPLE_PRESS,
VALUE,
ZHA_SEND_EVENT,
)

ELIT = "ELIT Scandinavia"

ACTION_TYPE = {
0: COMMAND_RELEASE,
1: COMMAND_SINGLE,
2: COMMAND_DOUBLE,
3: COMMAND_TRIPLE,
4: COMMAND_HOLD,
}

_LOGGER = logging.getLogger(__name__)


class EHCLinkDevice(CustomDeviceV2):
"""Quirk for ELIT Scandinavia EHC DIM Zigbee device."""

async def apply_custom_configuration(self, *args, **kwargs):
"""Apply custom configuration to device. Bind multistate input cluster and configure reporting."""
for endpoint in self.endpoints.values():
if isinstance(endpoint, ZDO):
continue
cluster = endpoint.in_clusters.get(MultistateInput.cluster_id)
if cluster:
await cluster.bind()
await cluster.configure_reporting(
MultistateInput.AttributeDefs.present_value.id, 0, 0, 0
)

cluster = endpoint.in_clusters.get(OnOff.cluster_id)
if cluster:
await cluster.bind()
Comment on lines +43 to +57
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is binding and manually configuring reporting really required here?

We already bind the MultistateInput cluster by default and set up reporting for the present_value attribute in ZHA: https://github.com/zigpy/zha/blob/695475a927451524475bdcef90a25177727e2345/zha/zigbee/cluster_handlers/general.py#L383-L392


# also apply custom configuration to clusters if defined
await super().apply_custom_configuration(*args, **kwargs)


class CustomMultistateInputCluster(CustomCluster, MultistateInput):
"""Multistate input cluster."""

def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if (
attrid == MultistateInput.AttributeDefs.present_value.id
and (action := ACTION_TYPE.get(value)) is not None
):
event_args = {VALUE: value}
self.listener_event(ZHA_SEND_EVENT, action, event_args)


(
QuirkBuilder(ELIT, "IHC Link")
.device_class(EHCLinkDevice)
.skip_configuration()
Copy link
Collaborator

@TheJulianJES TheJulianJES Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see you're skipping the ZHA configuration here. I don't think we should do that.

Can you provide the full device signature to see if there are any clusters that perhaps shouldn't be bound (which I'm guessing this is why you've added this)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there are really many endpoints (34 endpoints in worst case) and therefore we have to skip the automatic bindings, otherwise the device will work very slow and in some cases the binding table runs full. For the endpoints with MultistateInput, only this attribute needs binding.
This device enables/disables endpoints when it powered up the first time (is static after joining), depending on what is connected to it wired databusses. So it can have either 32 input endpoints, or 16 inputs + 8 outputs, or 16 outputs.

.replace_cluster_occurrences(
CustomMultistateInputCluster, replace_client_instances=False
)
.device_automation_triggers(
{
**{
(press_type, f"Button A.{i}"): {
COMMAND: command,
ENDPOINT_ID: i,
}
for i in list(range(1, 9)) + list(range(11, 19))
for press_type, command in {
(SHORT_PRESS, COMMAND_SINGLE),
(DOUBLE_PRESS, COMMAND_DOUBLE),
(TRIPLE_PRESS, COMMAND_TRIPLE),
(LONG_PRESS, COMMAND_HOLD),
(LONG_RELEASE, COMMAND_RELEASE),
}
},
**{
(press_type, f"Button B.{i}"): {
COMMAND: command,
ENDPOINT_ID: i + 30,
}
for i in list(range(1, 9)) + list(range(11, 19))
for press_type, command in {
(SHORT_PRESS, COMMAND_SINGLE),
(DOUBLE_PRESS, COMMAND_DOUBLE),
(TRIPLE_PRESS, COMMAND_TRIPLE),
(LONG_PRESS, COMMAND_HOLD),
(LONG_RELEASE, COMMAND_RELEASE),
}
},
}
)
.add_to_registry()
)

Unchanged files with check annotations Beta

self.debug("handle_set_time_request payload: %s", payload)
payload_rsp = TuyaTimePayload()
utc_now = datetime.datetime.utcnow() # noqa: DTZ003

Check warning on line 314 in zhaquirks/tuya/mcu/__init__.py

GitHub Actions / shared-ci / Run tests Python 3.13

datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).

Check warning on line 314 in zhaquirks/tuya/mcu/__init__.py

GitHub Actions / shared-ci / Run tests Python 3.12

datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.now()
offset_time = datetime.datetime(self.set_time_offset, 1, 1)