Skip to content

Commit

Permalink
Added support for the opening and closing states to MQTT covers
Browse files Browse the repository at this point in the history
  • Loading branch information
rickvdl committed Jan 28, 2020
1 parent ab8b943 commit 9c343a4
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 11 deletions.
59 changes: 48 additions & 11 deletions homeassistant/components/mqtt/cover.py
Expand Up @@ -25,7 +25,9 @@
CONF_OPTIMISTIC,
CONF_VALUE_TEMPLATE,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
STATE_UNKNOWN,
)
from homeassistant.core import callback
Expand Down Expand Up @@ -64,7 +66,9 @@
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"
Expand Down Expand Up @@ -131,7 +135,9 @@ def validate_options(value):
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
Expand Down Expand Up @@ -208,7 +214,7 @@ def __init__(self, config, config_entry, discovery_hash):
"""Initialize the cover."""
self._unique_id = config.get(CONF_UNIQUE_ID)
self._position = None
self._state = None
self._state = STATE_UNKNOWN
self._sub_state = None

self._optimistic = None
Expand Down Expand Up @@ -289,12 +295,20 @@ def state_message_received(msg):
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
Expand All @@ -309,7 +323,11 @@ def position_message_received(msg):
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
Expand Down Expand Up @@ -370,8 +388,23 @@ def name(self):

@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."""
return None if self._state == STATE_UNKNOWN else self._state == STATE_CLOSED

@property
def is_open(self):
"""Return true if the cover is open."""
return self._state == STATE_OPEN

@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):
Expand Down Expand Up @@ -423,7 +456,7 @@ async def async_open_cover(self, **kwargs):
)
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
Expand All @@ -444,7 +477,7 @@ async def async_close_cover(self, **kwargs):
)
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
Expand Down Expand Up @@ -523,7 +556,7 @@ async def async_set_cover_position(self, **kwargs):
position = set_position_template.async_render(**kwargs)
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None
self._state = STATE_UNKNOWN
elif (
self._config[CONF_POSITION_OPEN] != 100
and self._config[CONF_POSITION_CLOSED] != 0
Expand All @@ -538,7 +571,11 @@ async def async_set_cover_position(self, **kwargs):
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()

Expand Down
43 changes: 43 additions & 0 deletions tests/components/mqtt/test_cover.py
Expand Up @@ -19,7 +19,9 @@
SERVICE_TOGGLE,
SERVICE_TOGGLE_COVER_TILT,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
Expand Down Expand Up @@ -67,6 +69,47 @@ 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_position_via_position_topic(hass, mqtt_mock):
"""Test the controlling state via topic."""
assert await async_setup_component(
Expand Down

0 comments on commit 9c343a4

Please sign in to comment.