From 3745fa4a119bbbe4fd4bc7e29cb47a2b4468630c Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:14:25 -0400 Subject: [PATCH 1/2] Move metering formatting out of cluster handler and into entity --- zha/application/platforms/sensor/__init__.py | 77 +++++++++++++++++--- zha/application/platforms/sensor/helpers.py | 22 ++++++ zha/zigbee/cluster_handlers/smartenergy.py | 66 ++--------------- 3 files changed, 96 insertions(+), 69 deletions(-) diff --git a/zha/application/platforms/sensor/__init__.py b/zha/application/platforms/sensor/__init__.py index fcfa8558e..7b534e26c 100644 --- a/zha/application/platforms/sensor/__init__.py +++ b/zha/application/platforms/sensor/__init__.py @@ -11,7 +11,7 @@ import logging import numbers import typing -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast from zhaquirks.danfoss import thermostat as danfoss_thermostat from zhaquirks.quirk_ids import DANFOSS_ALLY_THERMOSTAT @@ -21,7 +21,11 @@ from zigpy.zcl import foundation from zigpy.zcl.clusters.closures import WindowCovering from zigpy.zcl.clusters.general import Basic -from zigpy.zcl.clusters.smartenergy import Metering +from zigpy.zcl.clusters.smartenergy import ( + Metering, + MeteringUnitofMeasure, + NumberFormatting, +) from zha.application import Platform from zha.application.platforms import ( @@ -41,7 +45,10 @@ SensorDeviceClass, SensorStateClass, ) -from zha.application.platforms.sensor.helpers import resolution_to_decimal_precision +from zha.application.platforms.sensor.helpers import ( + create_number_formatter, + resolution_to_decimal_precision, +) from zha.application.registries import PLATFORM_ENTITIES from zha.decorators import periodic from zha.units import ( @@ -96,6 +103,12 @@ from zha.zigbee.device import Device from zha.zigbee.endpoint import Endpoint +DEFAULT_FORMATTING = NumberFormatting( + num_digits_right_of_decimal=1, + num_digits_left_of_decimal=15, + suppress_leading_zeros=1, +) + BATTERY_SIZES = { 0: "No battery", 1: "Built in", @@ -1100,9 +1113,43 @@ def state(self) -> dict[str, Any]: response["zcl_unit_of_measurement"] = self._cluster_handler.unit_of_measurement return response + @property + def _multiplier(self) -> int | float | None: + return self._cluster_handler.multiplier + + @_multiplier.setter + def _multiplier(self, value: int | float | None) -> None: + raise AttributeError("Cannot set multiplier directly") + + @property + def _divisor(self) -> int | float | None: + return self._cluster_handler.divisor + + @_divisor.setter + def _divisor(self, value: int | float | None) -> None: + raise AttributeError("Cannot set divisor directly") + def formatter(self, value: int) -> int | float: - """Pass through cluster handler formatter.""" - return self._cluster_handler.demand_formatter(value) + """Metering formatter.""" + # TODO: improve typing for base class + scaled_value = cast(float, super().formatter(value)) + + if ( + self._cluster_handler.unit_of_measurement + == MeteringUnitofMeasure.Kwh_and_Kwh_binary + ): + # Zigbee spec power unit is kW, but we show the value in W + value_watt = scaled_value * 1000 + if value_watt < 100: + return round(value_watt, 1) + return round(value_watt) + + demand_formater = create_number_formatter( + self._cluster_handler.demand_formatting + if self._cluster_handler.demand_formatting is not None + else DEFAULT_FORMATTING + ) + return float(demand_formater.format(scaled_value)) @dataclass(frozen=True, kw_only=True) @@ -1184,14 +1231,22 @@ class SmartEnergySummation(SmartEnergyMetering): } def formatter(self, value: int) -> int | float: - """Numeric pass-through formatter.""" - if self._cluster_handler.unit_of_measurement != 0: - return self._cluster_handler.summa_formatter(value) + """Metering summation formatter.""" + # TODO: improve typing for base class + scaled_value = cast(float, Sensor.formatter(self, value)) + + if ( + self._cluster_handler.unit_of_measurement + == MeteringUnitofMeasure.Kwh_and_Kwh_binary + ): + return scaled_value - return ( - float(self._cluster_handler.multiplier * value) - / self._cluster_handler.divisor + summation_formater = create_number_formatter( + self._cluster_handler.summation_formatting + if self._cluster_handler.summation_formatting is not None + else DEFAULT_FORMATTING ) + return float(summation_formater.format(scaled_value)) @MULTI_MATCH( diff --git a/zha/application/platforms/sensor/helpers.py b/zha/application/platforms/sensor/helpers.py index 316af4003..d7e1c3a60 100644 --- a/zha/application/platforms/sensor/helpers.py +++ b/zha/application/platforms/sensor/helpers.py @@ -1,7 +1,10 @@ """Helpers for sensor platform.""" +import functools from math import ceil, log10 +from zigpy.zcl.clusters.smartenergy import NumberFormatting + def resolution_to_decimal_precision( resolution: float, *, epsilon: float = 2**-23, max_digits: int = 16 @@ -27,3 +30,22 @@ def resolution_to_decimal_precision( # If nothing was found, fall back to the number of decimal places in epsilon return ceil(-log10(epsilon)) + + +@functools.lru_cache(maxsize=32) +def create_number_formatter(formatting: int) -> str: + """Return a formatting string, given the formatting value.""" + formatting_obj = NumberFormatting(formatting) + r_digits = formatting_obj.num_digits_right_of_decimal + l_digits = formatting_obj.num_digits_left_of_decimal + + if l_digits == 0: + l_digits = 15 + + width = r_digits + l_digits + (1 if r_digits > 0 else 0) + + if formatting_obj.suppress_leading_zeros: + # suppress leading 0 + return f"{{:{width}.{r_digits}f}}" + + return f"{{:0{width}.{r_digits}f}}" diff --git a/zha/zigbee/cluster_handlers/smartenergy.py b/zha/zigbee/cluster_handlers/smartenergy.py index 9b29a7111..723e4baaf 100644 --- a/zha/zigbee/cluster_handlers/smartenergy.py +++ b/zha/zigbee/cluster_handlers/smartenergy.py @@ -3,7 +3,6 @@ from __future__ import annotations import enum -from functools import partialmethod from typing import TYPE_CHECKING import zigpy.zcl @@ -17,7 +16,6 @@ MduPairing, Messaging, Metering, - NumberFormatting, Prepayment, Price, Tunneling, @@ -34,13 +32,6 @@ from zha.zigbee.endpoint import Endpoint -DEFAULT_FORMATTING = NumberFormatting( - num_digits_right_of_decimal=1, - num_digits_left_of_decimal=15, - suppress_leading_zeros=1, -) - - @registries.CLUSTER_HANDLER_REGISTRY.register(Calendar.cluster_id) class CalendarClusterHandler(ClusterHandler): """Calendar cluster handler.""" @@ -306,18 +297,15 @@ def unit_of_measurement(self) -> int: """Return unit of measurement.""" return self.cluster.get(Metering.AttributeDefs.unit_of_measure.name) - async def async_initialize_cluster_handler_specific(self, from_cache: bool) -> None: # pylint: disable=unused-argument - """Fetch config from device and updates format specifier.""" - - fmting = self.cluster.get( - Metering.AttributeDefs.demand_formatting.name, DEFAULT_FORMATTING - ) - self._format_spec = self.get_formatting(fmting) + @property + def demand_formatting(self) -> int | None: + """Return demand formatting.""" + return self.cluster.get(Metering.AttributeDefs.demand_formatting.name) - fmting = self.cluster.get( - Metering.AttributeDefs.summation_formatting.name, DEFAULT_FORMATTING - ) - self._summa_format = self.get_formatting(fmting) + @property + def summation_formatting(self) -> int | None: + """Return summation formatting.""" + return self.cluster.get(Metering.AttributeDefs.summation_formatting.name) async def async_update(self) -> None: """Retrieve latest state.""" @@ -330,44 +318,6 @@ async def async_update(self) -> None: ] await self.get_attributes(attrs, from_cache=False, only_cache=False) - @staticmethod - def get_formatting(formatting: int) -> str: - """Return a formatting string, given the formatting value.""" - formatting_obj = NumberFormatting(formatting) - r_digits = formatting_obj.num_digits_right_of_decimal - l_digits = formatting_obj.num_digits_left_of_decimal - - if l_digits == 0: - l_digits = 15 - - width = r_digits + l_digits + (1 if r_digits > 0 else 0) - - if formatting_obj.suppress_leading_zeros: - # suppress leading 0 - return f"{{:{width}.{r_digits}f}}" - - return f"{{:0{width}.{r_digits}f}}" - - def _formatter_function( - self, selector: FormatSelector, value: int - ) -> int | float | str: - """Return formatted value for display.""" - value_float = value * self.multiplier / self.divisor - if self.unit_of_measurement == 0: - # Zigbee spec power unit is kW, but we show the value in W - value_watt = value_float * 1000 - if value_watt < 100: - return round(value_watt, 1) - return round(value_watt) - if selector == self.FormatSelector.SUMMATION: - assert self._summa_format - return float(self._summa_format.format(value_float).lstrip()) - assert self._format_spec - return float(self._format_spec.format(value_float).lstrip()) - - demand_formatter = partialmethod(_formatter_function, FormatSelector.DEMAND) - summa_formatter = partialmethod(_formatter_function, FormatSelector.SUMMATION) - @registries.CLUSTER_HANDLER_REGISTRY.register(Prepayment.cluster_id) class PrepaymentClusterHandler(ClusterHandler): From 9b2d5edeef217e22ec0470ede05108bc45f27340 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:15:33 -0500 Subject: [PATCH 2/2] Add diagnostics JSON for device requiring this change --- .../data/devices/tze200-a7sghmms-ts0601.json | 897 ++++++++++++++++++ 1 file changed, 897 insertions(+) create mode 100644 tests/data/devices/tze200-a7sghmms-ts0601.json diff --git a/tests/data/devices/tze200-a7sghmms-ts0601.json b/tests/data/devices/tze200-a7sghmms-ts0601.json new file mode 100644 index 000000000..7648f931b --- /dev/null +++ b/tests/data/devices/tze200-a7sghmms-ts0601.json @@ -0,0 +1,897 @@ +{ + "version": 1, + "ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "nwk": "0x5C8B", + "manufacturer": "_TZE200_a7sghmms", + "model": "TS0601", + "friendly_manufacturer": "_TZE200_a7sghmms", + "friendly_model": "TS0601", + "name": "_TZE200_a7sghmms TS0601", + "quirk_applied": true, + "quirk_class": "zigpy.quirks.v2.CustomDeviceV2", + "quirk_id": null, + "manufacturer_code": 4417, + "power_source": "Battery or Unknown", + "lqi": null, + "rssi": null, + "last_seen": "2025-07-05T08:46:21.109364+00:00", + "available": true, + "device_type": "EndDevice", + "active_coordinator": false, + "node_descriptor": { + "logical_type": "EndDevice", + "complex_descriptor_available": false, + "user_descriptor_available": false, + "reserved": 0, + "aps_flags": 0, + "frequency_band": 8, + "mac_capability_flags": 128, + "manufacturer_code": 4417, + "maximum_buffer_size": 66, + "maximum_incoming_transfer_size": 66, + "server_mask": 10752, + "maximum_outgoing_transfer_size": 66, + "descriptor_capability_field": 0 + }, + "endpoints": { + "1": { + "profile_id": 260, + "device_type": { + "name": "SMART_PLUG", + "id": 81 + }, + "in_clusters": [ + { + "cluster_id": "0x0000", + "endpoint_attribute": "basic", + "attributes": [ + { + "id": "0x0001", + "name": "app_version", + "zcl_type": "uint8", + "value": 72 + }, + { + "id": "0x0004", + "name": "manufacturer", + "zcl_type": "string", + "value": "_TZE200_a7sghmms" + }, + { + "id": "0x0005", + "name": "model", + "zcl_type": "string", + "value": "TS0601" + } + ] + }, + { + "cluster_id": "0x0001", + "endpoint_attribute": "power", + "attributes": [ + { + "id": "0x0021", + "name": "battery_percentage_remaining", + "zcl_type": "uint8", + "value": 180 + }, + { + "id": "0x0033", + "name": "battery_quantity", + "zcl_type": "uint8", + "value": 4 + }, + { + "id": "0x0034", + "name": "battery_rated_voltage", + "zcl_type": "uint8", + "value": 15 + }, + { + "id": "0x0031", + "name": "battery_size", + "zcl_type": "enum8", + "value": 3 + } + ] + }, + { + "cluster_id": "0x0004", + "endpoint_attribute": "groups", + "attributes": [] + }, + { + "cluster_id": "0x0005", + "endpoint_attribute": "scenes", + "attributes": [] + }, + { + "cluster_id": "0x0006", + "endpoint_attribute": "on_off", + "attributes": [ + { + "id": "0x0000", + "name": "on_off", + "zcl_type": "bool", + "value": 0 + } + ] + }, + { + "cluster_id": "0x0702", + "endpoint_attribute": "smartenergy_metering", + "attributes": [ + { + "id": "0x0000", + "name": "current_summ_delivered", + "zcl_type": "uint48", + "value": 0 + }, + { + "id": "0x0400", + "name": "instantaneous_demand", + "zcl_type": "int24", + "unsupported": true + }, + { + "id": "0x0306", + "name": "metering_device_type", + "zcl_type": "map8", + "value": 2 + }, + { + "id": "0x0300", + "name": "unit_of_measure", + "zcl_type": "enum8", + "value": 7 + } + ] + }, + { + "cluster_id": "0xef00", + "endpoint_attribute": "tuya_manufacturer", + "attributes": [ + { + "id": "0xef67", + "name": "irrigation_cycles", + "zcl_type": "uint8", + "value": 0 + }, + { + "id": "0xef72", + "name": "irrigation_duration", + "zcl_type": "uint32", + "value": 6 + }, + { + "id": "0xef66", + "name": "irrigation_end_time", + "zcl_type": "string", + "value": "2025-07-05 09:33:23.709383+04:00" + }, + { + "id": "0xef69", + "name": "irrigation_interval", + "zcl_type": "uint32", + "value": 0 + }, + { + "id": "0xef01", + "name": "irrigation_mode", + "zcl_type": "bool", + "value": 0 + }, + { + "id": "0xef65", + "name": "irrigation_start_time", + "zcl_type": "string", + "value": "2025-07-05 09:33:17.905219+04:00" + }, + { + "id": "0xef68", + "name": "irrigation_target", + "zcl_type": "uint32", + "value": 0 + }, + { + "id": "0xef00", + "name": "mcu_version", + "zcl_type": "uint48", + "value": "1.0.15" + }, + { + "id": "0xef6b", + "name": "weather_delay", + "zcl_type": "enum8", + "value": 0 + } + ] + } + ], + "out_clusters": [ + { + "cluster_id": "0x000a", + "endpoint_attribute": "time", + "attributes": [] + }, + { + "cluster_id": "0x0019", + "endpoint_attribute": "ota", + "attributes": [ + { + "id": "0x0002", + "name": "current_file_version", + "zcl_type": "uint32", + "value": 72 + } + ] + } + ] + } + }, + "original_signature": {}, + "zha_lib_entities": { + "number": [ + { + "info_object": { + "fallback_name": "Irrigation cycles", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-irrigation_cycles", + "migrate_unique_ids": [], + "platform": "number", + "class_name": "NumberConfigurationEntity", + "translation_key": "irrigation_cycles", + "translation_placeholders": null, + "device_class": null, + "state_class": null, + "entity_category": "config", + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "mode": "auto", + "native_max_value": 100, + "native_min_value": 0, + "native_step": 1, + "native_unit_of_measurement": null + }, + "state": { + "class_name": "NumberConfigurationEntity", + "available": true, + "state": 0 + } + }, + { + "info_object": { + "fallback_name": "Irrigation interval", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-irrigation_interval", + "migrate_unique_ids": [], + "platform": "number", + "class_name": "NumberConfigurationEntity", + "translation_key": "irrigation_interval", + "translation_placeholders": null, + "device_class": null, + "state_class": null, + "entity_category": "config", + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "mode": "auto", + "native_max_value": 43200, + "native_min_value": 0, + "native_step": 1, + "native_unit_of_measurement": "s" + }, + "state": { + "class_name": "NumberConfigurationEntity", + "available": true, + "state": 0 + } + }, + { + "info_object": { + "fallback_name": "Irrigation target", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-irrigation_target", + "migrate_unique_ids": [], + "platform": "number", + "class_name": "NumberConfigurationEntity", + "translation_key": "irrigation_target", + "translation_placeholders": null, + "device_class": null, + "state_class": null, + "entity_category": "config", + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "mode": "auto", + "native_max_value": 43200, + "native_min_value": 0, + "native_step": 1, + "native_unit_of_measurement": null + }, + "state": { + "class_name": "NumberConfigurationEntity", + "available": true, + "state": 0 + } + } + ], + "select": [ + { + "info_object": { + "fallback_name": "Irrigation mode", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-irrigation_mode", + "migrate_unique_ids": [], + "platform": "select", + "class_name": "ZCLEnumSelectEntity", + "translation_key": "irrigation_mode", + "translation_placeholders": null, + "device_class": null, + "state_class": null, + "entity_category": "config", + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "enum": "GiexIrrigationMode", + "options": [ + "Duration", + "Capacity" + ] + }, + "state": { + "class_name": "ZCLEnumSelectEntity", + "available": true, + "state": "Duration" + } + }, + { + "info_object": { + "fallback_name": "Weather delay", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-weather_delay", + "migrate_unique_ids": [], + "platform": "select", + "class_name": "ZCLEnumSelectEntity", + "translation_key": "weather_delay", + "translation_placeholders": null, + "device_class": null, + "state_class": null, + "entity_category": "config", + "entity_registry_enabled_default": false, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "enum": "GiexIrrigationWeatherDelay", + "options": [ + "NoDelay", + "TwentyFourHourDelay", + "FortyEightHourDelay", + "SeventyTwoHourDelay" + ] + }, + "state": { + "class_name": "ZCLEnumSelectEntity", + "available": true, + "state": "NoDelay" + } + } + ], + "sensor": [ + { + "info_object": { + "fallback_name": null, + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-0-lqi", + "migrate_unique_ids": [], + "platform": "sensor", + "class_name": "LQISensor", + "translation_key": "lqi", + "translation_placeholders": null, + "device_class": null, + "state_class": "measurement", + "entity_category": "diagnostic", + "entity_registry_enabled_default": false, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "BasicClusterHandler", + "generic_id": "cluster_handler_0x0000", + "endpoint_id": 1, + "cluster": { + "id": 0, + "name": "Basic", + "type": "server" + }, + "id": "1:0x0000", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0x0000", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "suggested_display_precision": null, + "unit": null + }, + "state": { + "class_name": "LQISensor", + "available": true, + "state": null + } + }, + { + "info_object": { + "fallback_name": null, + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-0-rssi", + "migrate_unique_ids": [], + "platform": "sensor", + "class_name": "RSSISensor", + "translation_key": "rssi", + "translation_placeholders": null, + "device_class": "signal_strength", + "state_class": "measurement", + "entity_category": "diagnostic", + "entity_registry_enabled_default": false, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "BasicClusterHandler", + "generic_id": "cluster_handler_0x0000", + "endpoint_id": 1, + "cluster": { + "id": 0, + "name": "Basic", + "type": "server" + }, + "id": "1:0x0000", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0x0000", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "suggested_display_precision": null, + "unit": "dBm" + }, + "state": { + "class_name": "RSSISensor", + "available": true, + "state": null + } + }, + { + "info_object": { + "fallback_name": null, + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-1", + "migrate_unique_ids": [], + "platform": "sensor", + "class_name": "Battery", + "translation_key": null, + "translation_placeholders": null, + "device_class": "battery", + "state_class": "measurement", + "entity_category": "diagnostic", + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "PowerConfigurationClusterHandler", + "generic_id": "cluster_handler_0x0001", + "endpoint_id": 1, + "cluster": { + "id": 1, + "name": "Power Configuration", + "type": "server" + }, + "id": "1:0x0001", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0x0001", + "status": "INITIALIZED", + "value_attribute": "battery_voltage" + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "suggested_display_precision": 0, + "unit": "%" + }, + "state": { + "class_name": "Battery", + "available": true, + "state": 90.0, + "battery_size": "AA", + "battery_quantity": 4 + }, + "extra_state_attributes": [ + "battery_quantity", + "battery_size", + "battery_voltage" + ] + }, + { + "info_object": { + "fallback_name": null, + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-1794-summation_delivered", + "migrate_unique_ids": [], + "platform": "sensor", + "class_name": "SmartEnergySummation", + "translation_key": "summation_delivered", + "translation_placeholders": null, + "device_class": "volume", + "state_class": "total_increasing", + "entity_category": null, + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "MeteringClusterHandler", + "generic_id": "cluster_handler_0x0702", + "endpoint_id": 1, + "cluster": { + "id": 1794, + "name": "TuyaValveWaterConsumedNoInstDemand", + "type": "server" + }, + "id": "1:0x0702", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0x0702", + "status": "INITIALIZED", + "value_attribute": "instantaneous_demand" + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "suggested_display_precision": 3, + "unit": "L" + }, + "state": { + "class_name": "SmartEnergySummation", + "available": true, + "state": 0.0, + "device_type": "Water Metering", + "zcl_unit_of_measurement": 7 + }, + "extra_state_attributes": [ + "device_type", + "status", + "zcl_unit_of_measurement" + ] + }, + { + "info_object": { + "fallback_name": "Irrigation duration", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-irrigation_duration", + "migrate_unique_ids": [], + "platform": "sensor", + "class_name": "Sensor", + "translation_key": "irrigation_duration", + "translation_placeholders": null, + "device_class": "duration", + "state_class": "measurement", + "entity_category": null, + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "suggested_display_precision": null, + "unit": "s" + }, + "state": { + "class_name": "Sensor", + "available": true, + "state": 6 + } + }, + { + "info_object": { + "fallback_name": "Irrigation end time", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-irrigation_end_time", + "migrate_unique_ids": [], + "platform": "sensor", + "class_name": "Sensor", + "translation_key": "irrigation_end_time", + "translation_placeholders": null, + "device_class": "timestamp", + "state_class": null, + "entity_category": null, + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "suggested_display_precision": null, + "unit": null + }, + "state": { + "class_name": "Sensor", + "available": true, + "state": "2025-07-05 09:33:23.709383+04:00" + } + }, + { + "info_object": { + "fallback_name": "Irrigation start time", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-irrigation_start_time", + "migrate_unique_ids": [], + "platform": "sensor", + "class_name": "Sensor", + "translation_key": "irrigation_start_time", + "translation_placeholders": null, + "device_class": "timestamp", + "state_class": null, + "entity_category": null, + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "TuyaClusterHandler", + "generic_id": "cluster_handler_0xef00", + "endpoint_id": 1, + "cluster": { + "id": 61184, + "name": "Tuya Manufacturer Specific", + "type": "server" + }, + "id": "1:0xef00", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0xef00", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "suggested_display_precision": null, + "unit": null + }, + "state": { + "class_name": "Sensor", + "available": true, + "state": "2025-07-05 09:33:17.905219+04:00" + } + } + ], + "switch": [ + { + "info_object": { + "fallback_name": null, + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1", + "migrate_unique_ids": [], + "platform": "switch", + "class_name": "Switch", + "translation_key": "switch", + "translation_placeholders": null, + "device_class": null, + "state_class": null, + "entity_category": null, + "entity_registry_enabled_default": true, + "enabled": true, + "primary": true, + "cluster_handlers": [ + { + "class_name": "OnOffClusterHandler", + "generic_id": "cluster_handler_0x0006", + "endpoint_id": 1, + "cluster": { + "id": 6, + "name": "On/Off", + "type": "server" + }, + "id": "1:0x0006", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0x0006", + "status": "INITIALIZED", + "value_attribute": "on_off" + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null + }, + "state": { + "class_name": "Switch", + "state": 0, + "available": true + } + } + ], + "update": [ + { + "info_object": { + "fallback_name": null, + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f-1-25-firmware_update", + "migrate_unique_ids": [], + "platform": "update", + "class_name": "FirmwareUpdateEntity", + "translation_key": null, + "translation_placeholders": null, + "device_class": "firmware", + "state_class": null, + "entity_category": "config", + "entity_registry_enabled_default": true, + "enabled": true, + "primary": false, + "cluster_handlers": [ + { + "class_name": "OtaClientClusterHandler", + "generic_id": "cluster_handler_0x0019_client", + "endpoint_id": 1, + "cluster": { + "id": 25, + "name": "Ota", + "type": "client" + }, + "id": "1:0x0019_client", + "unique_id": "ab:cd:ef:12:b5:0f:0b:2f:1:0x0019_CLIENT", + "status": "INITIALIZED", + "value_attribute": null + } + ], + "device_ieee": "ab:cd:ef:12:b5:0f:0b:2f", + "endpoint_id": 1, + "available": true, + "group_id": null, + "supported_features": 7 + }, + "state": { + "class_name": "FirmwareUpdateEntity", + "available": true, + "installed_version": "0x00000048", + "in_progress": false, + "update_percentage": null, + "latest_version": null, + "release_summary": null, + "release_notes": null, + "release_url": null + } + } + ] + }, + "neighbors": [], + "routes": [] +} \ No newline at end of file