diff --git a/homeassistant/components/matter/cover.py b/homeassistant/components/matter/cover.py index 9e72b69f335..61c5d4cd2ff 100644 --- a/homeassistant/components/matter/cover.py +++ b/homeassistant/components/matter/cover.py @@ -2,6 +2,7 @@ from __future__ import annotations from enum import IntEnum +from math import floor from typing import Any from chip.clusters import Objects as clusters @@ -59,9 +60,18 @@ class MatterCover(MatterEntity, CoverEntity): entity_description: CoverEntityDescription @property - def is_closed(self) -> bool: - """Return true if cover is closed, else False.""" - return self.current_cover_position == 0 + def is_closed(self) -> bool | None: + """Return true if cover is closed, if there is no position report, return None.""" + if not self._entity_info.endpoint.has_attribute( + None, clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths + ): + return None + + return ( + self.current_cover_position == 0 + if self.current_cover_position is not None + else None + ) async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover movement.""" @@ -126,37 +136,45 @@ class MatterCover(MatterEntity, CoverEntity): self._attr_is_opening = False self._attr_is_closing = False - # current position is inverted in matter (100 is closed, 0 is open) - current_cover_position = self.get_matter_attribute_value( - clusters.WindowCovering.Attributes.CurrentPositionLiftPercentage - ) - self._attr_current_cover_position = ( - 100 - current_cover_position if current_cover_position is not None else None - ) + if self._entity_info.endpoint.has_attribute( + None, clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths + ): + # current position is inverted in matter (100 is closed, 0 is open) + current_cover_position = self.get_matter_attribute_value( + clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths + ) + self._attr_current_cover_position = ( + 100 - floor(current_cover_position / 100) + if current_cover_position is not None + else None + ) - LOGGER.debug( - "Current position for %s - raw: %s - corrected: %s", - self.entity_id, - current_cover_position, - self.current_cover_position, - ) + LOGGER.debug( + "Current position for %s - raw: %s - corrected: %s", + self.entity_id, + current_cover_position, + self.current_cover_position, + ) - # current tilt position is inverted in matter (100 is closed, 0 is open) - current_cover_tilt_position = self.get_matter_attribute_value( - clusters.WindowCovering.Attributes.CurrentPositionTiltPercentage - ) - self._attr_current_cover_tilt_position = ( - 100 - current_cover_tilt_position - if current_cover_tilt_position is not None - else None - ) + if self._entity_info.endpoint.has_attribute( + None, clusters.WindowCovering.Attributes.CurrentPositionTiltPercent100ths + ): + # current tilt position is inverted in matter (100 is closed, 0 is open) + current_cover_tilt_position = self.get_matter_attribute_value( + clusters.WindowCovering.Attributes.CurrentPositionTiltPercent100ths + ) + self._attr_current_cover_tilt_position = ( + 100 - floor(current_cover_tilt_position / 100) + if current_cover_tilt_position is not None + else None + ) - LOGGER.debug( - "Current tilt position for %s - raw: %s - corrected: %s", - self.entity_id, - current_cover_tilt_position, - self.current_cover_tilt_position, - ) + LOGGER.debug( + "Current tilt position for %s - raw: %s - corrected: %s", + self.entity_id, + current_cover_tilt_position, + self.current_cover_tilt_position, + ) # map matter type to HA deviceclass device_type: clusters.WindowCovering.Enums.Type = ( @@ -188,8 +206,8 @@ DISCOVERY_SCHEMAS = [ clusters.WindowCovering.Attributes.Type, ), absent_attributes=( - clusters.WindowCovering.Attributes.CurrentPositionLiftPercentage, - clusters.WindowCovering.Attributes.CurrentPositionTiltPercentage, + clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths, + clusters.WindowCovering.Attributes.CurrentPositionTiltPercent100ths, ), ), MatterDiscoverySchema( @@ -199,10 +217,10 @@ DISCOVERY_SCHEMAS = [ required_attributes=( clusters.WindowCovering.Attributes.OperationalStatus, clusters.WindowCovering.Attributes.Type, - clusters.WindowCovering.Attributes.CurrentPositionLiftPercentage, + clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths, ), absent_attributes=( - clusters.WindowCovering.Attributes.CurrentPositionTiltPercentage, + clusters.WindowCovering.Attributes.CurrentPositionTiltPercent100ths, ), ), MatterDiscoverySchema( @@ -212,10 +230,10 @@ DISCOVERY_SCHEMAS = [ required_attributes=( clusters.WindowCovering.Attributes.OperationalStatus, clusters.WindowCovering.Attributes.Type, - clusters.WindowCovering.Attributes.CurrentPositionTiltPercentage, + clusters.WindowCovering.Attributes.CurrentPositionTiltPercent100ths, ), absent_attributes=( - clusters.WindowCovering.Attributes.CurrentPositionLiftPercentage, + clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths, ), ), MatterDiscoverySchema( @@ -227,8 +245,8 @@ DISCOVERY_SCHEMAS = [ required_attributes=( clusters.WindowCovering.Attributes.OperationalStatus, clusters.WindowCovering.Attributes.Type, - clusters.WindowCovering.Attributes.CurrentPositionLiftPercentage, - clusters.WindowCovering.Attributes.CurrentPositionTiltPercentage, + clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths, + clusters.WindowCovering.Attributes.CurrentPositionTiltPercent100ths, ), ), ] diff --git a/tests/components/matter/test_cover.py b/tests/components/matter/test_cover.py index bbbd0751e4a..d409983307f 100644 --- a/tests/components/matter/test_cover.py +++ b/tests/components/matter/test_cover.py @@ -1,4 +1,5 @@ """Test Matter covers.""" +from math import floor from unittest.mock import MagicMock, call from chip.clusters import Objects as clusters @@ -177,6 +178,14 @@ async def test_cover_lift_only( matter_client, ) + set_node_attribute(window_covering, 1, 258, 14, None) + set_node_attribute(window_covering, 1, 258, 10, 0b000000) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get(entity_id) + assert state + assert state.state == "unknown" + set_node_attribute(window_covering, 1, 258, 65529, [0, 1, 2]) await trigger_subscription_callback(hass, matter_client) @@ -224,17 +233,17 @@ async def test_cover_position_aware_lift( ) assert state.attributes["supported_features"] & mask == mask - for position in (0, 99): - set_node_attribute(window_covering, 1, 258, 8, position) + for position in (0, 9999): + set_node_attribute(window_covering, 1, 258, 14, position) set_node_attribute(window_covering, 1, 258, 10, 0b000000) await trigger_subscription_callback(hass, matter_client) state = hass.states.get(entity_id) assert state - assert state.attributes["current_position"] == 100 - position + assert state.attributes["current_position"] == 100 - floor(position / 100) assert state.state == STATE_OPEN - set_node_attribute(window_covering, 1, 258, 8, 100) + set_node_attribute(window_covering, 1, 258, 14, 10000) set_node_attribute(window_covering, 1, 258, 10, 0b000000) await trigger_subscription_callback(hass, matter_client) @@ -377,14 +386,16 @@ async def test_cover_position_aware_tilt( ) assert state.attributes["supported_features"] & mask == mask - for tilt_position in (0, 99, 100): - set_node_attribute(window_covering, 1, 258, 9, tilt_position) + for tilt_position in (0, 9999, 10000): + set_node_attribute(window_covering, 1, 258, 15, tilt_position) set_node_attribute(window_covering, 1, 258, 10, 0b000000) await trigger_subscription_callback(hass, matter_client) state = hass.states.get(entity_id) assert state - assert state.attributes["current_tilt_position"] == 100 - tilt_position + assert state.attributes["current_tilt_position"] == 100 - floor( + tilt_position / 100 + ) async def test_cover_full_features( @@ -411,8 +422,8 @@ async def test_cover_full_features( ) assert state.attributes["supported_features"] & mask == mask - set_node_attribute(window_covering, 1, 258, 8, 100) - set_node_attribute(window_covering, 1, 258, 9, 100) + set_node_attribute(window_covering, 1, 258, 14, 10000) + set_node_attribute(window_covering, 1, 258, 15, 10000) set_node_attribute(window_covering, 1, 258, 10, 0b000000) await trigger_subscription_callback(hass, matter_client) @@ -420,8 +431,8 @@ async def test_cover_full_features( assert state assert state.state == STATE_CLOSED - set_node_attribute(window_covering, 1, 258, 8, 50) - set_node_attribute(window_covering, 1, 258, 9, 100) + set_node_attribute(window_covering, 1, 258, 14, 5000) + set_node_attribute(window_covering, 1, 258, 15, 10000) set_node_attribute(window_covering, 1, 258, 10, 0b000000) await trigger_subscription_callback(hass, matter_client) @@ -429,11 +440,60 @@ async def test_cover_full_features( assert state assert state.state == STATE_OPEN - set_node_attribute(window_covering, 1, 258, 8, 100) - set_node_attribute(window_covering, 1, 258, 9, 50) + set_node_attribute(window_covering, 1, 258, 14, 10000) + set_node_attribute(window_covering, 1, 258, 15, 5000) set_node_attribute(window_covering, 1, 258, 10, 0b000000) await trigger_subscription_callback(hass, matter_client) state = hass.states.get(entity_id) assert state assert state.state == STATE_CLOSED + + set_node_attribute(window_covering, 1, 258, 14, 5000) + set_node_attribute(window_covering, 1, 258, 15, 5000) + set_node_attribute(window_covering, 1, 258, 10, 0b000000) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OPEN + + set_node_attribute(window_covering, 1, 258, 14, 5000) + set_node_attribute(window_covering, 1, 258, 15, None) + set_node_attribute(window_covering, 1, 258, 10, 0b000000) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OPEN + + set_node_attribute(window_covering, 1, 258, 14, None) + set_node_attribute(window_covering, 1, 258, 15, 5000) + set_node_attribute(window_covering, 1, 258, 10, 0b000000) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state + assert state.state == "unknown" + + set_node_attribute(window_covering, 1, 258, 14, 10000) + set_node_attribute(window_covering, 1, 258, 15, None) + set_node_attribute(window_covering, 1, 258, 10, 0b000000) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_CLOSED + + set_node_attribute(window_covering, 1, 258, 14, None) + set_node_attribute(window_covering, 1, 258, 15, 10000) + set_node_attribute(window_covering, 1, 258, 10, 0b000000) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state + assert state.state == "unknown" + + set_node_attribute(window_covering, 1, 258, 14, None) + set_node_attribute(window_covering, 1, 258, 15, None) + set_node_attribute(window_covering, 1, 258, 10, 0b000000) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state + assert state.state == "unknown"