Skip to content

Commit

Permalink
Matter cover position improvements (#92278)
Browse files Browse the repository at this point in the history
  • Loading branch information
Diegorro98 committed May 31, 2023
1 parent c8c3683 commit 3cf8ae6
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 55 deletions.
102 changes: 60 additions & 42 deletions homeassistant/components/matter/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -126,37 +136,45 @@ def _update_from_device(self) -> None:
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
)

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
)

LOGGER.debug(
"Current tilt position for %s - raw: %s - corrected: %s",
self.entity_id,
current_cover_tilt_position,
self.current_cover_tilt_position,
)
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,
)

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,
)

# map matter type to HA deviceclass
device_type: clusters.WindowCovering.Enums.Type = (
Expand Down Expand Up @@ -188,8 +206,8 @@ def _update_from_device(self) -> None:
clusters.WindowCovering.Attributes.Type,
),
absent_attributes=(
clusters.WindowCovering.Attributes.CurrentPositionLiftPercentage,
clusters.WindowCovering.Attributes.CurrentPositionTiltPercentage,
clusters.WindowCovering.Attributes.CurrentPositionLiftPercent100ths,
clusters.WindowCovering.Attributes.CurrentPositionTiltPercent100ths,
),
),
MatterDiscoverySchema(
Expand All @@ -199,10 +217,10 @@ def _update_from_device(self) -> None:
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(
Expand All @@ -212,10 +230,10 @@ def _update_from_device(self) -> None:
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(
Expand All @@ -227,8 +245,8 @@ def _update_from_device(self) -> None:
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,
),
),
]
86 changes: 73 additions & 13 deletions tests/components/matter/test_cover.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Test Matter covers."""
from math import floor
from unittest.mock import MagicMock, call

from chip.clusters import Objects as clusters
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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(
Expand All @@ -411,29 +422,78 @@ 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)

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, 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 == STATE_OPEN

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, 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, 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, 8, 100)
set_node_attribute(window_covering, 1, 258, 9, 50)
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"

0 comments on commit 3cf8ae6

Please sign in to comment.