Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement device scanning #190

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
82 changes: 82 additions & 0 deletions zha/zigbee/cluster_handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@
from typing import TYPE_CHECKING, Any, Final, ParamSpec, TypedDict

import zigpy.exceptions
import zigpy.types as t
import zigpy.util
import zigpy.zcl
from zigpy.zcl.foundation import (
GENERAL_COMMANDS,
AttributeReportingConfigWithStatus,
CommandSchema,
ConfigureReportingResponseRecord,
Direction,
DiscoverAttributesResponseRecord,
GeneralCommand,
ReadReportingConfigRecord,
Status,
ZCLAttributeAccess,
ZCLAttributeDef,
)

Expand Down Expand Up @@ -51,6 +59,8 @@
from zha.zigbee.endpoint import Endpoint

_LOGGER = logging.getLogger(__name__)

DEFAULT_RESPONSE = GENERAL_COMMANDS[GeneralCommand.Default_Response].schema
RETRYABLE_REQUEST_DECORATOR = zigpy.util.retryable_request(tries=3)
UNPROXIED_CLUSTER_METHODS = {"general_command"}

Expand Down Expand Up @@ -625,6 +635,78 @@
f"Failed to write attribute {name}={value}: {record.status}",
)

async def discover_commands(self) -> dict[str, list[int]]:
"""Discover generated and received commands for a cluster."""
response: dict[str, list[t.uint8_t]] = {}
for command_method in (

Check warning on line 641 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L640-L641

Added lines #L640 - L641 were not covered by tests
self._cluster.discover_commands_received,
self._cluster.discover_commands_generated,
):
command_id: int = 0
discovery_complete: bool = False
command_ids_discovered: list[t.uint8_t] = []
while not discovery_complete:
command_response = await command_method(

Check warning on line 649 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L645-L649

Added lines #L645 - L649 were not covered by tests
start_command_id=command_id, max_command_ids=10
)
if isinstance(command_response, DEFAULT_RESPONSE):
self.debug("Default response received: %s", command_response)
break
discovery_complete, command_ids = command_response
command_ids_discovered.extend(command_ids)
for discovered_command_id in command_ids:
self.debug("Discovered command id: %s", discovered_command_id)
command_id = discovered_command_id + 1
response[command_method.func.__name__] = command_ids_discovered
return response

Check warning on line 661 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L652-L661

Added lines #L652 - L661 were not covered by tests

async def discover_attributes(self) -> list[DiscoverAttributesResponseRecord]:
"""Discover attributes for the cluster of this cluster handler."""
attribute_id: int = 0
discovery_complete: bool = False
attributes: list[DiscoverAttributesResponseRecord] = []
while not discovery_complete:
response = await self._cluster.discover_attributes(

Check warning on line 669 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L665-L669

Added lines #L665 - L669 were not covered by tests
start_attribute_id=attribute_id, max_attribute_ids=10
)
if isinstance(response, DEFAULT_RESPONSE):
self.debug("Default response received: %s", response)
break
discovery_complete, attribute_info = response
attributes.extend(attribute_info)
for attribute in attribute_info:
self.debug(

Check warning on line 678 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L672-L678

Added lines #L672 - L678 were not covered by tests
"Discovered attribute: %s: %s",
attribute.attrid,
attribute.datatype,
)
attribute_id = attribute.attrid + 1
return attributes

Check warning on line 684 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L683-L684

Added lines #L683 - L684 were not covered by tests

async def read_attribute_report_configurations(
self, attributes: list[ZCLAttributeDef], direction: Direction
) -> list[AttributeReportingConfigWithStatus]:
"""Read attribute reporting configurations for the specified attributes."""
reportable_attributes = []
for attribute in attributes:
attribute_def = self._cluster.attributes.get(attribute.attrid)
if attribute_def and ZCLAttributeAccess.Report in attribute_def.access:
reportable_attributes.append(

Check warning on line 694 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L690-L694

Added lines #L690 - L694 were not covered by tests
ReadReportingConfigRecord(
direction=direction, attrid=attribute.attrid
)
)
if reportable_attributes:
response = await self._cluster.read_reporting_configuration(

Check warning on line 700 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L699-L700

Added lines #L699 - L700 were not covered by tests
reportable_attributes
)
if isinstance(response, DEFAULT_RESPONSE):
self.debug("Default response received: %s", response)
return []
self.debug("Report configurations: %s", response)
return response.attribute_configs
return []

Check warning on line 708 in zha/zigbee/cluster_handlers/__init__.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/cluster_handlers/__init__.py#L703-L708

Added lines #L703 - L708 were not covered by tests

def log(self, level, msg, *args, **kwargs) -> None:
"""Log a message."""
msg = f"[%s:%s]: {msg}"
Expand Down
25 changes: 25 additions & 0 deletions zha/zigbee/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,31 @@
fmt = f"{log_msg[1]} completed: %s"
zdo.debug(fmt, *(log_msg[2] + (outcome,)))

async def read_binding_table(self):
"""Read the binding table for this device."""
self.debug("Reading binding table")
(

Check warning on line 1069 in zha/zigbee/device.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/device.py#L1068-L1069

Added lines #L1068 - L1069 were not covered by tests
status,
entries,
start_index,
binding_table,
) = await self.device.zdo.Mgmt_Bind_req(0)
self.debug(

Check warning on line 1075 in zha/zigbee/device.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/device.py#L1075

Added line #L1075 was not covered by tests
"Read binding table status: %s, entries: %s, start_index: %s, binding_table: %s",
status,
entries,
start_index,
binding_table,
)

async def scan(self):
"""Scan device for ZCL details."""
await self.read_binding_table()
for endpoint_id, endpoint in self.endpoints.items():
if endpoint_id == 0:
continue
await endpoint.scan()

Check warning on line 1089 in zha/zigbee/device.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/device.py#L1085-L1089

Added lines #L1085 - L1089 were not covered by tests

def log(self, level: int, msg: str, *args: Any, **kwargs: Any) -> None:
"""Log a message."""
msg = f"[%s](%s): {msg}"
Expand Down
11 changes: 11 additions & 0 deletions zha/zigbee/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,14 @@
self.all_cluster_handlers[cluster_id]
for cluster_id in (available - claimed)
]

async def scan(self):
"""Scan the endpoint for ZCL details."""
for cluster_collection in (

Check warning on line 262 in zha/zigbee/endpoint.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/endpoint.py#L262

Added line #L262 was not covered by tests
self.all_cluster_handlers,
self.client_cluster_handlers,
):
for cluster_handler in cluster_collection.values():
_LOGGER.debug("Scanning cluster handler: %s", cluster_handler.id)
await cluster_handler.discover_attributes()
await cluster_handler.discover_commands()

Check warning on line 269 in zha/zigbee/endpoint.py

View check run for this annotation

Codecov / codecov/patch

zha/zigbee/endpoint.py#L266-L269

Added lines #L266 - L269 were not covered by tests
Loading