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 testpull/31329/head
parent
d5486f883d
commit
3718b25bd9
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue