|
11 | 11 | import logging |
12 | 12 | import numbers |
13 | 13 | import typing |
14 | | -from typing import TYPE_CHECKING, Any |
| 14 | +from typing import TYPE_CHECKING, Any, cast |
15 | 15 |
|
16 | 16 | from zhaquirks.danfoss import thermostat as danfoss_thermostat |
17 | 17 | from zhaquirks.quirk_ids import DANFOSS_ALLY_THERMOSTAT |
|
21 | 21 | from zigpy.zcl import foundation |
22 | 22 | from zigpy.zcl.clusters.closures import WindowCovering |
23 | 23 | from zigpy.zcl.clusters.general import Basic |
24 | | -from zigpy.zcl.clusters.smartenergy import Metering |
| 24 | +from zigpy.zcl.clusters.smartenergy import ( |
| 25 | + Metering, |
| 26 | + MeteringUnitofMeasure, |
| 27 | + NumberFormatting, |
| 28 | +) |
25 | 29 |
|
26 | 30 | from zha.application import Platform |
27 | 31 | from zha.application.platforms import ( |
|
41 | 45 | SensorDeviceClass, |
42 | 46 | SensorStateClass, |
43 | 47 | ) |
44 | | -from zha.application.platforms.sensor.helpers import resolution_to_decimal_precision |
| 48 | +from zha.application.platforms.sensor.helpers import ( |
| 49 | + create_number_formatter, |
| 50 | + resolution_to_decimal_precision, |
| 51 | +) |
45 | 52 | from zha.application.registries import PLATFORM_ENTITIES |
46 | 53 | from zha.decorators import periodic |
47 | 54 | from zha.units import ( |
|
96 | 103 | from zha.zigbee.device import Device |
97 | 104 | from zha.zigbee.endpoint import Endpoint |
98 | 105 |
|
| 106 | +DEFAULT_FORMATTING = NumberFormatting( |
| 107 | + num_digits_right_of_decimal=1, |
| 108 | + num_digits_left_of_decimal=15, |
| 109 | + suppress_leading_zeros=1, |
| 110 | +) |
| 111 | + |
99 | 112 | BATTERY_SIZES = { |
100 | 113 | 0: "No battery", |
101 | 114 | 1: "Built in", |
@@ -1100,9 +1113,43 @@ def state(self) -> dict[str, Any]: |
1100 | 1113 | response["zcl_unit_of_measurement"] = self._cluster_handler.unit_of_measurement |
1101 | 1114 | return response |
1102 | 1115 |
|
| 1116 | + @property |
| 1117 | + def _multiplier(self) -> int | float | None: |
| 1118 | + return self._cluster_handler.multiplier |
| 1119 | + |
| 1120 | + @_multiplier.setter |
| 1121 | + def _multiplier(self, value: int | float | None) -> None: |
| 1122 | + raise AttributeError("Cannot set multiplier directly") |
| 1123 | + |
| 1124 | + @property |
| 1125 | + def _divisor(self) -> int | float | None: |
| 1126 | + return self._cluster_handler.divisor |
| 1127 | + |
| 1128 | + @_divisor.setter |
| 1129 | + def _divisor(self, value: int | float | None) -> None: |
| 1130 | + raise AttributeError("Cannot set divisor directly") |
| 1131 | + |
1103 | 1132 | def formatter(self, value: int) -> int | float: |
1104 | | - """Pass through cluster handler formatter.""" |
1105 | | - return self._cluster_handler.demand_formatter(value) |
| 1133 | + """Metering formatter.""" |
| 1134 | + # TODO: improve typing for base class |
| 1135 | + scaled_value = cast(float, super().formatter(value)) |
| 1136 | + |
| 1137 | + if ( |
| 1138 | + self._cluster_handler.unit_of_measurement |
| 1139 | + == MeteringUnitofMeasure.Kwh_and_Kwh_binary |
| 1140 | + ): |
| 1141 | + # Zigbee spec power unit is kW, but we show the value in W |
| 1142 | + value_watt = scaled_value * 1000 |
| 1143 | + if value_watt < 100: |
| 1144 | + return round(value_watt, 1) |
| 1145 | + return round(value_watt) |
| 1146 | + |
| 1147 | + demand_formater = create_number_formatter( |
| 1148 | + self._cluster_handler.demand_formatting |
| 1149 | + if self._cluster_handler.demand_formatting is not None |
| 1150 | + else DEFAULT_FORMATTING |
| 1151 | + ) |
| 1152 | + return float(demand_formater.format(scaled_value)) |
1106 | 1153 |
|
1107 | 1154 |
|
1108 | 1155 | @dataclass(frozen=True, kw_only=True) |
@@ -1184,14 +1231,22 @@ class SmartEnergySummation(SmartEnergyMetering): |
1184 | 1231 | } |
1185 | 1232 |
|
1186 | 1233 | def formatter(self, value: int) -> int | float: |
1187 | | - """Numeric pass-through formatter.""" |
1188 | | - if self._cluster_handler.unit_of_measurement != 0: |
1189 | | - return self._cluster_handler.summa_formatter(value) |
| 1234 | + """Metering summation formatter.""" |
| 1235 | + # TODO: improve typing for base class |
| 1236 | + scaled_value = cast(float, Sensor.formatter(self, value)) |
| 1237 | + |
| 1238 | + if ( |
| 1239 | + self._cluster_handler.unit_of_measurement |
| 1240 | + == MeteringUnitofMeasure.Kwh_and_Kwh_binary |
| 1241 | + ): |
| 1242 | + return scaled_value |
1190 | 1243 |
|
1191 | | - return ( |
1192 | | - float(self._cluster_handler.multiplier * value) |
1193 | | - / self._cluster_handler.divisor |
| 1244 | + summation_formater = create_number_formatter( |
| 1245 | + self._cluster_handler.summation_formatting |
| 1246 | + if self._cluster_handler.summation_formatting is not None |
| 1247 | + else DEFAULT_FORMATTING |
1194 | 1248 | ) |
| 1249 | + return float(summation_formater.format(scaled_value)) |
1195 | 1250 |
|
1196 | 1251 |
|
1197 | 1252 | @MULTI_MATCH( |
|
0 commit comments