diff --git a/tests/test_cluster_handlers.py b/tests/test_cluster_handlers.py index 690f53d1..f8e65478 100644 --- a/tests/test_cluster_handlers.py +++ b/tests/test_cluster_handlers.py @@ -285,20 +285,28 @@ async def poll_control_device_mock(zha_gateway: Gateway) -> Device: zigpy.zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id, 1, { - "ac_frequency", - "ac_voltage_divisor", "ac_current_divisor", - "ac_power_divisor", - "ac_voltage_multiplier", - "ac_power_multiplier", - "power_divisor", - "power_multiplier", "ac_current_multiplier", "ac_frequency", + "ac_power_divisor", + "ac_power_multiplier", + "ac_voltage_divisor", + "ac_voltage_multiplier", "active_power", "active_power_ph_b", "active_power_ph_c", "apparent_power", + "dc_current", + "dc_current_divisor", + "dc_current_multiplier", + "dc_power", + "dc_power_divisor", + "dc_power_multiplier", + "dc_voltage", + "dc_voltage_divisor", + "dc_voltage_multiplier", + "power_divisor", + "power_multiplier", "rms_current", "rms_current_ph_b", "rms_current_ph_c", diff --git a/tests/test_sensor.py b/tests/test_sensor.py index b747e97a..71592eb4 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -89,9 +89,15 @@ def elec_measurement_zigpy_device_mock( "ac_power_multiplier": 1, "ac_voltage_divisor": 10, "ac_voltage_multiplier": 1, - "measurement_type": 8, + "measurement_type": 0x48, # PHASE_A_MEASUREMENT | DC_MEASUREMENT "power_divisor": 10, "power_multiplier": 1, + "dc_voltage_divisor": 10, + "dc_voltage_multiplier": 1, + "dc_current_divisor": 10, + "dc_current_multiplier": 1, + "dc_power_divisor": 10, + "dc_power_multiplier": 1, } return zigpy_device @@ -464,6 +470,67 @@ async def async_test_change_source_timestamp( assert entity.state["state"] == datetime(2024, 10, 4, 11, 15, 15, tzinfo=UTC) +async def async_test_em_dc_voltage( + zha_gateway: Gateway, cluster: Cluster, entity: PlatformEntity +) -> None: + """Test electrical measurement DC Voltage sensor.""" + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0100: 1234}) + assert_state(entity, 123.4, "V") + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0100: 234}) + assert_state(entity, 23.4, "V") + + await send_attributes_report(zha_gateway, cluster, {"dc_voltage_divisor": 100}) + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0100: 2236}) + assert_state(entity, 22.36, "V") + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0102: 888}) + assert entity.state["dc_voltage_max"] == 8.88 + + +async def async_test_em_dc_current( + zha_gateway: Gateway, cluster: Cluster, entity: PlatformEntity +) -> None: + """Test electrical measurement DC Current sensor.""" + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0103: 1234}) + assert_state(entity, 1.234, "A") + + await send_attributes_report(zha_gateway, cluster, {"dc_current_divisor": 10}) + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0103: 236}) + assert_state(entity, 23.6, "A") + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0103: 1236}) + assert_state(entity, 123.6, "A") + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0105: 88}) + assert entity.state["dc_current_max"] == 8.8 + + +async def async_test_em_dc_power( + zha_gateway: Gateway, cluster: Cluster, entity: PlatformEntity +) -> None: + """Test electrical measurement DC Power sensor.""" + # update divisor cached value + await send_attributes_report(zha_gateway, cluster, {"dc_power_divisor": 1}) + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0106: 100}) + assert_state(entity, 100, "W") + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0106: 99}) + assert_state(entity, 99, "W") + + await send_attributes_report(zha_gateway, cluster, {"dc_power_divisor": 10}) + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0106: 1000}) + assert_state(entity, 100, "W") + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0106: 99}) + assert_state(entity, 9.9, "W") + + await send_attributes_report(zha_gateway, cluster, {0: 1, 0x0108: 88}) + assert entity.state["dc_power_max"] == 8.8 + + @pytest.mark.parametrize( "cluster_id, entity_type, test_func, read_plug, unsupported_attrs", ( @@ -653,6 +720,27 @@ async def async_test_change_source_timestamp( None, None, ), + ( + homeautomation.ElectricalMeasurement.cluster_id, + sensor.ElectricalMeasurementDCVoltage, + async_test_em_dc_voltage, + {"dc_voltage_divisor": 10, "dc_voltage_multiplier": 1}, + {"active_power", "apparent_power", "rms_current", "rms_voltage"}, + ), + ( + homeautomation.ElectricalMeasurement.cluster_id, + sensor.ElectricalMeasurementDCCurrent, + async_test_em_dc_current, + {"dc_current_divisor": 1000, "dc_current_multiplier": 1}, + {"active_power", "apparent_power", "rms_current", "rms_voltage"}, + ), + ( + homeautomation.ElectricalMeasurement.cluster_id, + sensor.ElectricalMeasurementDCPower, + async_test_em_dc_power, + {"dc_power_divisor": 1000, "dc_power_multiplier": 1}, + {"active_power", "apparent_power", "rms_current", "rms_voltage"}, + ), ), ) async def test_sensor( diff --git a/zha/application/platforms/sensor/__init__.py b/zha/application/platforms/sensor/__init__.py index fdea9eb4..994a1f3f 100644 --- a/zha/application/platforms/sensor/__init__.py +++ b/zha/application/platforms/sensor/__init__.py @@ -910,6 +910,45 @@ class ElectricalMeasurementPowerFactorPhC(ElectricalMeasurementPowerFactor): _attr_max_attribute_name = "power_factor_max_ph_c" +@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT) +class ElectricalMeasurementDCVoltage(BaseElectricalMeasurement): + """DC Voltage measurement.""" + + _attribute_name = "dc_voltage" + _unique_id_suffix = "dc_voltage" + _attr_translation_key: str = "dc_voltage" + _attr_device_class: SensorDeviceClass = SensorDeviceClass.VOLTAGE + _attr_native_unit_of_measurement = UnitOfElectricPotential.VOLT + _divisor_attribute_name = "dc_voltage_divisor" + _multiplier_attribute_name = "dc_voltage_multiplier" + + +@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT) +class ElectricalMeasurementDCCurrent(BaseElectricalMeasurement): + """DC Current measurement.""" + + _attribute_name = "dc_current" + _unique_id_suffix = "dc_current" + _attr_translation_key: str = "dc_current" + _attr_device_class: SensorDeviceClass = SensorDeviceClass.CURRENT + _attr_native_unit_of_measurement = UnitOfElectricCurrent.AMPERE + _divisor_attribute_name = "dc_current_divisor" + _multiplier_attribute_name = "dc_current_multiplier" + + +@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT) +class ElectricalMeasurementDCPower(BaseElectricalMeasurement): + """DC Power measurement.""" + + _attribute_name = "dc_power" + _unique_id_suffix = "dc_power" + _attr_translation_key: str = "dc_power" + _attr_device_class: SensorDeviceClass = SensorDeviceClass.POWER + _attr_native_unit_of_measurement = UnitOfPower.WATT + _divisor_attribute_name = "dc_power_divisor" + _multiplier_attribute_name = "dc_power_multiplier" + + @MULTI_MATCH( generic_ids=CLUSTER_HANDLER_ST_HUMIDITY_CLUSTER, stop_on_match_group=CLUSTER_HANDLER_HUMIDITY, diff --git a/zha/zigbee/cluster_handlers/homeautomation.py b/zha/zigbee/cluster_handlers/homeautomation.py index 3d37504d..6ef6edde 100644 --- a/zha/zigbee/cluster_handlers/homeautomation.py +++ b/zha/zigbee/cluster_handlers/homeautomation.py @@ -137,6 +137,42 @@ class MeasurementType(enum.IntFlag): attr=ElectricalMeasurement.AttributeDefs.ac_frequency.name, config=REPORT_CONFIG_OP, ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_voltage_multiplier.name, + config=REPORT_CONFIG_IMMEDIATE, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_voltage_divisor.name, + config=REPORT_CONFIG_IMMEDIATE, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_current_multiplier.name, + config=REPORT_CONFIG_IMMEDIATE, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_current_divisor.name, + config=REPORT_CONFIG_IMMEDIATE, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_power_multiplier.name, + config=REPORT_CONFIG_IMMEDIATE, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_power_divisor.name, + config=REPORT_CONFIG_IMMEDIATE, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_voltage.name, + config=REPORT_CONFIG_OP, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_current.name, + config=REPORT_CONFIG_OP, + ), + AttrReportConfig( + attr=ElectricalMeasurement.AttributeDefs.dc_power.name, + config=REPORT_CONFIG_OP, + ), ) ZCL_POLLING_ATTRS = [ ElectricalMeasurement.AttributeDefs.ac_frequency.name, @@ -163,6 +199,12 @@ class MeasurementType(enum.IntFlag): ElectricalMeasurement.AttributeDefs.rms_voltage_max.name, ElectricalMeasurement.AttributeDefs.rms_voltage_max_ph_b.name, ElectricalMeasurement.AttributeDefs.rms_voltage_max_ph_c.name, + ElectricalMeasurement.AttributeDefs.dc_voltage.name, + ElectricalMeasurement.AttributeDefs.dc_voltage_max.name, + ElectricalMeasurement.AttributeDefs.dc_current.name, + ElectricalMeasurement.AttributeDefs.dc_current_max.name, + ElectricalMeasurement.AttributeDefs.dc_power.name, + ElectricalMeasurement.AttributeDefs.dc_power_max.name, ] ZCL_INIT_ATTRS = { ElectricalMeasurement.AttributeDefs.ac_frequency_divisor.name: True, @@ -181,6 +223,15 @@ class MeasurementType(enum.IntFlag): ElectricalMeasurement.AttributeDefs.rms_voltage_max.name: True, ElectricalMeasurement.AttributeDefs.rms_voltage_max_ph_b.name: True, ElectricalMeasurement.AttributeDefs.rms_voltage_max_ph_c.name: True, + ElectricalMeasurement.AttributeDefs.dc_voltage_divisor.name: True, + ElectricalMeasurement.AttributeDefs.dc_voltage_max.name: True, + ElectricalMeasurement.AttributeDefs.dc_voltage_multiplier.name: True, + ElectricalMeasurement.AttributeDefs.dc_current_divisor.name: True, + ElectricalMeasurement.AttributeDefs.dc_current_max.name: True, + ElectricalMeasurement.AttributeDefs.dc_current_multiplier.name: True, + ElectricalMeasurement.AttributeDefs.dc_power_divisor.name: True, + ElectricalMeasurement.AttributeDefs.dc_power_max.name: True, + ElectricalMeasurement.AttributeDefs.dc_power_multiplier.name: True, } async def async_update(self): @@ -266,13 +317,71 @@ def ac_power_divisor(self) -> int: @property def ac_power_multiplier(self) -> int: - """Return active power divisor.""" + """Return active power multiplier.""" return self.cluster.get( ElectricalMeasurement.AttributeDefs.ac_power_multiplier.name, self.cluster.get(ElectricalMeasurement.AttributeDefs.power_multiplier.name) or 1, ) + @property + def dc_voltage_divisor(self) -> int: + """Return DC voltage divisor.""" + return ( + self.cluster.get( + ElectricalMeasurement.AttributeDefs.dc_voltage_divisor.name + ) + or 1 + ) + + @property + def dc_voltage_multiplier(self) -> int: + """Return DC voltage multiplier.""" + return ( + self.cluster.get( + ElectricalMeasurement.AttributeDefs.dc_voltage_multiplier.name + ) + or 1 + ) + + @property + def dc_current_divisor(self) -> int: + """Return DC current divisor.""" + return ( + self.cluster.get( + ElectricalMeasurement.AttributeDefs.dc_current_divisor.name + ) + or 1 + ) + + @property + def dc_current_multiplier(self) -> int: + """Return DC current multiplier.""" + return ( + self.cluster.get( + ElectricalMeasurement.AttributeDefs.dc_current_multiplier.name + ) + or 1 + ) + + @property + def dc_power_divisor(self) -> int: + """Return DC power divisor.""" + return self.cluster.get( + ElectricalMeasurement.AttributeDefs.dc_power_divisor.name, + self.cluster.get(ElectricalMeasurement.AttributeDefs.power_divisor.name) + or 1, + ) + + @property + def dc_power_multiplier(self) -> int: + """Return DC power multiplier.""" + return self.cluster.get( + ElectricalMeasurement.AttributeDefs.dc_power_multiplier.name, + self.cluster.get(ElectricalMeasurement.AttributeDefs.power_multiplier.name) + or 1, + ) + @property def measurement_type(self) -> str | None: """Return Measurement type."""