Add opening and closing states to MQTT covers (#31259)

* Added support for the opening and closing states to MQTT covers

* Processed PR feedback on MQTT cover changes

* Add missing MQTT abbreviation to fix failing tests

* Fixed typo in MQTT abbreviations

* Added mqtt set cover position optimistic test
pull/31329/head
Rick 2020-01-30 21:14:46 +01:00 committed by GitHub
parent d5486f883d
commit 3718b25bd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 135 additions and 9 deletions

View File

@ -132,9 +132,11 @@ ABBREVIATIONS = {
"spds": "speeds",
"src_type": "source_type",
"stat_clsd": "state_closed",
"stat_closing": "state_closing",
"stat_off": "state_off",
"stat_on": "state_on",
"stat_open": "state_open",
"stat_opening": "state_opening",
"stat_locked": "state_locked",
"stat_unlocked": "state_unlocked",
"stat_t": "state_topic",

View File

@ -25,7 +25,9 @@ from homeassistant.const import (
CONF_OPTIMISTIC,
CONF_VALUE_TEMPLATE,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
STATE_UNKNOWN,
)
from homeassistant.core import callback
@ -64,7 +66,9 @@ CONF_PAYLOAD_STOP = "payload_stop"
CONF_POSITION_CLOSED = "position_closed"
CONF_POSITION_OPEN = "position_open"
CONF_STATE_CLOSED = "state_closed"
CONF_STATE_CLOSING = "state_closing"
CONF_STATE_OPEN = "state_open"
CONF_STATE_OPENING = "state_opening"
CONF_TILT_CLOSED_POSITION = "tilt_closed_value"
CONF_TILT_INVERT_STATE = "tilt_invert_state"
CONF_TILT_MAX = "tilt_max"
@ -131,7 +135,9 @@ PLATFORM_SCHEMA = vol.All(
vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_SET_POSITION_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_STATE_CLOSING, default=STATE_CLOSING): cv.string,
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_OPENING, default=STATE_OPENING): cv.string,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(
CONF_TILT_CLOSED_POSITION, default=DEFAULT_TILT_CLOSED_POSITION
@ -289,12 +295,20 @@ class MqttCover(
payload = template.async_render_with_possible_json_value(payload)
if payload == self._config[CONF_STATE_OPEN]:
self._state = False
self._state = STATE_OPEN
elif payload == self._config[CONF_STATE_OPENING]:
self._state = STATE_OPENING
elif payload == self._config[CONF_STATE_CLOSED]:
self._state = True
self._state = STATE_CLOSED
elif payload == self._config[CONF_STATE_CLOSING]:
self._state = STATE_CLOSING
else:
_LOGGER.warning("Payload is not True or False: %s", payload)
_LOGGER.warning(
"Payload is not supported (e.g. open, closed, opening, closing): %s",
payload,
)
return
self.async_write_ha_state()
@callback
@ -309,7 +323,11 @@ class MqttCover(
float(payload), COVER_PAYLOAD
)
self._position = percentage_payload
self._state = percentage_payload == DEFAULT_POSITION_CLOSED
self._state = (
STATE_CLOSED
if percentage_payload == DEFAULT_POSITION_CLOSED
else STATE_OPEN
)
else:
_LOGGER.warning("Payload is not integer within range: %s", payload)
return
@ -370,8 +388,21 @@ class MqttCover(
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._state
"""Return true if the cover is closed or None if the status is unknown."""
if self._state is None:
return None
return self._state == STATE_CLOSED
@property
def is_opening(self):
"""Return true if the cover is actively opening."""
return self._state == STATE_OPENING
@property
def is_closing(self):
"""Return true if the cover is actively closing."""
return self._state == STATE_CLOSING
@property
def current_cover_position(self):
@ -423,7 +454,7 @@ class MqttCover(
)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = False
self._state = STATE_OPEN
if self._config.get(CONF_GET_POSITION_TOPIC):
self._position = self.find_percentage_in_range(
self._config[CONF_POSITION_OPEN], COVER_PAYLOAD
@ -444,7 +475,7 @@ class MqttCover(
)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = True
self._state = STATE_CLOSED
if self._config.get(CONF_GET_POSITION_TOPIC):
self._position = self.find_percentage_in_range(
self._config[CONF_POSITION_CLOSED], COVER_PAYLOAD
@ -538,7 +569,11 @@ class MqttCover(
self._config[CONF_RETAIN],
)
if self._optimistic:
self._state = percentage_position == self._config[CONF_POSITION_CLOSED]
self._state = (
STATE_CLOSED
if percentage_position == self._config[CONF_POSITION_CLOSED]
else STATE_OPEN
)
self._position = percentage_position
self.async_write_ha_state()

View File

@ -19,7 +19,9 @@ from homeassistant.const import (
SERVICE_TOGGLE,
SERVICE_TOGGLE_COVER_TILT,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
@ -67,6 +69,93 @@ async def test_state_via_state_topic(hass, mqtt_mock):
assert state.state == STATE_OPEN
async def test_opening_and_closing_state_via_custom_state_payload(hass, mqtt_mock):
"""Test the controlling opening and closing state via a custom payload."""
assert await async_setup_component(
hass,
cover.DOMAIN,
{
cover.DOMAIN: {
"platform": "mqtt",
"name": "test",
"state_topic": "state-topic",
"command_topic": "command-topic",
"qos": 0,
"payload_open": "OPEN",
"payload_close": "CLOSE",
"payload_stop": "STOP",
"state_opening": "34",
"state_closing": "--43",
}
},
)
state = hass.states.get("cover.test")
assert state.state == STATE_UNKNOWN
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "state-topic", "34")
state = hass.states.get("cover.test")
assert state.state == STATE_OPENING
async_fire_mqtt_message(hass, "state-topic", "--43")
state = hass.states.get("cover.test")
assert state.state == STATE_CLOSING
async_fire_mqtt_message(hass, "state-topic", STATE_CLOSED)
state = hass.states.get("cover.test")
assert state.state == STATE_CLOSED
async def test_open_closed_state_from_position_optimistic(hass, mqtt_mock):
"""Test the state after setting the position using optimistic mode."""
assert await async_setup_component(
hass,
cover.DOMAIN,
{
cover.DOMAIN: {
"platform": "mqtt",
"name": "test",
"position_topic": "position-topic",
"set_position_topic": "set-position-topic",
"qos": 0,
"payload_open": "OPEN",
"payload_close": "CLOSE",
"payload_stop": "STOP",
"optimistic": True,
}
},
)
state = hass.states.get("cover.test")
assert state.state == STATE_UNKNOWN
await hass.services.async_call(
cover.DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.test", ATTR_POSITION: 0},
blocking=True,
)
state = hass.states.get("cover.test")
assert state.state == STATE_CLOSED
assert state.attributes.get(ATTR_ASSUMED_STATE)
await hass.services.async_call(
cover.DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.test", ATTR_POSITION: 100},
blocking=True,
)
state = hass.states.get("cover.test")
assert state.state == STATE_OPEN
assert state.attributes.get(ATTR_ASSUMED_STATE)
async def test_position_via_position_topic(hass, mqtt_mock):
"""Test the controlling state via topic."""
assert await async_setup_component(