Bump ZHA to 0.0.57 (#143963)

* Use new (internal) cluster handler IDs in unit tests

* Always add a profile_id to created endpoints

* Use new library decimal formatting

* Implement the ENUM device class for sensors

* Use the suggested display precision hint

* Revert "Implement the ENUM device class for sensors"

This reverts commit d11ab268121b7ffe67c81e45fdc46004fb57a22a.

* Bump ZHA to 0.0.57

* Add strings for v2 quirk entities

* Use ZHA library diagnostics

* Update snapshot

* Revert ZHA change that reports a cover state of `open` if either lift or tilt axes are `open`

This is an interim change to address issues with some cover 'relay' type devices which falsely report support for both lift and tilt. In reality these only support one axes or the other, with users using yaml overrides to restrict functionality in HA.

Devices that genuinely support both movement axes will behave the same as they did prior to https://github.com/zigpy/zha/pull/376
https://github.com/home-assistant/core/pull/141447

A subsequent PR will be made to allow users to override the covering type in a way that allows the entity handler to be aware of the configuration, calculating the state accordingly.

* Spelling mistake

---------

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
Co-authored-by: Jack <46714706+jeverley@users.noreply.github.com>
This commit is contained in:
puddly 2025-04-30 13:43:38 -04:00 committed by GitHub
parent 0f5d5ab0a2
commit 8760a82dfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 248 additions and 188 deletions

View File

@ -6,26 +6,14 @@ import dataclasses
from importlib.metadata import version
from typing import Any
from zha.application.const import (
ATTR_ATTRIBUTE,
ATTR_DEVICE_TYPE,
ATTR_IEEE,
ATTR_IN_CLUSTERS,
ATTR_OUT_CLUSTERS,
ATTR_PROFILE_ID,
ATTR_VALUE,
UNKNOWN,
)
from zha.application.const import ATTR_IEEE
from zha.application.gateway import Gateway
from zha.zigbee.device import Device
from zigpy.config import CONF_NWK_EXTENDED_PAN_ID
from zigpy.profiles import PROFILES
from zigpy.types import Channels
from zigpy.zcl import Cluster
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ID, CONF_NAME, CONF_UNIQUE_ID
from homeassistant.const import CONF_UNIQUE_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
@ -44,6 +32,7 @@ KEYS_TO_REDACT = {
"network_key",
CONF_NWK_EXTENDED_PAN_ID,
"partner_ieee",
"device_ieee",
}
ATTRIBUTES = "attributes"
@ -122,60 +111,5 @@ async def async_get_device_diagnostics(
) -> dict[str, Any]:
"""Return diagnostics for a device."""
zha_device_proxy: ZHADeviceProxy = async_get_zha_device_proxy(hass, device.id)
device_info: dict[str, Any] = zha_device_proxy.zha_device_info
device_info[CLUSTER_DETAILS] = get_endpoint_cluster_attr_data(
zha_device_proxy.device
)
return async_redact_data(device_info, KEYS_TO_REDACT)
def get_endpoint_cluster_attr_data(zha_device: Device) -> dict:
"""Return endpoint cluster attribute data."""
cluster_details = {}
for ep_id, endpoint in zha_device.device.endpoints.items():
if ep_id == 0:
continue
endpoint_key = (
f"{PROFILES.get(endpoint.profile_id).DeviceType(endpoint.device_type).name}"
if PROFILES.get(endpoint.profile_id) is not None
and endpoint.device_type is not None
else UNKNOWN
)
cluster_details[ep_id] = {
ATTR_DEVICE_TYPE: {
CONF_NAME: endpoint_key,
CONF_ID: endpoint.device_type,
},
ATTR_PROFILE_ID: endpoint.profile_id,
ATTR_IN_CLUSTERS: {
f"0x{cluster_id:04x}": {
"endpoint_attribute": cluster.ep_attribute,
**get_cluster_attr_data(cluster),
}
for cluster_id, cluster in endpoint.in_clusters.items()
},
ATTR_OUT_CLUSTERS: {
f"0x{cluster_id:04x}": {
"endpoint_attribute": cluster.ep_attribute,
**get_cluster_attr_data(cluster),
}
for cluster_id, cluster in endpoint.out_clusters.items()
},
}
return cluster_details
def get_cluster_attr_data(cluster: Cluster) -> dict:
"""Return cluster attribute data."""
return {
ATTRIBUTES: {
f"0x{attr_id:04x}": {
ATTR_ATTRIBUTE: repr(attr_def),
ATTR_VALUE: cluster.get(attr_def.name),
}
for attr_id, attr_def in cluster.attributes.items()
},
UNSUPPORTED_ATTRIBUTES: sorted(
cluster.unsupported_attributes, key=lambda v: (isinstance(v, str), v)
),
}
diagnostics_json: dict[str, Any] = zha_device_proxy.device.get_diagnostics_json()
return async_redact_data(diagnostics_json, KEYS_TO_REDACT)

View File

@ -21,7 +21,7 @@
"zha",
"universal_silabs_flasher"
],
"requirements": ["zha==0.0.56"],
"requirements": ["zha==0.0.57"],
"usb": [
{
"vid": "10C4",

View File

@ -138,6 +138,11 @@ class Sensor(ZHAEntity, SensorEntity):
entity_description.device_class.value
)
if entity.info_object.suggested_display_precision is not None:
self._attr_suggested_display_precision = (
entity.info_object.suggested_display_precision
)
@property
def native_value(self) -> StateType:
"""Return the state of the entity."""

View File

@ -1128,6 +1128,15 @@
},
"water_interval": {
"name": "Water interval"
},
"hush_duration": {
"name": "Hush duration"
},
"temperature_control_accuracy": {
"name": "Temperature control accuracy"
},
"external_temperature_sensor_value": {
"name": "External temperature sensor value"
}
},
"select": {
@ -1349,6 +1358,15 @@
},
"speed": {
"name": "Speed"
},
"led_brightness": {
"name": "LED brightness"
},
"alarm_sound_level": {
"name": "Alarm sound level"
},
"alarm_sound_mode": {
"name": "Alarm sound mode"
}
},
"sensor": {
@ -1699,6 +1717,9 @@
},
"device_status": {
"name": "Device status"
},
"lifetime": {
"name": "Lifetime"
}
},
"switch": {
@ -1908,6 +1929,12 @@
},
"auto_clean": {
"name": "Auto clean"
},
"test_mode": {
"name": "Test mode"
},
"external_temperature_sensor": {
"name": "External temperature sensor"
}
}
}

