|  | 
| 11 | 11 | from typing import TYPE_CHECKING, Any, Final, ParamSpec, TypedDict | 
| 12 | 12 | 
 | 
| 13 | 13 | import zigpy.exceptions | 
|  | 14 | +import zigpy.types | 
| 14 | 15 | import zigpy.util | 
| 15 | 16 | import zigpy.zcl | 
| 16 | 17 | from zigpy.zcl.foundation import ( | 
| 17 | 18 |     CommandSchema, | 
| 18 | 19 |     ConfigureReportingResponseRecord, | 
|  | 20 | +    DiscoverAttributesResponseRecord, | 
|  | 21 | +    GeneralCommand, | 
| 19 | 22 |     Status, | 
| 20 | 23 |     ZCLAttributeDef, | 
| 21 | 24 | ) | 
| @@ -441,6 +444,10 @@ async def async_configure(self) -> None: | 
| 441 | 444 |             if ch_specific_cfg: | 
| 442 | 445 |                 self.debug("Performing cluster handler specific configuration") | 
| 443 | 446 |                 await ch_specific_cfg() | 
|  | 447 | + | 
|  | 448 | +            self.debug("Discovering available attributes") | 
|  | 449 | +            await self.discover_unsupported_attributes() | 
|  | 450 | + | 
| 444 | 451 |             self.debug("finished cluster handler configuration") | 
| 445 | 452 |         else: | 
| 446 | 453 |             self.debug("skipping cluster handler configuration") | 
| @@ -624,6 +631,46 @@ async def write_attributes_safe( | 
| 624 | 631 |                     f"Failed to write attribute {name}={value}: {record.status}", | 
| 625 | 632 |                 ) | 
| 626 | 633 | 
 | 
|  | 634 | +    async def _discover_attributes_all( | 
|  | 635 | +        self, | 
|  | 636 | +    ) -> list[DiscoverAttributesResponseRecord] | None: | 
|  | 637 | +        discovery_complete = zigpy.types.Bool.false | 
|  | 638 | +        start_attribute_id = 0 | 
|  | 639 | +        attribute_info = [] | 
|  | 640 | +        cluster = self.cluster | 
|  | 641 | +        while discovery_complete != zigpy.types.Bool.true: | 
|  | 642 | +            rsp = await cluster.discover_attributes( | 
|  | 643 | +                start_attribute_id=start_attribute_id, max_attribute_ids=0xFF | 
|  | 644 | +            ) | 
|  | 645 | +            assert rsp, "Must have a response to discover request" | 
|  | 646 | + | 
|  | 647 | +            if rsp.command.id == GeneralCommand.Default_Response: | 
|  | 648 | +                self.debug( | 
|  | 649 | +                    "Ignoring attribute discovery due to unexpected default response" | 
|  | 650 | +                ) | 
|  | 651 | +                return None | 
|  | 652 | + | 
|  | 653 | +            attribute_info.extend(rsp.attribute_info) | 
|  | 654 | +            discovery_complete = rsp.discovery_complete | 
|  | 655 | +            start_attribute_id = ( | 
|  | 656 | +                max((info.attrid for info in rsp.attribute_info), default=0) + 1 | 
|  | 657 | +            ) | 
|  | 658 | +        return attribute_info | 
|  | 659 | + | 
|  | 660 | +    async def discover_unsupported_attributes(self): | 
|  | 661 | +        """Discover the list of unsupported attributes from the device.""" | 
|  | 662 | +        attribute_info = await self._discover_attributes_all() | 
|  | 663 | +        if attribute_info is None: | 
|  | 664 | +            return | 
|  | 665 | +        attr_ids = {info.attrid for info in attribute_info} | 
|  | 666 | + | 
|  | 667 | +        cluster = self.cluster | 
|  | 668 | +        for attr_id in cluster.attributes: | 
|  | 669 | +            if attr_id in attr_ids: | 
|  | 670 | +                cluster.remove_unsupported_attribute(attr_id) | 
|  | 671 | +            else: | 
|  | 672 | +                cluster.add_unsupported_attribute(attr_id) | 
|  | 673 | + | 
| 627 | 674 |     def log(self, level, msg, *args, **kwargs) -> None: | 
| 628 | 675 |         """Log a message.""" | 
| 629 | 676 |         msg = f"[%s:%s]: {msg}" | 
|  | 
0 commit comments