From 8e98810500c661d62ac4646150073a89aefa25d1 Mon Sep 17 00:00:00 2001 From: mdeweerd Date: Mon, 24 Apr 2023 18:05:35 +0200 Subject: [PATCH 1/3] Cope with deprecated module location for save_json --- custom_components/zha_toolkit/scan_device.py | 5 +++-- custom_components/zha_toolkit/utils.py | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/custom_components/zha_toolkit/scan_device.py b/custom_components/zha_toolkit/scan_device.py index 9429257..4082c50 100644 --- a/custom_components/zha_toolkit/scan_device.py +++ b/custom_components/zha_toolkit/scan_device.py @@ -314,7 +314,7 @@ async def discover_commands_received( await asyncio.sleep(0.2) except (ValueError, DeliveryError, asyncio.TimeoutError) as ex: LOGGER.error( - "Failed to discover 0x{%04x} commands starting %s. Error: %s", + "Failed to discover 0x%04x commands starting %s. Error: %s", cluster.cluster_id, cmd_id, ex, @@ -378,7 +378,8 @@ async def discover_commands_generated( await asyncio.sleep(0.2) except (ValueError, DeliveryError, asyncio.TimeoutError) as ex: LOGGER.error( - "Failed to discover commands starting %s. Error: %s", + "Failed to discover generated 0x%04X commands starting %s. Error: %s", + cluster.cluster_id, cmd_id, ex, ) diff --git a/custom_components/zha_toolkit/utils.py b/custom_components/zha_toolkit/utils.py index 4d80ffa..5e3d717 100644 --- a/custom_components/zha_toolkit/utils.py +++ b/custom_components/zha_toolkit/utils.py @@ -11,7 +11,6 @@ import packaging 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 @@ -24,6 +23,11 @@ LOGGER = logging.getLogger(__name__) +if packaging.version.parse(HA_VERSION) < packaging.version.parse("2023.4"): + from homeassistant.util.json import save_json +else: + from homeassistant.helpers.json import save_json + if typing.TYPE_CHECKING: VERSION_TIME: float = 0.0 VERSION: str = "Unknown" From dfff5c25f2c8bea5909755bb95ed32391ae1a460 Mon Sep 17 00:00:00 2001 From: mdeweerd Date: Mon, 24 Apr 2023 19:28:06 +0200 Subject: [PATCH 2/3] Format code --- custom_components/zha_toolkit/scan_device.py | 3 ++- custom_components/zha_toolkit/utils.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/custom_components/zha_toolkit/scan_device.py b/custom_components/zha_toolkit/scan_device.py index 4082c50..5a433e6 100644 --- a/custom_components/zha_toolkit/scan_device.py +++ b/custom_components/zha_toolkit/scan_device.py @@ -378,7 +378,8 @@ async def discover_commands_generated( await asyncio.sleep(0.2) except (ValueError, DeliveryError, asyncio.TimeoutError) as ex: LOGGER.error( - "Failed to discover generated 0x%04X commands starting %s. Error: %s", + "Failed to discover generated 0x%04X commands" + " starting %s. Error: %s", cluster.cluster_id, cmd_id, ex, diff --git a/custom_components/zha_toolkit/utils.py b/custom_components/zha_toolkit/utils.py index 5e3d717..b7bd792 100644 --- a/custom_components/zha_toolkit/utils.py +++ b/custom_components/zha_toolkit/utils.py @@ -24,8 +24,10 @@ LOGGER = logging.getLogger(__name__) if packaging.version.parse(HA_VERSION) < packaging.version.parse("2023.4"): + # pylint: disable=ungrouped-imports from homeassistant.util.json import save_json else: + # pylint: disable=ungrouped-imports from homeassistant.helpers.json import save_json if typing.TYPE_CHECKING: From 4de930a50d414c0b4ebeb58ce4e894ea9f7983c0 Mon Sep 17 00:00:00 2001 From: mdeweerd Date: Fri, 28 Apr 2023 11:56:17 +0200 Subject: [PATCH 3/3] Slim device list --- README.md | 16 ++-- custom_components/zha_toolkit/zha.py | 114 ++++++++++++++++----------- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index a8d49b0..c0413f4 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ ZHA Toolkit can also: - [`backup`: Backup the coordinator](#backup-backup-the-coordinator) - [`misc_settime`: Set attributes of a Time Cluster](#misc_settime-set-attributes-of-a-time-cluster) - [`ota_notify` - Download/Trigger Device FW update](#ota_notify---downloadtrigger-device-fw-update) - - [`zha_devices`: Device List Information to Event or CSV](#zha_devices-device-list-information-to-event-or-csv) + - [`zha_devices`: Device Information to Event or CSV](#zha_devices-device-information-to-event-or-csv) - [`register_services`: Reregister ZHA-Toolkit services](#register_services-reregister-zha-toolkit-services) - [`ha_set_state` - Update HA state](#ha_set_state---update-ha-state) - [User method](#user-method) @@ -1687,11 +1687,17 @@ data: path: /config/zb_ota ``` -### `zha_devices`: Device List Information to Event or CSV +### `zha_devices`: Device Information to Event or CSV -Write information from currently known ZHA devices to a CSV file. You also -get this data in the 'devices' field of the generated events which allows -you to get information about endpoints and services as well. +Get Device information as event data or in a CSV file. + +You can select the data fields in the CSV and the event data through the +command_data parameter. If you do not provide a list, a default list is +used for the CSV file, and all available data is provided in the devices +field of the event data. + +You also get this data in the 'devices' field of the generated events which +allows you to get information about endpoints and services as well. ```yaml service: zha_toolkit.zha_devices diff --git a/custom_components/zha_toolkit/zha.py b/custom_components/zha_toolkit/zha.py index cbbab74..44610ac 100644 --- a/custom_components/zha_toolkit/zha.py +++ b/custom_components/zha_toolkit/zha.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +from typing import Any from . import utils as u from .params import INTERNAL_PARAMS as p @@ -11,8 +12,43 @@ async def zha_devices( app, listener, ieee, cmd, data, service, params, event_data ): + doGenerateCSV = params[p.CSV_FILE] is not None + + # Determine fields to render. + # If the user provides a list, it is also used to + # limit the contents of "devices" in the event_data. + if data is not None and isinstance(data, list): + selectDeviceFields = True + columns = data + else: + selectDeviceFields = False + columns = [ + "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", + ] + # TODO: Skipped in columns, needs special handling + # 'signature' + # 'endpoints' devices = [device.zha_device_info for device in listener.devices.values()] + # Set default value for 'devices' in event_data, + # may be slimmed down. Ensures that devices is set in case + # an exception occurs. event_data["devices"] = devices if params[p.CSV_LABEL] is not None and isinstance( @@ -33,59 +69,47 @@ async def zha_devices( except Exception: # nosec pass - if params[p.CSV_FILE] is not None: - if data is not None and isinstance(data, list): - columns = data - else: - columns = [ - "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", - ] - # TODO: Skipped in columns, needs special handling - # 'signature' - # 'endpoints' + if doGenerateCSV or selectDeviceFields: - u.append_to_csvfile( - columns, - "csv", - params[p.CSV_FILE], - "device_dump['HEADER']", - listener=listener, - overwrite=True, - ) + if doGenerateCSV: + # Write CSV header + u.append_to_csvfile( + columns, + "csv", + params[p.CSV_FILE], + "device_dump['HEADER']", + listener=listener, + overwrite=True, + ) + slimmedDevices: list[Any] = [] for d in devices: - fields: list[int | str | None] = [] + # Fields for CSV + csvFields: list[int | str | None] = [] + # Fields for slimmed devices dict + rawFields: dict[str, Any] = {} + for c in columns: if c not in d.keys(): - fields.append(None) + csvFields.append(None) else: val = d[c] + rawFields[c] = val if c in ["manufacturer", "nwk"] and isinstance(val, int): val = f"0x{val:04X}" - fields.append(d[c]) + csvFields.append(d[c]) - LOGGER.debug("Device %r", fields) - u.append_to_csvfile( - fields, - "csv", - params[p.CSV_FILE], - f"device_dump[{d['ieee']}]", - listener=listener, - ) + slimmedDevices.append(rawFields) + + if doGenerateCSV: + LOGGER.debug("Device %r", csvFields) + u.append_to_csvfile( + csvFields, + "csv", + params[p.CSV_FILE], + f"device_dump[{d['ieee']}]", + listener=listener, + ) + if selectDeviceFields: + event_data["devices"] = slimmedDevices