Skip to content

Commit a833f43

Browse files
authored
Add support for rms_current_ph_b/c (#324)
* Add support for rms_current_ph_b/c * Make max attribute name overrideable; Add tests * Use attribute for conditional create_platform_entity * Fix cluster handler config test * Use ph B as base for C to reduce duplicated attrs * Revert "Use ph B as base for C to reduce duplicated attrs" This reverts commit a0296f5. * Address review suggestions
1 parent fd6cf2f commit a833f43

File tree

4 files changed

+107
-33
lines changed

4 files changed

+107
-33
lines changed

tests/test_cluster_handlers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,17 @@ async def poll_control_device_mock(zha_gateway: Gateway) -> Device:
285285
zigpy.zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id,
286286
1,
287287
{
288+
"ac_frequency",
289+
"ac_frequency_max",
288290
"active_power",
289291
"active_power_max",
290292
"apparent_power",
291293
"rms_current",
294+
"rms_current_ph_b",
295+
"rms_current_ph_c",
292296
"rms_current_max",
297+
"rms_current_max_b",
298+
"rms_current_max_c",
293299
"rms_voltage",
294300
"rms_voltage_max",
295301
},

tests/test_sensor.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
from collections.abc import Awaitable, Callable
55
from datetime import UTC, datetime
6+
from functools import partial
67
import math
78
from typing import Any, Optional
89
from unittest.mock import AsyncMock, MagicMock
@@ -298,22 +299,27 @@ async def async_test_em_power_factor(
298299

299300

300301
async def async_test_em_rms_current(
301-
zha_gateway: Gateway, cluster: Cluster, entity: PlatformEntity
302+
current_attrid: int,
303+
current_max_attrid: int,
304+
current_max_attr_name: str,
305+
zha_gateway: Gateway,
306+
cluster: Cluster,
307+
entity: PlatformEntity,
302308
) -> None:
303309
"""Test electrical measurement RMS Current sensor."""
304310

305-
await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0508: 1234})
311+
await send_attributes_report(zha_gateway, cluster, {0: 1, current_attrid: 1234})
306312
assert_state(entity, 1.2, "A")
307313

308314
await send_attributes_report(zha_gateway, cluster, {"ac_current_divisor": 10})
309-
await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0508: 236})
315+
await send_attributes_report(zha_gateway, cluster, {0: 1, current_attrid: 236})
310316
assert_state(entity, 23.6, "A")
311317

312-
await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0508: 1236})
318+
await send_attributes_report(zha_gateway, cluster, {0: 1, current_attrid: 1236})
313319
assert_state(entity, 124, "A")
314320

315-
await send_attributes_report(zha_gateway, cluster, {0: 1, 0x050A: 88})
316-
assert entity.state["rms_current_max"] == 8.8
321+
await send_attributes_report(zha_gateway, cluster, {0: 1, current_max_attrid: 88})
322+
assert entity.state[current_max_attr_name] == 8.8
317323

318324

