ZHA support for additional entities on ElectricalMeasurement ZCL cluster (#56909)

* Add electrical measurement type state attribute.

* Add active_power_max attribute

* Skip unsupported attributes on entity update

* Fix tests

* Create sensor only if the main attribute is supported

* Refactor ElectricalMeasurement sensor to use attr specific divisor and multiplier

* Multiple entities for electrical measurement cluster

* Update discovery tests

* Sensor clean up

* update tests

* Pylint
pull/56974/head
Alexei Chetroi 2021-10-02 21:57:49 -04:00 committed by GitHub
parent 7e5a991de5
commit d0827a9129
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 546 additions and 70 deletions

View File

@ -1,12 +1,15 @@
"""Home automation channels module for Zigbee Home Automation."""
from __future__ import annotations
import enum
from zigpy.zcl.clusters import homeautomation
from .. import registries
from ..const import (
CHANNEL_ELECTRICAL_MEASUREMENT,
REPORT_CONFIG_DEFAULT,
REPORT_CONFIG_OP,
SIGNAL_ATTR_UPDATED,
)
from .base import ZigbeeChannel
@ -46,11 +49,36 @@ class ElectricalMeasurementChannel(ZigbeeChannel):
CHANNEL_NAME = CHANNEL_ELECTRICAL_MEASUREMENT
REPORT_CONFIG = ({"attr": "active_power", "config": REPORT_CONFIG_DEFAULT},)
class MeasurementType(enum.IntFlag):
"""Measurement types."""
ACTIVE_MEASUREMENT = 1
REACTIVE_MEASUREMENT = 2
APPARENT_MEASUREMENT = 4
PHASE_A_MEASUREMENT = 8
PHASE_B_MEASUREMENT = 16
PHASE_C_MEASUREMENT = 32
DC_MEASUREMENT = 64
HARMONICS_MEASUREMENT = 128
POWER_QUALITY_MEASUREMENT = 256
REPORT_CONFIG = (
{"attr": "active_power", "config": REPORT_CONFIG_OP},
{"attr": "active_power_max", "config": REPORT_CONFIG_DEFAULT},
{"attr": "rms_current", "config": REPORT_CONFIG_OP},
{"attr": "rms_current_max", "config": REPORT_CONFIG_DEFAULT},
{"attr": "rms_voltage", "config": REPORT_CONFIG_OP},
{"attr": "rms_voltage_max", "config": REPORT_CONFIG_DEFAULT},
)
ZCL_INIT_ATTRS = {
"ac_current_divisor": True,
"ac_current_multiplier": True,
"ac_power_divisor": True,
"power_divisor": True,
"ac_power_multiplier": True,
"ac_voltage_divisor": True,
"ac_voltage_multiplier": True,
"measurement_type": True,
"power_divisor": True,
"power_multiplier": True,
}
@ -59,29 +87,65 @@ class ElectricalMeasurementChannel(ZigbeeChannel):
self.debug("async_update")
# This is a polling channel. Don't allow cache.
result = await self.get_attribute_value("active_power", from_cache=False)
if result is not None:
self.async_send_signal(
f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}",
0x050B,
"active_power",
result,
)
attrs = [
a["attr"]
for a in self.REPORT_CONFIG
if a["attr"] not in self.cluster.unsupported_attributes
]
result = await self.get_attributes(attrs, from_cache=False)
if result:
for attr, value in result.items():
self.async_send_signal(
f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}",
self.cluster.attridx.get(attr, attr),
attr,
value,
)
@property
def divisor(self) -> int | None:
def ac_current_divisor(self) -> int:
"""Return ac current divisor."""
return self.cluster.get("ac_current_divisor") or 1
@property
def ac_current_multiplier(self) -> int:
"""Return ac current multiplier."""
return self.cluster.get("ac_current_multiplier") or 1
@property
def ac_voltage_divisor(self) -> int:
"""Return ac voltage divisor."""
return self.cluster.get("ac_voltage_divisor") or 1
@property
def ac_voltage_multiplier(self) -> int:
"""Return ac voltage multiplier."""
return self.cluster.get("ac_voltage_multiplier") or 1
@property
def ac_power_divisor(self) -> int:
"""Return active power divisor."""
return self.cluster.get(
"ac_power_divisor", self.cluster.get("power_divisor", 1)
"ac_power_divisor", self.cluster.get("power_divisor") or 1
)
@property
def multiplier(self) -> int | None:
def ac_power_multiplier(self) -> int:
"""Return active power divisor."""
return self.cluster.get(
"ac_power_multiplier", self.cluster.get("power_multiplier", 1)
"ac_power_multiplier", self.cluster.get("power_multiplier") or 1
)
@property
def measurement_type(self) -> str | None:
"""Return Measurement type."""
meas_type = self.cluster.get("measurement_type")
if meas_type is None:
return None
meas_type = self.MeasurementType(meas_type)
return ", ".join(m.name for m in self.MeasurementType if m in meas_type)
@registries.ZIGBEE_CHANNEL_REGISTRY.register(
homeautomation.MeterIdentification.cluster_id

View File

@ -73,7 +73,6 @@ SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {
zcl.clusters.general.MultistateInput.cluster_id: SENSOR,
zcl.clusters.general.OnOff.cluster_id: SWITCH,
zcl.clusters.general.PowerConfiguration.cluster_id: SENSOR,
zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: SENSOR,
zcl.clusters.hvac.Fan.cluster_id: FAN,
zcl.clusters.measurement.CarbonDioxideConcentration.cluster_id: SENSOR,
zcl.clusters.measurement.CarbonMonoxideConcentration.cluster_id: SENSOR,

View File

@ -9,6 +9,7 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO,
DEVICE_CLASS_CO2,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_POWER,
@ -24,6 +25,8 @@ from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
DEVICE_CLASS_ENERGY,
ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT,
ENERGY_KILO_WATT_HOUR,
LIGHT_LUX,
PERCENTAGE,
@ -129,6 +132,24 @@ class Sensor(ZhaEntity, SensorEntity):
super().__init__(unique_id, zha_device, channels, **kwargs)
self._channel: ChannelType = channels[0]
@classmethod
def create_entity(
cls,
unique_id: str,
zha_device: ZhaDeviceType,
channels: list[ChannelType],
**kwargs,
) -> ZhaEntity | None:
"""Entity Factory.
Return entity if it is a supported configuration, otherwise return None
"""
channel = channels[0]
if cls.SENSOR_ATTR in channel.cluster.unsupported_attributes:
return None
return cls(unique_id, zha_device, channels, **kwargs)
async def async_added_to_hass(self) -> None:
"""Run when about to be added to hass."""
await super().async_added_to_hass()
@ -220,7 +241,7 @@ class Battery(Sensor):
return state_attrs
@STRICT_MATCH(channel_names=CHANNEL_ELECTRICAL_MEASUREMENT)
@MULTI_MATCH(channel_names=CHANNEL_ELECTRICAL_MEASUREMENT)
class ElectricalMeasurement(Sensor):
"""Active power measurement."""
@ -228,16 +249,32 @@ class ElectricalMeasurement(Sensor):
_device_class = DEVICE_CLASS_POWER
_state_class = STATE_CLASS_MEASUREMENT
_unit = POWER_WATT
_div_mul_prefix = "ac_power"
@property
def should_poll(self) -> bool:
"""Return True if HA needs to poll for state changes."""
return True
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return device state attrs for sensor."""
attrs = {}
if self._channel.measurement_type is not None:
attrs["measurement_type"] = self._channel.measurement_type
max_attr_name = f"{self.SENSOR_ATTR}_max"
if (max_v := self._channel.cluster.get(max_attr_name)) is not None:
attrs[max_attr_name] = str(self.formatter(max_v))
return attrs
def formatter(self, value: int) -> int | float:
"""Return 'normalized' value."""
value = value * self._channel.multiplier / self._channel.divisor
if value < 100 and self._channel.divisor > 1:
multiplier = getattr(self._channel, f"{self._div_mul_prefix}_multiplier")
divisor = getattr(self._channel, f"{self._div_mul_prefix}_divisor")
value = float(value * multiplier) / divisor
if value < 100 and divisor > 1:
return round(value, self._decimals)
return round(value)
@ -248,6 +285,36 @@ class ElectricalMeasurement(Sensor):
await super().async_update()
@MULTI_MATCH(channel_names=CHANNEL_ELECTRICAL_MEASUREMENT)
class ElectricalMeasurementRMSCurrent(ElectricalMeasurement, id_suffix="rms_current"):
"""RMS current measurement."""
SENSOR_ATTR = "rms_current"
_device_class = DEVICE_CLASS_CURRENT
_unit = ELECTRIC_CURRENT_AMPERE
_div_mul_prefix = "ac_current"
@property
def should_poll(self) -> bool:
"""Poll indirectly by ElectricalMeasurementSensor."""
return False
@MULTI_MATCH(channel_names=CHANNEL_ELECTRICAL_MEASUREMENT)
class ElectricalMeasurementRMSVoltage(ElectricalMeasurement, id_suffix="rms_voltage"):
"""RMS Voltage measurement."""
SENSOR_ATTR = "rms_voltage"
_device_class = DEVICE_CLASS_CURRENT
_unit = ELECTRIC_POTENTIAL_VOLT
_div_mul_prefix = "ac_voltage"
@property
def should_poll(self) -> bool:
"""Poll indirectly by ElectricalMeasurementSensor."""
return False
@STRICT_MATCH(generic_ids=CHANNEL_ST_HUMIDITY_CLUSTER)
@STRICT_MATCH(channel_names=CHANNEL_HUMIDITY)
class Humidity(Sensor):
@ -298,24 +365,6 @@ class SmartEnergyMetering(Sensor):
0x0C: f"MJ/{TIME_SECONDS}",
}
@classmethod
def create_entity(
cls,
unique_id: str,
zha_device: ZhaDeviceType,
channels: list[ChannelType],
**kwargs,
) -> ZhaEntity | None:
"""Entity Factory.
Return entity if it is a supported configuration, otherwise return None
"""
se_channel = channels[0]
if cls.SENSOR_ATTR in se_channel.cluster.unsupported_attributes:
return None
return cls(unique_id, zha_device, channels, **kwargs)
def formatter(self, value: int) -> int | float:
"""Pass through channel formatter."""
return self._channel.demand_formatter(value)

View File

@ -152,7 +152,18 @@ async def poll_control_device(zha_device_restored, zigpy_device_mock):
(0x0405, 1, {"measured_value"}),
(0x0406, 1, {"occupancy"}),
(0x0702, 1, {"instantaneous_demand"}),
(0x0B04, 1, {"active_power"}),
(
0x0B04,
1,
{
"active_power",
"active_power_max",
"rms_current",
"rms_current_max",
"rms_voltage",
"rms_voltage_max",
},
),
],
)
async def test_in_channel_config(

View File

@ -1,5 +1,5 @@
"""Test zha sensor."""
from unittest import mock
import math
import pytest
import zigpy.profiles.zha
@ -9,6 +9,7 @@ import zigpy.zcl.clusters.measurement as measurement
import zigpy.zcl.clusters.smartenergy as smartenergy
from homeassistant.components.sensor import DOMAIN
from homeassistant.components.zha.core.const import ZHA_CHANNEL_READS_PER_REQ
import homeassistant.config as config_util
from homeassistant.const import (
ATTR_DEVICE_CLASS,
@ -17,6 +18,8 @@ from homeassistant.const import (
CONF_UNIT_SYSTEM_IMPERIAL,
CONF_UNIT_SYSTEM_METRIC,
DEVICE_CLASS_ENERGY,
ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT,
ENERGY_KILO_WATT_HOUR,
LIGHT_LUX,
PERCENTAGE,
@ -30,6 +33,7 @@ from homeassistant.const import (
VOLUME_CUBIC_METERS,
)
from homeassistant.helpers import restore_state
from homeassistant.helpers.entity_component import async_update_entity
from homeassistant.util import dt as dt_util
from .common import (
@ -40,11 +44,52 @@ from .common import (
send_attribute_report,
send_attributes_report,
)
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_TYPE
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
ENTITY_ID_PREFIX = "sensor.fakemanufacturer_fakemodel_e769900a_{}"
@pytest.fixture
async def elec_measurement_zigpy_dev(hass, zigpy_device_mock):
"""Electric Measurement zigpy device."""
zigpy_device = zigpy_device_mock(
{
1: {
SIG_EP_INPUT: [
general.Basic.cluster_id,
homeautomation.ElectricalMeasurement.cluster_id,
],
SIG_EP_OUTPUT: [],
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.SIMPLE_SENSOR,
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
}
},
)
zigpy_device.node_desc.mac_capability_flags |= 0b_0000_0100
zigpy_device.endpoints[1].electrical_measurement.PLUGGED_ATTR_READS = {
"ac_current_divisor": 10,
"ac_current_multiplier": 1,
"ac_power_divisor": 10,
"ac_power_multiplier": 1,
"ac_voltage_divisor": 10,
"ac_voltage_multiplier": 1,
"measurement_type": 8,
"power_divisor": 10,
"power_multiplier": 1,
}
return zigpy_device
@pytest.fixture
async def elec_measurement_zha_dev(elec_measurement_zigpy_dev, zha_device_joined):
"""Electric Measurement ZHA device."""
zha_dev = await zha_device_joined(elec_measurement_zigpy_dev)
zha_dev.available = True
return zha_dev
async def async_test_humidity(hass, cluster, entity_id):
"""Test humidity sensor."""
await send_attributes_report(hass, cluster, {1: 1, 0: 1000, 2: 100})
@ -109,26 +154,60 @@ async def async_test_smart_energy_summation(hass, cluster, entity_id):
async def async_test_electrical_measurement(hass, cluster, entity_id):
"""Test electrical measurement sensor."""
with mock.patch(
(
"homeassistant.components.zha.core.channels.homeautomation"
".ElectricalMeasurementChannel.divisor"
),
new_callable=mock.PropertyMock,
) as divisor_mock:
divisor_mock.return_value = 1
await send_attributes_report(hass, cluster, {0: 1, 1291: 100, 10: 1000})
assert_state(hass, entity_id, "100", POWER_WATT)
# update divisor cached value
await send_attributes_report(hass, cluster, {"ac_power_divisor": 1})
await send_attributes_report(hass, cluster, {0: 1, 1291: 100, 10: 1000})
assert_state(hass, entity_id, "100", POWER_WATT)
await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 1000})
assert_state(hass, entity_id, "99", POWER_WATT)
await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 1000})
assert_state(hass, entity_id, "99", POWER_WATT)
divisor_mock.return_value = 10
await send_attributes_report(hass, cluster, {0: 1, 1291: 1000, 10: 5000})
assert_state(hass, entity_id, "100", POWER_WATT)
await send_attributes_report(hass, cluster, {"ac_power_divisor": 10})
await send_attributes_report(hass, cluster, {0: 1, 1291: 1000, 10: 5000})
assert_state(hass, entity_id, "100", POWER_WATT)
await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 5000})
assert_state(hass, entity_id, "9.9", POWER_WATT)
await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 5000})
assert_state(hass, entity_id, "9.9", POWER_WATT)
assert "active_power_max" not in hass.states.get(entity_id).attributes
await send_attributes_report(hass, cluster, {0: 1, 0x050D: 88, 10: 5000})
assert hass.states.get(entity_id).attributes["active_power_max"] == "8.8"
async def async_test_em_rms_current(hass, cluster, entity_id):
"""Test electrical measurement RMS Current sensor."""
await send_attributes_report(hass, cluster, {0: 1, 0x0508: 1234, 10: 1000})
assert_state(hass, entity_id, "1.2", ELECTRIC_CURRENT_AMPERE)
await send_attributes_report(hass, cluster, {"ac_current_divisor": 10})
await send_attributes_report(hass, cluster, {0: 1, 0x0508: 236, 10: 1000})
assert_state(hass, entity_id, "23.6", ELECTRIC_CURRENT_AMPERE)
await send_attributes_report(hass, cluster, {0: 1, 0x0508: 1236, 10: 1000})
assert_state(hass, entity_id, "124", ELECTRIC_CURRENT_AMPERE)
assert "rms_current_max" not in hass.states.get(entity_id).attributes
await send_attributes_report(hass, cluster, {0: 1, 0x050A: 88, 10: 5000})
assert hass.states.get(entity_id).attributes["rms_current_max"] == "8.8"
async def async_test_em_rms_voltage(hass, cluster, entity_id):
"""Test electrical measurement RMS Voltage sensor."""
await send_attributes_report(hass, cluster, {0: 1, 0x0505: 1234, 10: 1000})
assert_state(hass, entity_id, "123", ELECTRIC_POTENTIAL_VOLT)
await send_attributes_report(hass, cluster, {0: 1, 0x0505: 234, 10: 1000})
assert_state(hass, entity_id, "23.4", ELECTRIC_POTENTIAL_VOLT)
await send_attributes_report(hass, cluster, {"ac_voltage_divisor": 100})
await send_attributes_report(hass, cluster, {0: 1, 0x0505: 2236, 10: 1000})
assert_state(hass, entity_id, "22.4", ELECTRIC_POTENTIAL_VOLT)
assert "rms_voltage_max" not in hass.states.get(entity_id).attributes
await send_attributes_report(hass, cluster, {0: 1, 0x0507: 888, 10: 5000})
assert hass.states.get(entity_id).attributes["rms_voltage_max"] == "8.9"
async def async_test_powerconfiguration(hass, cluster, entity_id):
@ -211,9 +290,25 @@ async def async_test_powerconfiguration(hass, cluster, entity_id):
homeautomation.ElectricalMeasurement.cluster_id,
"electrical_measurement",
async_test_electrical_measurement,
1,
None,
None,
6,
{"ac_power_divisor": 1000, "ac_power_multiplier": 1},
{"rms_current", "rms_voltage"},
),
(
homeautomation.ElectricalMeasurement.cluster_id,
"electrical_measurement_rms_current",
async_test_em_rms_current,
6,
{"ac_current_divisor": 1000, "ac_current_multiplier": 1},
{"active_power", "rms_voltage"},
),
(
homeautomation.ElectricalMeasurement.cluster_id,
"electrical_measurement_rms_voltage",
async_test_em_rms_voltage,
6,
{"ac_voltage_divisor": 10, "ac_voltage_multiplier": 1},
{"active_power", "rms_current"},
),
(
general.PowerConfiguration.cluster_id,
@ -255,7 +350,10 @@ async def test_sensor(
if unsupported_attrs:
for attr in unsupported_attrs:
cluster.add_unsupported_attribute(attr)
if cluster_id == smartenergy.Metering.cluster_id:
if cluster_id in (
smartenergy.Metering.cluster_id,
homeautomation.ElectricalMeasurement.cluster_id,
):
# this one is mains powered
zigpy_device.node_desc.mac_capability_flags |= 0b_0000_0100
cluster.PLUGGED_ATTR_READS = read_plug
@ -432,35 +530,60 @@ async def test_electrical_measurement_init(
assert int(hass.states.get(entity_id).state) == 100
channel = zha_device.channels.pools[0].all_channels["1:0x0b04"]
assert channel.divisor == 1
assert channel.multiplier == 1
assert channel.ac_power_divisor == 1
assert channel.ac_power_multiplier == 1
# update power divisor
await send_attributes_report(hass, cluster, {0: 1, 1291: 20, 0x0403: 5, 10: 1000})
assert channel.divisor == 5
assert channel.multiplier == 1
assert channel.ac_power_divisor == 5
assert channel.ac_power_multiplier == 1
assert hass.states.get(entity_id).state == "4.0"
await send_attributes_report(hass, cluster, {0: 1, 1291: 30, 0x0605: 10, 10: 1000})
assert channel.divisor == 10
assert channel.multiplier == 1
assert channel.ac_power_divisor == 10
assert channel.ac_power_multiplier == 1
assert hass.states.get(entity_id).state == "3.0"
# update power multiplier
await send_attributes_report(hass, cluster, {0: 1, 1291: 20, 0x0402: 6, 10: 1000})
assert channel.divisor == 10
assert channel.multiplier == 6
assert channel.ac_power_divisor == 10
assert channel.ac_power_multiplier == 6
assert hass.states.get(entity_id).state == "12.0"
await send_attributes_report(hass, cluster, {0: 1, 1291: 30, 0x0604: 20, 10: 1000})
assert channel.divisor == 10
assert channel.multiplier == 20
assert channel.ac_power_divisor == 10
assert channel.ac_power_multiplier == 20
assert hass.states.get(entity_id).state == "60.0"
@pytest.mark.parametrize(
"cluster_id, unsupported_attributes, entity_ids, missing_entity_ids",
(
(
homeautomation.ElectricalMeasurement.cluster_id,
{"rms_voltage", "rms_current"},
{"electrical_measurement"},
{
"electrical_measurement_rms_voltage",
"electrical_measurement_rms_current",
},
),
(
homeautomation.ElectricalMeasurement.cluster_id,
{"rms_current"},
{"electrical_measurement_rms_voltage", "electrical_measurement"},
{"electrical_measurement_rms_current"},
),
(
homeautomation.ElectricalMeasurement.cluster_id,
set(),
{
"electrical_measurement_rms_voltage",
"electrical_measurement",
"electrical_measurement_rms_current",
},
set(),
),
(
smartenergy.Metering.cluster_id,
{
@ -650,3 +773,101 @@ async def test_se_summation_uom(
await zha_device_joined(zigpy_device)
assert_state(hass, entity_id, expected_state, expected_uom)
@pytest.mark.parametrize(
"raw_measurement_type, expected_type",
(
(1, "ACTIVE_MEASUREMENT"),
(8, "PHASE_A_MEASUREMENT"),
(9, "ACTIVE_MEASUREMENT, PHASE_A_MEASUREMENT"),
(
15,
"ACTIVE_MEASUREMENT, REACTIVE_MEASUREMENT, APPARENT_MEASUREMENT, PHASE_A_MEASUREMENT",
),
),
)
async def test_elec_measurement_sensor_type(
hass,
elec_measurement_zigpy_dev,
raw_measurement_type,
expected_type,
zha_device_joined,
):
"""Test zha electrical measurement sensor type."""
entity_id = ENTITY_ID_PREFIX.format("electrical_measurement")
zigpy_dev = elec_measurement_zigpy_dev
zigpy_dev.endpoints[1].electrical_measurement.PLUGGED_ATTR_READS[
"measurement_type"
] = raw_measurement_type
await zha_device_joined(zigpy_dev)
state = hass.states.get(entity_id)
assert state is not None
assert state.attributes["measurement_type"] == expected_type
@pytest.mark.parametrize(
"supported_attributes",
(
set(),
{
"active_power",
"active_power_max",
"rms_current",
"rms_current_max",
"rms_voltage",
"rms_voltage_max",
},
{
"active_power",
},
{
"active_power",
"active_power_max",
},
{
"rms_current",
"rms_current_max",
},
{
"rms_voltage",
"rms_voltage_max",
},
),
)
async def test_elec_measurement_skip_unsupported_attribute(
hass,
elec_measurement_zha_dev,
supported_attributes,
):
"""Test zha electrical measurement skipping update of unsupported attributes."""
entity_id = ENTITY_ID_PREFIX.format("electrical_measurement")
zha_dev = elec_measurement_zha_dev
cluster = zha_dev.device.endpoints[1].electrical_measurement
all_attrs = {
"active_power",
"active_power_max",
"rms_current",
"rms_current_max",
"rms_voltage",
"rms_voltage_max",
}
for attr in all_attrs - supported_attributes:
cluster.add_unsupported_attribute(attr)
cluster.read_attributes.reset_mock()
await async_update_entity(hass, entity_id)
await hass.async_block_till_done()
assert cluster.read_attributes.call_count == math.ceil(
len(supported_attributes) / ZHA_CHANNEL_READS_PER_REQ
)
read_attrs = {
a for call in cluster.read_attributes.call_args_list for a in call[0][0]
}
assert read_attrs == supported_attributes

View File

@ -117,6 +117,8 @@ DEVICES = [
},
DEV_SIG_ENTITIES: [
"sensor.centralite_3210_l_77665544_electrical_measurement",
"sensor.centralite_3210_l_77665544_electrical_measurement_rms_current",
"sensor.centralite_3210_l_77665544_electrical_measurement_rms_voltage",
"sensor.centralite_3210_l_77665544_smartenergy_metering",
"sensor.centralite_3210_l_77665544_smartenergy_metering_summation_delivered",
"switch.centralite_3210_l_77665544_on_off",
@ -142,6 +144,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.centralite_3210_l_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.centralite_3210_l_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.centralite_3210_l_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["1:0x0019"],
SIG_MANUFACTURER: "CentraLite",
@ -1436,6 +1448,8 @@ DEVICES = [
"sensor.lumi_lumi_plug_maus01_77665544_analog_input",
"sensor.lumi_lumi_plug_maus01_77665544_analog_input_2",
"sensor.lumi_lumi_plug_maus01_77665544_electrical_measurement",
"sensor.lumi_lumi_plug_maus01_77665544_electrical_measurement_rms_current",
"sensor.lumi_lumi_plug_maus01_77665544_electrical_measurement_rms_voltage",
"switch.lumi_lumi_plug_maus01_77665544_on_off",
],
DEV_SIG_ENT_MAP: {
@ -1459,6 +1473,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_plug_maus01_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_plug_maus01_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_plug_maus01_77665544_electrical_measurement_rms_voltage",
},
("binary_sensor", "00:11:22:33:44:55:66:77-100-15"): {
DEV_SIG_CHANNELS: ["binary_input"],
DEV_SIG_ENT_MAP_CLASS: "BinaryInput",
@ -1493,6 +1517,8 @@ DEVICES = [
"light.lumi_lumi_relay_c2acn01_77665544_on_off",
"light.lumi_lumi_relay_c2acn01_77665544_on_off_2",
"sensor.lumi_lumi_relay_c2acn01_77665544_electrical_measurement",
"sensor.lumi_lumi_relay_c2acn01_77665544_electrical_measurement_rms_current",
"sensor.lumi_lumi_relay_c2acn01_77665544_electrical_measurement_rms_voltage",
],
DEV_SIG_ENT_MAP: {
("light", "00:11:22:33:44:55:66:77-1"): {
@ -1505,6 +1531,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_relay_c2acn01_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_relay_c2acn01_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.lumi_lumi_relay_c2acn01_77665544_electrical_measurement_rms_voltage",
},
("light", "00:11:22:33:44:55:66:77-2"): {
DEV_SIG_CHANNELS: ["on_off"],
DEV_SIG_ENT_MAP_CLASS: "Light",
@ -2566,6 +2602,8 @@ DEVICES = [
DEV_SIG_ENTITIES: [
"light.osram_lightify_rt_tunable_white_77665544_level_light_color_on_off",
"sensor.osram_lightify_rt_tunable_white_77665544_electrical_measurement",
"sensor.osram_lightify_rt_tunable_white_77665544_electrical_measurement_rms_current",
"sensor.osram_lightify_rt_tunable_white_77665544_electrical_measurement_rms_voltage",
],
DEV_SIG_ENT_MAP: {
("light", "00:11:22:33:44:55:66:77-3"): {
@ -2578,6 +2616,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.osram_lightify_rt_tunable_white_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-3-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.osram_lightify_rt_tunable_white_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-3-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.osram_lightify_rt_tunable_white_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["3:0x0019"],
SIG_MANUFACTURER: "OSRAM",
@ -2598,6 +2646,8 @@ DEVICES = [
},
DEV_SIG_ENTITIES: [
"sensor.osram_plug_01_77665544_electrical_measurement",
"sensor.osram_plug_01_77665544_electrical_measurement_rms_current",
"sensor.osram_plug_01_77665544_electrical_measurement_rms_voltage",
"switch.osram_plug_01_77665544_on_off",
],
DEV_SIG_ENT_MAP: {
@ -2611,6 +2661,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.osram_plug_01_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-3-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.osram_plug_01_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-3-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.osram_plug_01_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["3:0x0019"],
SIG_MANUFACTURER: "OSRAM",
@ -2870,6 +2930,8 @@ DEVICES = [
},
DEV_SIG_ENTITIES: [
"sensor.securifi_ltd_unk_model_77665544_electrical_measurement",
"sensor.securifi_ltd_unk_model_77665544_electrical_measurement_rms_current",
"sensor.securifi_ltd_unk_model_77665544_electrical_measurement_rms_voltage",
"switch.securifi_ltd_unk_model_77665544_on_off",
],
DEV_SIG_ENT_MAP: {
@ -2883,6 +2945,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.securifi_ltd_unk_model_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.securifi_ltd_unk_model_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.securifi_ltd_unk_model_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["1:0x0005", "1:0x0006", "1:0x0019"],
SIG_MANUFACTURER: "Securifi Ltd.",
@ -2948,6 +3020,8 @@ DEVICES = [
DEV_SIG_ENTITIES: [
"light.sercomm_corp_sz_esw01_77665544_on_off",
"sensor.sercomm_corp_sz_esw01_77665544_electrical_measurement",
"sensor.sercomm_corp_sz_esw01_77665544_electrical_measurement_rms_current",
"sensor.sercomm_corp_sz_esw01_77665544_electrical_measurement_rms_voltage",
"sensor.sercomm_corp_sz_esw01_77665544_smartenergy_metering",
"sensor.sercomm_corp_sz_esw01_77665544_smartenergy_metering_summation_delivered",
],
@ -2972,6 +3046,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.sercomm_corp_sz_esw01_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.sercomm_corp_sz_esw01_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.sercomm_corp_sz_esw01_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["1:0x0019", "2:0x0006"],
SIG_MANUFACTURER: "Sercomm Corp.",
@ -3035,6 +3119,8 @@ DEVICES = [
},
DEV_SIG_ENTITIES: [
"sensor.sinope_technologies_rm3250zb_77665544_electrical_measurement",
"sensor.sinope_technologies_rm3250zb_77665544_electrical_measurement_rms_current",
"sensor.sinope_technologies_rm3250zb_77665544_electrical_measurement_rms_voltage",
"switch.sinope_technologies_rm3250zb_77665544_on_off",
],
DEV_SIG_ENT_MAP: {
@ -3048,6 +3134,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_rm3250zb_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_rm3250zb_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_rm3250zb_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["1:0x0019"],
SIG_MANUFACTURER: "Sinope Technologies",
@ -3075,6 +3171,8 @@ DEVICES = [
DEV_SIG_ENTITIES: [
"climate.sinope_technologies_th1123zb_77665544_thermostat",
"sensor.sinope_technologies_th1123zb_77665544_electrical_measurement",
"sensor.sinope_technologies_th1123zb_77665544_electrical_measurement_rms_current",
"sensor.sinope_technologies_th1123zb_77665544_electrical_measurement_rms_voltage",
"sensor.sinope_technologies_th1123zb_77665544_temperature",
],
DEV_SIG_ENT_MAP: {
@ -3093,6 +3191,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_th1123zb_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_th1123zb_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_th1123zb_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["1:0x0019"],
SIG_MANUFACTURER: "Sinope Technologies",
@ -3120,6 +3228,8 @@ DEVICES = [
},
DEV_SIG_ENTITIES: [
"sensor.sinope_technologies_th1124zb_77665544_electrical_measurement",
"sensor.sinope_technologies_th1124zb_77665544_electrical_measurement_rms_current",
"sensor.sinope_technologies_th1124zb_77665544_electrical_measurement_rms_voltage",
"sensor.sinope_technologies_th1124zb_77665544_temperature",
"climate.sinope_technologies_th1124zb_77665544_thermostat",
],
@ -3139,6 +3249,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_th1124zb_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_th1124zb_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.sinope_technologies_th1124zb_77665544_electrical_measurement_rms_voltage",
},
},
DEV_SIG_EVT_CHANNELS: ["1:0x0019"],
SIG_MANUFACTURER: "Sinope Technologies",
@ -3159,6 +3279,8 @@ DEVICES = [
},
DEV_SIG_ENTITIES: [
"sensor.smartthings_outletv4_77665544_electrical_measurement",
"sensor.smartthings_outletv4_77665544_electrical_measurement_rms_current",
"sensor.smartthings_outletv4_77665544_electrical_measurement_rms_voltage",
"switch.smartthings_outletv4_77665544_on_off",
],
DEV_SIG_ENT_MAP: {
@ -3172,6 +3294,16 @@ DEVICES = [
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurement",
DEV_SIG_ENT_MAP_ID: "sensor.smartthings_outletv4_77665544_electrical_measurement",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_current"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSCurrent",
DEV_SIG_ENT_MAP_ID: "sensor.smartthings_outletv4_77665544_electrical_measurement_rms_current",
},
("sensor", "00:11:22:33:44:55:66:77-1-2820-rms_voltage"): {
DEV_SIG_CHANNELS: ["electrical_measurement"],
DEV_SIG_ENT_MAP_CLASS: "ElectricalMeasurementRMSVoltage",
DEV_SIG_ENT_MAP_ID: "sensor.smartthings_outletv4_77665544_electrical_measurement_rms_voltage",
},
("binary_sensor", "00:11:22:33:44:55:66:77-1-15"): {
DEV_SIG_CHANNELS: ["binary_input"],
DEV_SIG_ENT_MAP_CLASS: "BinaryInput",