From 707a8e62f97f77c9777b751a2f1cdc56c97b5154 Mon Sep 17 00:00:00 2001 From: Olivier Cloirec <5033885+clook@users.noreply.github.com> Date: Sun, 10 Jan 2021 18:05:52 +0100 Subject: [PATCH] Add stop support to openzwave (mqtt) cover (#44622) * feat: add stop to openzwave (mqtt) cover * Fix isort and black linter * Remove supported_features for cover. As suggested by @MartinHjelmare, not needed anymore because base class implementation is sufficient. https://github.com/home-assistant/core/pull/44622#discussion_r549854542 * Make a simpler version depending on idempotency qt-openzwave already implements idempotency, see: https://github.com/OpenZWave/qt-openzwave/blob/77e414217f83fae89a8f41156db3783d562703b1/qt-openzwave/source/qtozwvalueidmodel.cpp#L180 We can use it and trigger button release anywhen. * Clean up Co-authored-by: Martin Hjelmare --- homeassistant/components/ozw/cover.py | 22 +++--- tests/components/ozw/test_cover.py | 96 +++++++++++++++++++-------- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/ozw/cover.py b/homeassistant/components/ozw/cover.py index 0ac2da91e44..1c708b55ffb 100644 --- a/homeassistant/components/ozw/cover.py +++ b/homeassistant/components/ozw/cover.py @@ -7,7 +7,6 @@ from homeassistant.components.cover import ( DOMAIN as COVER_DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverEntity, ) from homeassistant.core import callback @@ -16,9 +15,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity -SUPPORTED_FEATURES_POSITION = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE VALUE_SELECTED_ID = "Selected_id" +PRESS_BUTTON = True +RELEASE_BUTTON = False async def async_setup_entry(hass, config_entry, async_add_entities): @@ -52,11 +52,6 @@ def percent_to_zwave_position(value): class ZWaveCoverEntity(ZWaveDeviceEntity, CoverEntity): """Representation of a Z-Wave Cover device.""" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORTED_FEATURES_POSITION - @property def is_closed(self): """Return true if cover is closed.""" @@ -73,11 +68,20 @@ class ZWaveCoverEntity(ZWaveDeviceEntity, CoverEntity): async def async_open_cover(self, **kwargs): """Open the cover.""" - self.values.primary.send_value(99) + self.values.open.send_value(PRESS_BUTTON) async def async_close_cover(self, **kwargs): """Close cover.""" - self.values.primary.send_value(0) + self.values.close.send_value(PRESS_BUTTON) + + async def async_stop_cover(self, **kwargs): + """Stop cover.""" + # Need to issue both buttons release since qt-openzwave implements idempotency + # keeping internal state of model to trigger actual updates. We could also keep + # another state in Home Assistant to know which button to release, + # but this implementation is simpler. + self.values.open.send_value(RELEASE_BUTTON) + self.values.close.send_value(RELEASE_BUTTON) class ZwaveGarageDoorBarrier(ZWaveDeviceEntity, CoverEntity): diff --git a/tests/components/ozw/test_cover.py b/tests/components/ozw/test_cover.py index 07f7d76efb0..2b3b1e06862 100644 --- a/tests/components/ozw/test_cover.py +++ b/tests/components/ozw/test_cover.py @@ -16,6 +16,25 @@ async def test_cover(hass, cover_data, sent_messages, cover_msg): assert state.state == "closed" assert state.attributes[ATTR_CURRENT_POSITION] == 0 + # Test setting position + await hass.services.async_call( + "cover", + "set_cover_position", + {"entity_id": "cover.roller_shutter_3_instance_1_level", "position": 50}, + blocking=True, + ) + assert len(sent_messages) == 1 + msg = sent_messages[0] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 50, "ValueIDKey": 625573905} + + # Feedback on state + cover_msg.decode() + cover_msg.payload["Value"] = 50 + cover_msg.encode() + receive_message(cover_msg) + await hass.async_block_till_done() + # Test opening await hass.services.async_call( "cover", @@ -23,22 +42,26 @@ async def test_cover(hass, cover_data, sent_messages, cover_msg): {"entity_id": "cover.roller_shutter_3_instance_1_level"}, blocking=True, ) - assert len(sent_messages) == 1 - msg = sent_messages[0] + assert len(sent_messages) == 2 + msg = sent_messages[1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert msg["payload"] == {"Value": 99, "ValueIDKey": 625573905} + assert msg["payload"] == {"Value": True, "ValueIDKey": 281475602284568} - # Feedback on state - cover_msg.decode() - cover_msg.payload["Value"] = 99 - cover_msg.encode() - receive_message(cover_msg) - await hass.async_block_till_done() + # Test stopping after opening + await hass.services.async_call( + "cover", + "stop_cover", + {"entity_id": "cover.roller_shutter_3_instance_1_level"}, + blocking=True, + ) + assert len(sent_messages) == 4 + msg = sent_messages[2] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 281475602284568} - state = hass.states.get("cover.roller_shutter_3_instance_1_level") - assert state is not None - assert state.state == "open" - assert state.attributes[ATTR_CURRENT_POSITION] == 100 + msg = sent_messages[3] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 562950578995224} # Test closing await hass.services.async_call( @@ -47,22 +70,43 @@ async def test_cover(hass, cover_data, sent_messages, cover_msg): {"entity_id": "cover.roller_shutter_3_instance_1_level"}, blocking=True, ) - assert len(sent_messages) == 2 - msg = sent_messages[1] + assert len(sent_messages) == 5 + msg = sent_messages[4] assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert msg["payload"] == {"Value": 0, "ValueIDKey": 625573905} + assert msg["payload"] == {"Value": True, "ValueIDKey": 562950578995224} - # Test setting position + # Test stopping after closing await hass.services.async_call( "cover", - "set_cover_position", - {"entity_id": "cover.roller_shutter_3_instance_1_level", "position": 50}, + "stop_cover", + {"entity_id": "cover.roller_shutter_3_instance_1_level"}, blocking=True, ) - assert len(sent_messages) == 3 - msg = sent_messages[2] + assert len(sent_messages) == 7 + msg = sent_messages[5] assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert msg["payload"] == {"Value": 50, "ValueIDKey": 625573905} + assert msg["payload"] == {"Value": False, "ValueIDKey": 281475602284568} + + msg = sent_messages[6] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 562950578995224} + + # Test stopping after no open/close + await hass.services.async_call( + "cover", + "stop_cover", + {"entity_id": "cover.roller_shutter_3_instance_1_level"}, + blocking=True, + ) + # both stop open/close messages sent + assert len(sent_messages) == 9 + msg = sent_messages[7] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 281475602284568} + + msg = sent_messages[8] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 562950578995224} # Test converting position to zwave range for position > 0 await hass.services.async_call( @@ -71,8 +115,8 @@ async def test_cover(hass, cover_data, sent_messages, cover_msg): {"entity_id": "cover.roller_shutter_3_instance_1_level", "position": 100}, blocking=True, ) - assert len(sent_messages) == 4 - msg = sent_messages[3] + assert len(sent_messages) == 10 + msg = sent_messages[9] assert msg["topic"] == "OpenZWave/1/command/setvalue/" assert msg["payload"] == {"Value": 99, "ValueIDKey": 625573905} @@ -83,8 +127,8 @@ async def test_cover(hass, cover_data, sent_messages, cover_msg): {"entity_id": "cover.roller_shutter_3_instance_1_level", "position": 0}, blocking=True, ) - assert len(sent_messages) == 5 - msg = sent_messages[4] + assert len(sent_messages) == 11 + msg = sent_messages[10] assert msg["topic"] == "OpenZWave/1/command/setvalue/" assert msg["payload"] == {"Value": 0, "ValueIDKey": 625573905}