2
requirements_all.txt generated
View File

@ -3162,7 +3162,7 @@ zeroconf==0.146.5
zeversolar==0.3.2
# homeassistant.components.zha
zha==0.0.56
zha==0.0.57
# homeassistant.components.zhong_hong
zhong-hong-hvac==1.0.13

View File

@ -2561,7 +2561,7 @@ zeroconf==0.146.5
zeversolar==0.3.2
# homeassistant.components.zha
zha==0.0.56
zha==0.0.57
# homeassistant.components.zwave_js
zwave-js-server-python==0.63.0

View File

@ -17,6 +17,7 @@ from zigpy.const import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
import zigpy.device
import zigpy.group
import zigpy.profiles
from zigpy.profiles import zha
import zigpy.quirks
import zigpy.state
import zigpy.types
@ -173,6 +174,7 @@ async def zigpy_app_controller():
dev.model = "Coordinator Model"
ep = dev.add_endpoint(1)
ep.profile_id = zha.PROFILE_ID
ep.add_input_cluster(Basic.cluster_id)
ep.add_input_cluster(Groups.cluster_id)

View File

@ -154,31 +154,21 @@
# name: test_diagnostics_for_device
dict({
'active_coordinator': False,
'area_id': None,
'available': True,
'cluster_details': dict({
'device_type': 'EndDevice',
'endpoints': dict({
'1': dict({
'device_type': dict({
'id': 1025,
'name': 'IAS_ANCILLARY_CONTROL',
}),
'in_clusters': dict({
'0x0500': dict({
'attributes': dict({
'0x0000': dict({
'attribute': "ZCLAttributeDef(id=0x0000, name='zone_state', type=<enum 'ZoneState'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
'value': None,
}),
'0x0001': dict({
'attribute': "ZCLAttributeDef(id=0x0001, name='zone_type', type=<enum 'ZoneType'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
'value': None,
}),
'0x0002': dict({
'attribute': "ZCLAttributeDef(id=0x0002, name='zone_status', type=<flag 'ZoneStatus'>, zcl_type=<DataTypeId.map16: 25>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
'value': None,
}),
'0x0010': dict({
'attribute': "ZCLAttributeDef(id=0x0010, name='cie_addr', type=<class 'zigpy.types.named.EUI64'>, zcl_type=<DataTypeId.EUI64: 240>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=True, is_manufacturer_specific=False)",
'in_clusters': list([
dict({
'attributes': list([
dict({
'id': '0x0010',
'name': 'cie_addr',
'unsupported': False,
'value': list([
50,
79,
@ -189,61 +179,82 @@
21,
0,
]),
'zcl_type': 'EUI64',
}),
'0x0011': dict({
'attribute': "ZCLAttributeDef(id=0x0011, name='zone_id', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
dict({
'id': '0x0013',
'name': 'current_zone_sensitivity_level',
'unsupported': False,
'value': None,
'zcl_type': 'uint8',
}),
'0x0012': dict({
'attribute': "ZCLAttributeDef(id=0x0012, name='num_zone_sensitivity_levels_supported', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
dict({
'id': '0x0012',
'name': 'num_zone_sensitivity_levels_supported',
'unsupported': True,
'value': None,
'zcl_type': 'uint8',
}),
'0x0013': dict({
'attribute': "ZCLAttributeDef(id=0x0013, name='current_zone_sensitivity_level', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
dict({
'id': '0x0011',
'name': 'zone_id',
'unsupported': False,
'value': None,
'zcl_type': 'uint8',
}),
}),
dict({
'id': '0x0000',
'name': 'zone_state',
'unsupported': False,
'value': None,
'zcl_type': 'enum8',
}),
dict({
'id': '0x0002',
'name': 'zone_status',
'unsupported': False,
'value': None,
'zcl_type': 'map16',
}),
dict({
'id': '0x0001',
'name': 'zone_type',
'unsupported': False,
'value': None,
'zcl_type': 'uint16',
}),
]),
'cluster_id': '0x0500',
'endpoint_attribute': 'ias_zone',
'unsupported_attributes': list([
18,
'current_zone_sensitivity_level',
]),
}),
'0x0501': dict({
'attributes': dict({
'0xfffd': dict({
'attribute': "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
dict({
'attributes': list([
dict({
'id': '0xfffd',
'name': 'cluster_revision',
'unsupported': False,
'value': None,
'zcl_type': 'uint16',
}),
'0xfffe': dict({
'attribute': "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
dict({
'id': '0xfffe',
'name': 'reporting_status',
'unsupported': False,
'value': None,
'zcl_type': 'enum8',
}),
}),
]),
'cluster_id': '0x0501',
'endpoint_attribute': 'ias_ace',
'unsupported_attributes': list([
4096,
'unknown_attribute_name',
]),
}),
}),
'out_clusters': dict({
}),
]),
'out_clusters': list([
]),
'profile_id': 260,
}),
}),
'device_type': 'EndDevice',
'endpoint_names': list([
dict({
'name': 'IAS_ANCILLARY_CONTROL',
}),
]),
'entities': list([
dict({
'entity_id': 'alarm_control_panel.fakemanufacturer_fakemodel_alarm_control_panel',
'name': 'FakeManufacturer FakeModel',
}),
]),
'friendly_manufacturer': 'FakeManufacturer',
'friendly_model': 'FakeModel',
'ieee': '**REDACTED**',
'lqi': None,
'manufacturer': 'FakeManufacturer',
@ -252,7 +263,22 @@
'name': 'FakeManufacturer FakeModel',
'neighbors': list([
]),
'nwk': 47004,
'node_descriptor': dict({
'aps_flags': 0,
'complex_descriptor_available': False,
'descriptor_capability_field': 0,
'frequency_band': 8,
'logical_type': 'EndDevice',
'mac_capability_flags': 140,
'manufacturer_code': 4098,
'maximum_buffer_size': 82,
'maximum_incoming_transfer_size': 82,
'maximum_outgoing_transfer_size': 82,
'reserved': 0,
'server_mask': 0,
'user_descriptor_available': False,
}),
'nwk': '0xB79C',
'power_source': 'Mains',
'quirk_applied': False,
'quirk_class': 'zigpy.device.Device',
@ -260,37 +286,100 @@
'routes': list([
]),
'rssi': None,
'signature': dict({
'endpoints': dict({
'1': dict({
'device_type': '0x0401',
'input_clusters': list([
'0x0500',
'0x0501',
]),
'output_clusters': list([
]),
'profile_id': '0x0104',
'version': 1,
'zha_lib_entities': dict({
'alarm_control_panel': list([
dict({
'info_object': dict({
'available': True,
'class_name': 'AlarmControlPanel',
'cluster_handlers': list([
dict({
'class_name': 'IasAceClusterHandler',
'cluster': dict({
'id': 1281,
'name': 'IAS Ancillary Control Equipment',
'type': 'server',
}),
'endpoint_id': 1,
'generic_id': 'cluster_handler_0x0501',
'id': '1:0x0501',
'status': 'INITIALIZED',
'unique_id': '**REDACTED**',
'value_attribute': None,
}),
]),
'code_arm_required': False,
'code_format': 'number',
'device_class': None,
'device_ieee': '**REDACTED**',
'enabled': True,
'endpoint_id': 1,
'entity_category': None,
'entity_registry_enabled_default': True,
'fallback_name': None,
'group_id': None,
'migrate_unique_ids': list([
]),
'platform': 'alarm_control_panel',
'primary': False,
'state_class': None,
'supported_features': 15,
'translation_key': 'alarm_control_panel',
'unique_id': '**REDACTED**',
}),
'state': dict({
'available': True,
'class_name': 'AlarmControlPanel',
'state': 'disarmed',
}),
}),
}),
'manufacturer': 'FakeManufacturer',
'model': 'FakeModel',
'node_descriptor': dict({
'aps_flags': 0,
'complex_descriptor_available': 0,
'descriptor_capability_field': 0,
'frequency_band': 8,
'logical_type': 2,
'mac_capability_flags': 140,
'manufacturer_code': 4098,
'maximum_buffer_size': 82,
'maximum_incoming_transfer_size': 82,
'maximum_outgoing_transfer_size': 82,
'reserved': 0,
'server_mask': 0,
'user_descriptor_available': 0,
}),
]),
'binary_sensor': list([
dict({
'info_object': dict({
'attribute_name': 'zone_status',
'available': True,
'class_name': 'IASZone',
'cluster_handlers': list([
dict({
'class_name': 'IASZoneClusterHandler',
'cluster': dict({
'id': 1280,
'name': 'IAS Zone',
'type': 'server',
}),
'endpoint_id': 1,
'generic_id': 'cluster_handler_0x0500',
'id': '1:0x0500',
'status': 'INITIALIZED',
'unique_id': '**REDACTED**',
'value_attribute': None,
}),
]),
'device_class': None,
'device_ieee': '**REDACTED**',
'enabled': True,
'endpoint_id': 1,
'entity_category': None,
'entity_registry_enabled_default': True,
'fallback_name': None,
'group_id': None,
'migrate_unique_ids': list([
]),
'platform': 'binary_sensor',
'primary': True,
'state_class': None,
'translation_key': 'ias_zone',
'unique_id': '**REDACTED**',
}),
'state': dict({
'available': True,
'class_name': 'IASZone',
'state': False,
}),
}),
]),
}),
'user_given_name': None,
})
# ---

View File

@ -80,8 +80,8 @@ async def test_cover(hass: HomeAssistant, setup_zha, zigpy_device_mock) -> None:
# load up cover domain
cluster = zigpy_device.endpoints[1].window_covering
cluster.PLUGGED_ATTR_READS = {
WCAttrs.current_position_lift_percentage.name: 0,
WCAttrs.current_position_tilt_percentage.name: 100,
WCAttrs.current_position_lift_percentage.name: 0, # Zigbee open %
WCAttrs.current_position_tilt_percentage.name: 100, # Zigbee closed %
WCAttrs.window_covering_type.name: WCT.Tilt_blind_tilt_and_lift,
WCAttrs.config_status.name: WCCS(~WCCS.Open_up_commands_reversed),
}
@ -114,8 +114,8 @@ async def test_cover(hass: HomeAssistant, setup_zha, zigpy_device_mock) -> None:
state = hass.states.get(entity_id)
assert state
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 100
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 0
assert state.attributes[ATTR_CURRENT_POSITION] == 100 # HA open %
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 0 # HA closed %
# test that the state has changed from open to closed
await send_attributes_report(
@ -164,7 +164,9 @@ async def test_cover(hass: HomeAssistant, setup_zha, zigpy_device_mock) -> None:
await send_attributes_report(
hass, cluster, {WCAttrs.current_position_tilt_percentage.id: 0}
)
assert hass.states.get(entity_id).state == CoverState.OPEN
assert (
hass.states.get(entity_id).state == CoverState.CLOSED
) # CLOSED lift state currently takes precedence over OPEN tilt
with patch("zigpy.zcl.Cluster.request", return_value=[0x1, zcl_f.Status.SUCCESS]):
await hass.services.async_call(
COVER_DOMAIN,

View File

@ -209,7 +209,7 @@ async def test_action(
cluster_handler = (
gateway.get_device(zigpy_device.ieee)
.endpoints[1]
.client_cluster_handlers["1:0x0006"]
.client_cluster_handlers["1:0x0006_client"]
)
cluster_handler.zha_send_event(COMMAND_SINGLE, [])
await hass.async_block_till_done()
@ -252,7 +252,7 @@ async def test_invalid_zha_event_type(
cluster_handler = (
gateway.get_device(zigpy_device.ieee)
.endpoints[1]
.client_cluster_handlers["1:0x0006"]
.client_cluster_handlers["1:0x0006_client"]
)
# `zha_send_event` accepts only zigpy responses, lists, and dicts

View File

@ -199,6 +199,7 @@ async def test_if_fires_on_event(
)
ep = zigpy_device.add_endpoint(1)
ep.add_output_cluster(0x0006)
ep.profile_id = zigpy.profiles.zha.PROFILE_ID
zigpy_device.device_automation_triggers = {
(SHAKEN, SHAKEN): {COMMAND: COMMAND_SHAKE},

View File

@ -62,10 +62,10 @@ async def async_test_temperature(hass: HomeAssistant, cluster: Cluster, entity_i
async def async_test_pressure(hass: HomeAssistant, cluster: Cluster, entity_id: str):
"""Test pressure sensor."""
await send_attributes_report(hass, cluster, {1: 1, 0: 1000, 2: 10000})
assert_state(hass, entity_id, "1000", UnitOfPressure.HPA)
assert_state(hass, entity_id, "1000.0", UnitOfPressure.HPA)
await send_attributes_report(hass, cluster, {0: 1000, 20: -1, 16: 10000})
assert_state(hass, entity_id, "1000", UnitOfPressure.HPA)
assert_state(hass, entity_id, "1000.0", UnitOfPressure.HPA)
async def async_test_illuminance(hass: HomeAssistant, cluster: Cluster, entity_id: str):
@ -167,14 +167,14 @@ async def async_test_electrical_measurement(
# 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", UnitOfPower.WATT)
assert_state(hass, entity_id, "100.0", UnitOfPower.WATT)
await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 1000})
assert_state(hass, entity_id, "99", UnitOfPower.WATT)
assert_state(hass, entity_id, "99.0", UnitOfPower.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", UnitOfPower.WATT)
assert_state(hass, entity_id, "100.0", UnitOfPower.WATT)
await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 5000})
assert_state(hass, entity_id, "9.9", UnitOfPower.WATT)
@ -191,14 +191,14 @@ async def async_test_em_apparent_power(
# update divisor cached value
await send_attributes_report(hass, cluster, {"ac_power_divisor": 1})
await send_attributes_report(hass, cluster, {0: 1, 0x050F: 100, 10: 1000})
assert_state(hass, entity_id, "100", UnitOfApparentPower.VOLT_AMPERE)
assert_state(hass, entity_id, "100.0", UnitOfApparentPower.VOLT_AMPERE)
await send_attributes_report(hass, cluster, {0: 1, 0x050F: 99, 10: 1000})
assert_state(hass, entity_id, "99", UnitOfApparentPower.VOLT_AMPERE)
assert_state(hass, entity_id, "99.0", UnitOfApparentPower.VOLT_AMPERE)
await send_attributes_report(hass, cluster, {"ac_power_divisor": 10})
await send_attributes_report(hass, cluster, {0: 1, 0x050F: 1000, 10: 5000})
assert_state(hass, entity_id, "100", UnitOfApparentPower.VOLT_AMPERE)
assert_state(hass, entity_id, "100.0", UnitOfApparentPower.VOLT_AMPERE)
await send_attributes_report(hass, cluster, {0: 1, 0x050F: 99, 10: 5000})
assert_state(hass, entity_id, "9.9", UnitOfApparentPower.VOLT_AMPERE)
@ -211,17 +211,17 @@ async def async_test_em_power_factor(
# update divisor cached value
await send_attributes_report(hass, cluster, {"ac_power_divisor": 1})
await send_attributes_report(hass, cluster, {0: 1, 0x0510: 100, 10: 1000})
assert_state(hass, entity_id, "100", PERCENTAGE)
assert_state(hass, entity_id, "100.0", PERCENTAGE)
await send_attributes_report(hass, cluster, {0: 1, 0x0510: 99, 10: 1000})
assert_state(hass, entity_id, "99", PERCENTAGE)
assert_state(hass, entity_id, "99.0", PERCENTAGE)
await send_attributes_report(hass, cluster, {"ac_power_divisor": 10})
await send_attributes_report(hass, cluster, {0: 1, 0x0510: 100, 10: 5000})
assert_state(hass, entity_id, "100", PERCENTAGE)
assert_state(hass, entity_id, "100.0", PERCENTAGE)
await send_attributes_report(hass, cluster, {0: 1, 0x0510: 99, 10: 5000})
assert_state(hass, entity_id, "99", PERCENTAGE)
assert_state(hass, entity_id, "99.0", PERCENTAGE)
async def async_test_em_rms_current(
@ -230,14 +230,14 @@ async def async_test_em_rms_current(
"""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", UnitOfElectricCurrent.AMPERE)
assert_state(hass, entity_id, "1.234", UnitOfElectricCurrent.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", UnitOfElectricCurrent.AMPERE)
await send_attributes_report(hass, cluster, {0: 1, 0x0508: 1236, 10: 1000})
assert_state(hass, entity_id, "124", UnitOfElectricCurrent.AMPERE)
assert_state(hass, entity_id, "123.6", UnitOfElectricCurrent.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})
@ -250,18 +250,18 @@ async def async_test_em_rms_voltage(
"""Test electrical measurement RMS Voltage sensor."""
await send_attributes_report(hass, cluster, {0: 1, 0x0505: 1234, 10: 1000})
assert_state(hass, entity_id, "123", UnitOfElectricPotential.VOLT)
assert_state(hass, entity_id, "123.4", UnitOfElectricPotential.VOLT)
await send_attributes_report(hass, cluster, {0: 1, 0x0505: 234, 10: 1000})
assert_state(hass, entity_id, "23.4", UnitOfElectricPotential.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", UnitOfElectricPotential.VOLT)
assert_state(hass, entity_id, "22.36", UnitOfElectricPotential.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
assert hass.states.get(entity_id).attributes["rms_voltage_max"] == 8.88
async def async_test_powerconfiguration(
@ -269,7 +269,7 @@ async def async_test_powerconfiguration(
):
"""Test powerconfiguration/battery sensor."""
await send_attributes_report(hass, cluster, {33: 98})
assert_state(hass, entity_id, "49", "%")
assert_state(hass, entity_id, "49.0", "%")
assert hass.states.get(entity_id).attributes["battery_voltage"] == 2.9
assert hass.states.get(entity_id).attributes["battery_quantity"] == 3
assert hass.states.get(entity_id).attributes["battery_size"] == "AAA"
@ -288,7 +288,7 @@ async def async_test_powerconfiguration2(
assert_state(hass, entity_id, STATE_UNKNOWN, "%")
await send_attributes_report(hass, cluster, {33: 98})
assert_state(hass, entity_id, "49", "%")
assert_state(hass, entity_id, "49.0", "%")
async def async_test_device_temperature(
@ -317,7 +317,7 @@ async def async_test_pi_heating_demand(
await send_attributes_report(
hass, cluster, {Thermostat.AttributeDefs.pi_heating_demand.id: 1}
)
assert_state(hass, entity_id, "1", "%")
assert_state(hass, entity_id, "1.0", "%")
@pytest.mark.parametrize(