Skip to content

Commit

Permalink
Merge pull request #159 from mdeweerd/dev
Browse files Browse the repository at this point in the history
Update scan_device/zcl_attr to adapt to zigpy 0.54.X/HA 2023.4.X
  • Loading branch information
mdeweerd authored Apr 22, 2023
2 parents 1d93d24 + 03ff7c3 commit 9bdd9f5
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 70 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/stats.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
- uses: actions/checkout@v3
with:
ref: ${{ steps.extract_branch.outputs.branch }}
fetch-depth: 0
#with:
# ref: ${{ steps.extract_branch.outputs.branch }}
# fetch-depth: 0
- run: ${{ github.workspace }}/.github/scripts/gen_stats.sh
- name: Commit updated resources
uses: test-room-7/action-update-file@v1
Expand Down
88 changes: 49 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
![Downloads latest](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/latest/total.svg)
![Downloads](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/total)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
[![Open ZHA-Toolit inside your Home Assistant Community Store (HACS).](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=mdeweerd&repository=zha-toolkit&category=integration)
[![Open ZHA-Toolkit inside your Home Assistant Community Store (HACS).](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=mdeweerd&repository=zha-toolkit&category=integration)

[ZHA Toolkit](https://github.com/mdeweerd/zha-toolkit) (Zigbee Home
Assistant Toolkit) helps go beyond some limitations when using the
Expand Down Expand Up @@ -257,47 +257,57 @@ documentation updates.

This crash course's wording may deviate from Zigbee's wording.

Access to a Zigbee device's functionality is organised in Endpoints that
contain "Clusters". An Endpoint will represent some major function of a
device.

For instance if the device has two switches, there should be an Endpoint
for each switch function.\
If the device has a temperature sensor and a
humidity sensor, you may have an endpoint for each.\
An Endpoint can also
represent some "administrative" Zigbee functionality such as Over The Air
(OTA) updates or Green Power functionality.

Endpoints are organised in clusters that have attributes and commands
mostly defined in the Zigbee Cluster Library specification. A Cluster
represents some kind of "unatary" functionality of the device such as
"On/Off", "Temperature Measurement", "Color Control". These are combined in
an endpoint - for example a light bulb that you can turn On and Off and for
which you can set the color and that also measures the room temperature.
The manufacturer can extend these clusters with predefined attributes and
commands with their own.\
Attributes allow you to define how the device
should function, or let's you read or set the state or data from a device.
For instance you may write a temperature setpoint used by the device to
regulate the heat in a room. And you may read an attribute to know how much
power was already delivered by a power outlet.\
Commands let you control a
device. For instance there are commands to turn a switch on and off.
Sometimes you'll write an attribute to "command" a device, sometimes you
can use a command or write an attribute to to the same thing.\
Attributes
have a type - there are quite a few of them. For instance there are boolean
attributes, unsigned and signed byte attributes, up to arrays, timestamps
and more. (Most of the time zha-toolkit and zha will find the type without
your help.)

Home Assistant uses a Zigbee USB key or Zigbee gateway which contains the
Zigbee Coordinator. The Coordinator is the "central" node in the network.
To start, we can say that there is a network oriented view, and a device
oriented view.

The network has one coordinator (the key or device that your Home Assistant
instance is controlling), and several other devices that are classified as
Routers and End Devices.\
Only permanently powered devices can be routers.
Everything can be an End Device.

A router will (store and) forward messages. Devices either reply to
requests, or they communicate (report) autnomously if they have a reporting
configuration and configured bindings. A reporting configuration defines
when the device should communicate attribute changes. Bindings define what
other device or group these changes should be communicated to. Commands
(such as those resulting from a button press) are also subject to bindings.

A device is internally organised into Endpoints. And endpoint could be
viewed as a grouping of all the configurations and values for a
function.\
For instance if the device has two switches, there should be an
Endpoint for each switch function.\
If the device has a temperature sensor
and a humidity sensor, you may have an endpoint for each.\
An Endpoint can
also represent some "administrative" Zigbee functionality such as Over The
Air (OTA) updates or Green Power functionality.

The Attributes associated with the Endpoints let's you control the
configuration, or get values for the current state (on/off, temperature,
etc).\
Attributes are grouped into Clusters that represent a small reusable
set of "features" such the On/Off state, the Color control, a temperature
measurement, energy metering, etc.\\

In practice the Clusters are "defined" on the Endpoints and the full
address of an Attribute is IEEE address/EndpointID/ClusterID/AttributeID.
The IEEE Address is a 64 bit number (8 bytes), the EndpointID a byte
(1-254), the ClusterID and AttributeID each a word (two bytes) often
expressed as a hex number such as 0x0400.

Attributes are typed. For instance there are boolean attributes, unsigned
and signed byte attributes, up to arrays, timestamps and more. (Most of the
time zha-toolkit and zha will find the type without your help.)

The Zigbee Cluster Library document (ZCL) defines "standard" attributes and
their organisation in clusters, and what liberty the manufacturers have to
add other attributes that are not defined in the ZCL.

The actions of reading or writing attributes, and commands are often
initiated from the Coordinator. Commands can also be sent by zigbee devices
\- for example a switch telling a light bulb to turn on or off.
--- for example a switch telling a light bulb to turn on or off.

To avoid traffic on the network by "polling" devices through read requests
to know their internal state, devices can be (pre-)configured to report
Expand Down
1 change: 1 addition & 0 deletions STATS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Badges showing number of downloads per version

- ![badge latest](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/latest/total.svg)
- ![badge v0.8.36](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.36/total.svg)
- ![badge v0.8.35](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.35/total.svg)
- ![badge v0.8.34](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.34/total.svg)
- ![badge v0.8.33](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.33/total.svg)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/zha_toolkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
vol.Required(ATTR_IEEE): vol.Any(
cv.entity_id_or_uuid, t.EUI64.convert
),
vol.Required(ATTR_COMMAND_DATA): cv.string,
vol.Optional(ATTR_COMMAND_DATA): cv.string,
vol.Optional(P.CLUSTER): vol.Any(
vol.Range(0, 0xFFFF), [vol.Range(0, 0xFFFF)]
),
Expand Down
6 changes: 3 additions & 3 deletions custom_components/zha_toolkit/binds.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,11 @@ async def unbind_group(
async def bind_ieee(
app, listener, ieee, cmd, data, service, params, event_data
):
if ieee is None or data is None:
raise ValueError("'ieee' and 'data' required")
if ieee is None:
raise ValueError("'ieee' required")

src_dev = app.get_device(ieee=ieee)
if data in [0, False]:
if data in [0, False, "0", None]:
# when command_data is set to 0 or false, bind to coordinator
data = app.ieee

Expand Down
37 changes: 28 additions & 9 deletions custom_components/zha_toolkit/scan_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from zigpy import types as t
from zigpy.exceptions import ControllerException, DeliveryError
from zigpy.util import retryable
from zigpy.zcl import foundation

from . import utils as u
from .params import INTERNAL_PARAMS as p
Expand Down Expand Up @@ -172,8 +173,6 @@ async def scan_cluster(cluster, is_server=True, manufacturer=None, tries=3):


async def discover_attributes_extended(cluster, manufacturer=None, tries=3):
from zigpy.zcl import foundation

LOGGER.debug("Discovering attributes extended")
result = {}
to_read = []
Expand Down Expand Up @@ -210,12 +209,19 @@ async def discover_attributes_extended(cluster, manufacturer=None, tries=3):
attr_id,
)
break
LOGGER.debug("Cluster %s attr_rec: %s", cluster.cluster_id, rsp)
LOGGER.debug("Cluster %s attr_recs: %s", cluster.cluster_id, rsp)
for attr_rec in rsp: # Get attribute information from response
attr_id = attr_rec.attrid
attr_name = cluster.attributes.get(
attr_id = attr_rec.attrid
attr_def = cluster.attributes.get(
attr_rec.attrid, (str(attr_rec.attrid), None)
)[0]
)
if u.is_zigpy_ge("0.50.0") and isinstance(
attr_def, foundation.ZCLAttributeDef
):
attr_name = attr_def.name
else:
attr_name = attr_def[0]
attr_type = foundation.DATA_TYPES.get(attr_rec.datatype)
access_acl = t.uint8_t(attr_rec.acl)

Expand Down Expand Up @@ -320,10 +326,16 @@ async def discover_commands_received(
)
break
for cmd_id in rsp:
cmd_data = cluster.server_commands.get(
cmd_def = cluster.server_commands.get(
cmd_id, (str(cmd_id), "not_in_zcl", None)
)
cmd_name, cmd_args, _ = cmd_data
if u.is_zigpy_ge("0.50.0") and isinstance(
cmd_def, foundation.ZCLCommandDef
):
cmd_name = cmd_def.name
cmd_args = cmd_def.schema
else:
cmd_name, cmd_args, _ = cmd_def

if not isinstance(cmd_args, str):
try:
Expand Down Expand Up @@ -377,10 +389,17 @@ async def discover_commands_generated(
)
break
for cmd_id in rsp:
cmd_data = cluster.client_commands.get(
cmd_def = cluster.client_commands.get(
cmd_id, (str(cmd_id), "not_in_zcl", None)
)
cmd_name, cmd_args, _ = cmd_data
if u.is_zigpy_ge("0.50.0") and isinstance(
cmd_def, foundation.ZCLCommandDef
):
cmd_name = cmd_def.name
cmd_args = cmd_def.schema
else:
cmd_name, cmd_args, _ = cmd_def

if not isinstance(cmd_args, str):
try:
cmd_args = [arg.__name__ for arg in cmd_args]
Expand Down
28 changes: 21 additions & 7 deletions custom_components/zha_toolkit/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,10 @@ bind_ieee:
entity:
integration: zha
command_data:
description: >-
Binding target\nEntity name,\ndevice name, or\nIEEE address of
the node to execute command"
description: 'Binding target (Entity name, device name, or IEEE address of the
node to execute command). By default: coordinator'
example: 00:0d:6f:00:05:7d:2d:34
required: true
required: false
selector:
entity:
integration: zha
Expand Down Expand Up @@ -2162,9 +2161,24 @@ zha_devices:
example: lqi
selector:
select:
options: [ieee, nwk, manufacturer, model, name, quirk_applied, quirk_class,
manufacturer_code, power_source, lqi, rssi, last_seen, available, device_type,
user_given_name, device_reg_id, area_id]
options:
- ieee
- nwk
- manufacturer
- model
- name
- quirk_applied
- quirk_class
- manufacturer_code
- power_source
- lqi
- rssi
- last_seen
- available
- device_type
- user_given_name
- device_reg_id
- area_id
event_success:
description: Event name in case of success
example: my_read_success_trigger_event
Expand Down
20 changes: 16 additions & 4 deletions custom_components/zha_toolkit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import packaging.version
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.util.json import save_json
from pkg_resources import parse_version
from zigpy import __version__ as zigpy_version
from zigpy import types as t
from zigpy.exceptions import ControllerException, DeliveryError
from zigpy.util import retryable
Expand Down Expand Up @@ -505,11 +507,15 @@ def get_attr_id(cluster, attribute):
def get_attr_type(cluster, attr_id):
"""Get type for attribute in cluster, or None if not found"""
try:
return f.DATA_TYPES.pytype_to_datatype_id(
cluster.attributes.get(attr_id, (None, f.Unknown))[1]
)
attr_def = cluster.attributes.get(attr_id, (None, f.Unknown))
if is_zigpy_ge("0.50.0") and isinstance(attr_def, f.ZCLAttributeDef):
attr_type = attr_def.type
else:
attr_type = attr_def[1]

return f.DATA_TYPES.pytype_to_datatype_id(attr_type)
except Exception: # nosec
pass
LOGGER.debug("Could not find type for %s in %r", attr_id, cluster)

return None

Expand Down Expand Up @@ -858,3 +864,9 @@ def get_local_dir() -> str:
if not os.path.isdir(local_dir):
os.mkdir(local_dir)
return local_dir


def is_zigpy_ge(version: str) -> bool:
"""Test if zigpy library is newer than version"""
# Example version value: "0.45.0"
return parse_version(zigpy_version) >= parse_version(version)
21 changes: 17 additions & 4 deletions custom_components/zha_toolkit/zcl_attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,16 @@ async def conf_report_read(
)
try:
# Try to add name of the attribute
attr_name = cluster.attributes.get(
attr_def = cluster.attributes.get(
attr_id, (str(attr_id), None)
)[0]
)
if u.is_zigpy_ge("0.50.0") and isinstance(
attr_def, f.ZCLAttributeDef
):
attr_name = attr_def.name
else:
attr_name = attr_def[0]

if attr_name is not None and attr_name != "":
r_conf["attr"] = attr_name
except Exception: # nosec
Expand Down Expand Up @@ -565,9 +572,15 @@ async def attr_write( # noqa: C901
attr_name = params[p.CSV_LABEL]
else:
try:
attr_name = cluster.attributes.get(
attr_def = cluster.attributes.get(
attr_id, (str(attr_id), None)
)[0]
)
if u.is_zigpy_ge("0.50.0") and isinstance(
attr_def, f.ZCLAttributeDef
):
attr_name = attr_def.name
else:
attr_name = attr_def[0]
except Exception:
attr_name = attr_id

Expand Down

0 comments on commit 9bdd9f5

Please sign in to comment.