Mqtt cover toggle add stop function (#59233)

* Change existing toggle to add new function

* Fixed using old property method to using actual protected variable.

* Adding service tests to new cover toggle function

* Working on comments from Pull Request 59233

* Adjust existing tests to fit new fake cover setup

* MockCover is calling state method of MockEntity but should call it from CoverEntity

* using different entity to get back test coverage
This commit is contained in:
Oliver Gruß 2021-11-10 09:03:20 +01:00 committed by GitHub
parent 01fe69511f
commit eec84ad71e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 224 additions and 42 deletions

View File

@ -187,6 +187,8 @@ class CoverEntity(Entity):
_attr_is_opening: bool | None = None
_attr_state: None = None
_cover_is_last_toggle_direction_open = True
@property
def current_cover_position(self) -> int | None:
"""Return current position of cover.
@ -208,8 +210,10 @@ class CoverEntity(Entity):
def state(self) -> str | None:
"""Return the state of the cover."""
if self.is_opening:
self._cover_is_last_toggle_direction_open = True
return STATE_OPENING
if self.is_closing:
self._cover_is_last_toggle_direction_open = False
return STATE_CLOSING
if (closed := self.is_closed) is None:
@ -285,17 +289,23 @@ class CoverEntity(Entity):
def toggle(self, **kwargs: Any) -> None:
"""Toggle the entity."""
if self.is_closed:
self.open_cover(**kwargs)
else:
self.close_cover(**kwargs)
fns = {
"open": self.open_cover,
"close": self.close_cover,
"stop": self.stop_cover,
}
function = self._get_toggle_function(fns)
function(**kwargs)
async def async_toggle(self, **kwargs):
"""Toggle the entity."""
if self.is_closed:
await self.async_open_cover(**kwargs)
else:
await self.async_close_cover(**kwargs)
fns = {
"open": self.async_open_cover,
"close": self.async_close_cover,
"stop": self.async_stop_cover,
}
function = self._get_toggle_function(fns)
await function(**kwargs)
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
@ -363,6 +373,17 @@ class CoverEntity(Entity):
else:
await self.async_close_cover_tilt(**kwargs)
def _get_toggle_function(self, fns):
if SUPPORT_STOP | self.supported_features and (
self.is_closing or self.is_opening
):
return fns["stop"]
if self.is_closed:
return fns["open"]
if self._cover_is_last_toggle_direction_open:
return fns["close"]
return fns["open"]
class CoverDevice(CoverEntity):
"""Representation of a cover (for backwards compatibility)."""

View File

@ -110,7 +110,7 @@ async def test_get_action_capabilities(
"""Test we get the expected capabilities from a cover action."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
ent = platform.ENTITIES[0]
ent = platform.ENTITIES[2]
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
@ -126,7 +126,7 @@ async def test_get_action_capabilities(
await hass.async_block_till_done()
actions = await async_get_device_automations(hass, "action", device_entry.id)
assert len(actions) == 3 # open, close, stop
assert len(actions) == 4 # open, close, stop, set_position
for action in actions:
capabilities = await async_get_device_automation_capabilities(
hass, "action", action

View File

@ -196,7 +196,7 @@ async def test_get_condition_capabilities_set_tilt_pos(
"""Test we get the expected capabilities from a cover condition."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
ent = platform.ENTITIES[2]
ent = platform.ENTITIES[3]
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
@ -487,7 +487,7 @@ async def test_if_tilt_position(hass, calls, caplog, enable_custom_integrations)
"""Test for tilt position conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
ent = platform.ENTITIES[2]
ent = platform.ENTITIES[3]
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
await hass.async_block_till_done()

View File

@ -228,7 +228,7 @@ async def test_get_trigger_capabilities_set_tilt_pos(
"""Test we get the expected capabilities from a cover trigger."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
ent = platform.ENTITIES[2]
ent = platform.ENTITIES[3]
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)

View File

@ -1,5 +1,116 @@
"""The tests for Cover."""
import homeassistant.components.cover as cover
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_PLATFORM,
SERVICE_TOGGLE,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
)
from homeassistant.setup import async_setup_component
async def test_services(hass, enable_custom_integrations):
"""Test the provided services."""
platform = getattr(hass.components, "test.cover")
platform.init()
assert await async_setup_component(
hass, cover.DOMAIN, {cover.DOMAIN: {CONF_PLATFORM: "test"}}
)
await hass.async_block_till_done()
# ent1 = cover without tilt and position
# ent2 = cover with position but no tilt
# ent3 = cover with simple tilt functions and no position
# ent4 = cover with all tilt functions but no position
# ent5 = cover with all functions
ent1, ent2, ent3, ent4, ent5 = platform.ENTITIES
# Test init all covers should be open
assert is_open(hass, ent1)
assert is_open(hass, ent2)
assert is_open(hass, ent3)
assert is_open(hass, ent4)
assert is_open(hass, ent5)
# call basic toggle services
await call_service(hass, SERVICE_TOGGLE, ent1)
await call_service(hass, SERVICE_TOGGLE, ent2)
await call_service(hass, SERVICE_TOGGLE, ent3)
await call_service(hass, SERVICE_TOGGLE, ent4)
await call_service(hass, SERVICE_TOGGLE, ent5)
# entities without stop should be closed and with stop should be closing
assert is_closed(hass, ent1)
assert is_closing(hass, ent2)
assert is_closed(hass, ent3)
assert is_closed(hass, ent4)
assert is_closing(hass, ent5)
# call basic toggle services and set different cover position states
await call_service(hass, SERVICE_TOGGLE, ent1)
set_cover_position(ent2, 0)
await call_service(hass, SERVICE_TOGGLE, ent2)
await call_service(hass, SERVICE_TOGGLE, ent3)
await call_service(hass, SERVICE_TOGGLE, ent4)
set_cover_position(ent5, 15)
await call_service(hass, SERVICE_TOGGLE, ent5)
# entities should be in correct state depending on the SUPPORT_STOP feature and cover position
assert is_open(hass, ent1)
assert is_closed(hass, ent2)
assert is_open(hass, ent3)
assert is_open(hass, ent4)
assert is_open(hass, ent5)
# call basic toggle services
await call_service(hass, SERVICE_TOGGLE, ent1)
await call_service(hass, SERVICE_TOGGLE, ent2)
await call_service(hass, SERVICE_TOGGLE, ent3)
await call_service(hass, SERVICE_TOGGLE, ent4)
await call_service(hass, SERVICE_TOGGLE, ent5)
# entities should be in correct state depending on the SUPPORT_STOP feature and cover position
assert is_closed(hass, ent1)
assert is_opening(hass, ent2)
assert is_closed(hass, ent3)
assert is_closed(hass, ent4)
assert is_opening(hass, ent5)
def call_service(hass, service, ent):
"""Call any service on entity."""
return hass.services.async_call(
cover.DOMAIN, service, {ATTR_ENTITY_ID: ent.entity_id}, blocking=True
)
def set_cover_position(ent, position) -> None:
"""Set a position value to a cover."""
ent._values["current_cover_position"] = position
def is_open(hass, ent):
"""Return if the cover is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, STATE_OPEN)
def is_opening(hass, ent):
"""Return if the cover is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, STATE_OPENING)
def is_closed(hass, ent):
"""Return if the cover is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, STATE_CLOSED)
def is_closing(hass, ent):
"""Return if the cover is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, STATE_CLOSING)
def test_deprecated_base_class(caplog):

View File

@ -14,6 +14,7 @@ from homeassistant.components.cover import (
SUPPORT_STOP_TILT,
CoverEntity,
)
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING
from tests.common import MockEntity
@ -32,27 +33,53 @@ def init(empty=False):
name="Simple cover",
is_on=True,
unique_id="unique_cover",
supports_tilt=False,
supported_features=SUPPORT_OPEN | SUPPORT_CLOSE,
),
MockCover(
name="Set position cover",
is_on=True,
unique_id="unique_set_pos_cover",
current_cover_position=50,
supports_tilt=False,
supported_features=SUPPORT_OPEN
| SUPPORT_CLOSE
| SUPPORT_STOP
| SUPPORT_SET_POSITION,
),
MockCover(
name="Simple tilt cover",
is_on=True,
unique_id="unique_tilt_cover",
supported_features=SUPPORT_OPEN
| SUPPORT_CLOSE
| SUPPORT_OPEN_TILT
| SUPPORT_CLOSE_TILT,
),
MockCover(
name="Set tilt position cover",
is_on=True,
unique_id="unique_set_pos_tilt_cover",
current_cover_tilt_position=50,
supports_tilt=True,
supported_features=SUPPORT_OPEN
| SUPPORT_CLOSE
| SUPPORT_OPEN_TILT
| SUPPORT_CLOSE_TILT
| SUPPORT_STOP_TILT
| SUPPORT_SET_TILT_POSITION,
),
MockCover(
name="Tilt cover",
name="All functions cover",
is_on=True,
unique_id="unique_tilt_cover",
supports_tilt=True,
unique_id="unique_all_functions_cover",
current_cover_position=50,
current_cover_tilt_position=50,
supported_features=SUPPORT_OPEN
| SUPPORT_CLOSE
| SUPPORT_STOP
| SUPPORT_SET_POSITION
| SUPPORT_OPEN_TILT
| SUPPORT_CLOSE_TILT
| SUPPORT_STOP_TILT
| SUPPORT_SET_TILT_POSITION,
),
]
)
@ -71,8 +98,54 @@ class MockCover(MockEntity, CoverEntity):
@property
def is_closed(self):
"""Return if the cover is closed or not."""
if self.supported_features & SUPPORT_STOP:
return self.current_cover_position == 0
if "state" in self._values:
return self._values["state"] == STATE_CLOSED
return False
@property
def is_opening(self):
"""Return if the cover is opening or not."""
if self.supported_features & SUPPORT_STOP:
if "state" in self._values:
return self._values["state"] == STATE_OPENING
return False
@property
def is_closing(self):
"""Return if the cover is closing or not."""
if self.supported_features & SUPPORT_STOP:
if "state" in self._values:
return self._values["state"] == STATE_CLOSING
return False
def open_cover(self, **kwargs) -> None:
"""Open cover."""
if self.supported_features & SUPPORT_STOP:
self._values["state"] = STATE_OPENING
else:
self._values["state"] = STATE_OPEN
def close_cover(self, **kwargs) -> None:
"""Close cover."""
if self.supported_features & SUPPORT_STOP:
self._values["state"] = STATE_CLOSING
else:
self._values["state"] = STATE_CLOSED
def stop_cover(self, **kwargs) -> None:
"""Stop cover."""
self._values["state"] = STATE_CLOSED if self.is_closed else STATE_OPEN
@property
def state(self):
"""Fake State."""
return CoverEntity.state.fget(self)
@property
def current_cover_position(self):
"""Return current position of cover."""
@ -82,26 +155,3 @@ class MockCover(MockEntity, CoverEntity):
def current_cover_tilt_position(self):
"""Return current position of cover tilt."""
return self._handle("current_cover_tilt_position")
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
if self._handle("supports_tilt"):
supported_features |= (
SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT
)
if self.current_cover_position is not None:
supported_features |= SUPPORT_SET_POSITION
if self.current_cover_tilt_position is not None:
supported_features |= (
SUPPORT_OPEN_TILT
| SUPPORT_CLOSE_TILT
| SUPPORT_STOP_TILT
| SUPPORT_SET_TILT_POSITION
)
return supported_features