319325
async def async_test_em_rms_voltage(
@@ -515,10 +521,32 @@ async def async_test_change_source_timestamp(
515521
(
516522
homeautomation.ElectricalMeasurement.cluster_id,
517523
sensor.ElectricalMeasurementRMSCurrent,
518-
async_test_em_rms_current,
524+
partial(async_test_em_rms_current, 0x0508, 0x050A, "rms_current_max"),
519525
{"ac_current_divisor": 1000, "ac_current_multiplier": 1},
520526
{"active_power", "apparent_power", "rms_voltage"},
521527
),
528+
(
529+
homeautomation.ElectricalMeasurement.cluster_id,
530+
sensor.ElectricalMeasurementRMSCurrentPhB,
531+
partial(async_test_em_rms_current, 0x0908, 0x090A, "rms_current_max_ph_b"),
532+
{
533+
"ac_current_divisor": 1000,
534+
"ac_current_multiplier": 1,
535+
"rms_current_ph_b": 0,
536+
},
537+
{"active_power", "apparent_power", "rms_voltage"},
538+
),
539+
(
540+
homeautomation.ElectricalMeasurement.cluster_id,
541+
sensor.ElectricalMeasurementRMSCurrentPhC,
542+
partial(async_test_em_rms_current, 0x0A08, 0x0A0A, "rms_current_max_ph_c"),
543+
{
544+
"ac_current_divisor": 1000,
545+
"ac_current_multiplier": 1,
546+
"rms_current_ph_c": 0,
547+
},
548+
{"active_power", "apparent_power", "rms_voltage"},
549+
),
522550
(
523551
homeautomation.ElectricalMeasurement.cluster_id,
524552
sensor.ElectricalMeasurementRMSVoltage,
@@ -1122,7 +1150,11 @@ async def test_elec_measurement_skip_unsupported_attribute(
11221150
"active_power_max",
11231151
"apparent_power",
11241152
"rms_current",
1153+
"rms_current_ph_b",
1154+
"rms_current_ph_c",
11251155
"rms_current_max",
1156+
"rms_current_max_ph_b",
1157+
"rms_current_max_ph_c",
11261158
"rms_voltage",
11271159
"rms_voltage_max",
11281160
"power_factor",

zha/application/platforms/sensor/__init__.py

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class Sensor(PlatformEntity):
157157
_attr_native_unit_of_measurement: str | None = None
158158
_attr_device_class: SensorDeviceClass | None = None
159159
_attr_state_class: SensorStateClass | None = None
160+
_skip_creation_if_no_attr_cache: bool = False
160161

161162
@classmethod
162163
def create_platform_entity(
@@ -183,6 +184,12 @@ def create_platform_entity(
183184
)
184185
return None
185186

187+
if (
188+
cls._skip_creation_if_no_attr_cache
189+
and cluster_handlers[0].cluster.get(cls._attribute_name) is None
190+
):
191+
return None
192+
186193
return cls(unique_id, cluster_handlers, endpoint, device, **kwargs)
187194

188195
def __init__(
@@ -622,6 +629,7 @@ class ElectricalMeasurement(PollableSensor):
622629
_attr_device_class: SensorDeviceClass = SensorDeviceClass.POWER
623630
_attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
624631
_attr_native_unit_of_measurement: str = UnitOfPower.WATT
632+
_attr_max_attribute_name: str = None
625633
_div_mul_prefix: str | None = "ac_power"
626634

627635
def __init__(
@@ -636,17 +644,22 @@ def __init__(
636644
super().__init__(unique_id, cluster_handlers, endpoint, device, **kwargs)
637645
self._attr_extra_state_attribute_names: set[str] = {
638646
"measurement_type",
639-
f"{self._attribute_name}_max",
647+
self._max_attribute_name,
640648
}
641649

650+
@property
651+
def _max_attribute_name(self) -> str:
652+
"""Return the max attribute name."""
653+
return self._attr_max_attribute_name or f"{self._attribute_name}_max"
654+
642655
@property
643656
def state(self) -> dict[str, Any]:
644657
"""Return the state for this sensor."""
645658
response = super().state
646659
if self._cluster_handler.measurement_type is not None:
647660
response["measurement_type"] = self._cluster_handler.measurement_type
648661

649-
max_attr_name = f"{self._attribute_name}_max"
662+
max_attr_name = self._max_attribute_name
650663
if not hasattr(self._cluster_handler.cluster.AttributeDefs, max_attr_name):
651664
return response
652665

@@ -705,6 +718,28 @@ class ElectricalMeasurementRMSCurrent(PolledElectricalMeasurement):
705718
_div_mul_prefix = "ac_current"
706719

707720

721+
@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT)
722+
class ElectricalMeasurementRMSCurrentPhB(ElectricalMeasurementRMSCurrent):
723+
"""RMS current measurement."""
724+
725+
_attribute_name = "rms_current_ph_b"
726+
_unique_id_suffix = "rms_current_ph_b"
727+
_attr_translation_key: str = "rms_current_ph_b"
728+
_skip_creation_if_no_attr_cache = True
729+
_attr_max_attribute_name: str = "rms_current_max_ph_b"
730+
731+
732+
@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT)
733+
class ElectricalMeasurementRMSCurrentPhC(ElectricalMeasurementRMSCurrent):
734+
"""RMS current measurement."""
735+
736+
_attribute_name: str = "rms_current_ph_c"
737+
_unique_id_suffix: str = "rms_current_ph_c"
738+
_attr_translation_key: str = "rms_current_ph_c"
739+
_skip_creation_if_no_attr_cache = True
740+
_attr_max_attribute_name: str = "rms_current_max_ph_c"
741+
742+
708743
@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT)
709744
class ElectricalMeasurementRMSVoltage(PolledElectricalMeasurement):
710745
"""RMS Voltage measurement."""
@@ -1115,30 +1150,15 @@ class SmartEnergySummationReceived(PolledSmartEnergySummation):
11151150
_attribute_name = "current_summ_received"
11161151
_unique_id_suffix = "summation_received"
11171152
_attr_translation_key: str = "summation_received"
1118-
1119-
@classmethod
1120-
def create_platform_entity(
1121-
cls: type[Self],
1122-
unique_id: str,
1123-
cluster_handlers: list[ClusterHandler],
1124-
endpoint: Endpoint,
1125-
device: Device,
1126-
**kwargs: Any,
1127-
) -> Self | None:
1128-
"""Entity Factory.
1129-
1130-
This attribute only started to be initialized in HA 2024.2.0,
1131-
so the entity would be created on the first HA start after the
1132-
upgrade for existing devices, as the initialization to see if
1133-
an attribute is unsupported happens later in the background.
1134-
To avoid creating unnecessary entities for existing devices,
1135-
wait until the attribute was properly initialized once for now.
1136-
"""
1137-
if cluster_handlers[0].cluster.get(cls._attribute_name) is None:
1138-
return None
1139-
return super().create_platform_entity(
1140-
unique_id, cluster_handlers, endpoint, device, **kwargs
1141-
)
1153+
"""
1154+
This attribute only started to be initialized in HA 2024.2.0,
1155+
so the entity would be created on the first HA start after the
1156+
upgrade for existing devices, as the initialization to see if
1157+
an attribute is unsupported happens later in the background.
1158+
To avoid creating unnecessary entities for existing devices,
1159+
wait until the attribute was properly initialized once for now.
1160+
"""
1161+
_skip_creation_if_no_attr_cache = True
11421162

11431163

11441164
@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_PRESSURE)

zha/zigbee/cluster_handlers/homeautomation.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,26 @@ class MeasurementType(enum.IntFlag):
7777
attr=ElectricalMeasurement.AttributeDefs.rms_current.name,
7878
config=REPORT_CONFIG_OP,
7979
),
80+
AttrReportConfig(
81+
attr=ElectricalMeasurement.AttributeDefs.rms_current_ph_b.name,
82+
config=REPORT_CONFIG_OP,
83+
),
84+
AttrReportConfig(
85+
attr=ElectricalMeasurement.AttributeDefs.rms_current_ph_c.name,
86+
config=REPORT_CONFIG_OP,
87+
),
8088
AttrReportConfig(
8189
attr=ElectricalMeasurement.AttributeDefs.rms_current_max.name,
8290
config=REPORT_CONFIG_DEFAULT,
8391
),
92+
AttrReportConfig(
93+
attr=ElectricalMeasurement.AttributeDefs.rms_current_max_ph_b.name,
94+
config=REPORT_CONFIG_DEFAULT,
95+
),
96+
AttrReportConfig(
97+
attr=ElectricalMeasurement.AttributeDefs.rms_current_max_ph_c.name,
98+
config=REPORT_CONFIG_DEFAULT,
99+
),
84100
AttrReportConfig(
85101
attr=ElectricalMeasurement.AttributeDefs.rms_voltage.name,
86102
config=REPORT_CONFIG_OP,

0 commit comments

Comments
 (0)