Skip to content

Add support for DC Electrical Measurement #453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions tests/test_cluster_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
90 changes: 89 additions & 1 deletion tests/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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",
(
Expand Down Expand Up @@ -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(
Expand Down
39 changes: 39 additions & 0 deletions zha/application/platforms/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
111 changes: 110 additions & 1 deletion zha/zigbee/cluster_handlers/homeautomation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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):
Expand Down Expand Up @@ -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."""
Expand Down
Loading