mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
Update MQTT cover template handling (#50236)
* flake 8 * Implement feedback from PR * update warning message * added and updated tests * remove _has_tilt_topic variable * flake 8 * Implement feedback from PR * update warning message * added and updated tests * remove _has_tilt_topic variable * renamed _tilt_message_received to _tilt_payload_received * merged with latesed upstream/dev * converted if to try except for type check * Implemented the suggestions of @emontnemery * Tweak tests * logger info to debug Co-authored-by: Shay Levy <levyshay1@gmail.com> * cast tilt payload as int; combine exceptions to one line * Add test for JSONDecodeError * Update homeassistant/components/mqtt/cover.py Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Shay Levy <levyshay1@gmail.com>
This commit is contained in:
parent
72dfa8606e
commit
3554316f3f
@ -1,5 +1,6 @@
|
|||||||
"""Support for MQTT cover devices."""
|
"""Support for MQTT cover devices."""
|
||||||
import functools
|
import functools
|
||||||
|
from json import JSONDecodeError, loads as json_loads
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -252,7 +253,7 @@ class MqttCover(MqttEntity, CoverEntity):
|
|||||||
if tilt_status_template is not None:
|
if tilt_status_template is not None:
|
||||||
tilt_status_template.hass = self.hass
|
tilt_status_template.hass = self.hass
|
||||||
|
|
||||||
async def _subscribe_topics(self):
|
async def _subscribe_topics(self): # noqa: C901
|
||||||
"""(Re)Subscribe to topics."""
|
"""(Re)Subscribe to topics."""
|
||||||
topics = {}
|
topics = {}
|
||||||
|
|
||||||
@ -261,45 +262,36 @@ class MqttCover(MqttEntity, CoverEntity):
|
|||||||
def tilt_message_received(msg):
|
def tilt_message_received(msg):
|
||||||
"""Handle tilt updates."""
|
"""Handle tilt updates."""
|
||||||
payload = msg.payload
|
payload = msg.payload
|
||||||
tilt_status_template = self._config.get(CONF_TILT_STATUS_TEMPLATE)
|
template = self._config.get(CONF_TILT_STATUS_TEMPLATE)
|
||||||
if tilt_status_template is not None:
|
if template is not None:
|
||||||
payload = tilt_status_template.async_render_with_possible_json_value(
|
variables = {
|
||||||
payload
|
"entity_id": self.entity_id,
|
||||||
|
"position_open": self._config[CONF_POSITION_OPEN],
|
||||||
|
"position_closed": self._config[CONF_POSITION_CLOSED],
|
||||||
|
"tilt_min": self._config[CONF_TILT_MIN],
|
||||||
|
"tilt_max": self._config[CONF_TILT_MAX],
|
||||||
|
}
|
||||||
|
payload = template.async_render_with_possible_json_value(
|
||||||
|
payload, variables=variables
|
||||||
)
|
)
|
||||||
|
|
||||||
if not payload:
|
if not payload:
|
||||||
_LOGGER.debug("Ignoring empty tilt message from '%s'", msg.topic)
|
_LOGGER.debug("Ignoring empty tilt message from '%s'", msg.topic)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not payload.isnumeric():
|
self.tilt_payload_received(payload)
|
||||||
_LOGGER.warning("Payload '%s' is not numeric", payload)
|
|
||||||
elif (
|
|
||||||
self._config[CONF_TILT_MIN]
|
|
||||||
<= int(payload)
|
|
||||||
<= self._config[CONF_TILT_MAX]
|
|
||||||
or self._config[CONF_TILT_MAX]
|
|
||||||
<= int(payload)
|
|
||||||
<= self._config[CONF_TILT_MIN]
|
|
||||||
):
|
|
||||||
level = self.find_percentage_in_range(float(payload))
|
|
||||||
self._tilt_value = level
|
|
||||||
self.async_write_ha_state()
|
|
||||||
else:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Payload '%s' is out of range, must be between '%s' and '%s' inclusive",
|
|
||||||
payload,
|
|
||||||
self._config[CONF_TILT_MIN],
|
|
||||||
self._config[CONF_TILT_MAX],
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@log_messages(self.hass, self.entity_id)
|
@log_messages(self.hass, self.entity_id)
|
||||||
def state_message_received(msg):
|
def state_message_received(msg):
|
||||||
"""Handle new MQTT state messages."""
|
"""Handle new MQTT state messages."""
|
||||||
payload = msg.payload
|
payload = msg.payload
|
||||||
value_template = self._config.get(CONF_VALUE_TEMPLATE)
|
template = self._config.get(CONF_VALUE_TEMPLATE)
|
||||||
if value_template is not None:
|
if template is not None:
|
||||||
payload = value_template.async_render_with_possible_json_value(payload)
|
variables = {"entity_id": self.entity_id}
|
||||||
|
payload = template.async_render_with_possible_json_value(
|
||||||
|
payload, variables=variables
|
||||||
|
)
|
||||||
|
|
||||||
if not payload:
|
if not payload:
|
||||||
_LOGGER.debug("Ignoring empty state message from '%s'", msg.topic)
|
_LOGGER.debug("Ignoring empty state message from '%s'", msg.topic)
|
||||||
@ -347,26 +339,57 @@ class MqttCover(MqttEntity, CoverEntity):
|
|||||||
template = self._config.get(CONF_VALUE_TEMPLATE)
|
template = self._config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
if template is not None:
|
if template is not None:
|
||||||
payload = template.async_render_with_possible_json_value(payload)
|
variables = {
|
||||||
|
"entity_id": self.entity_id,
|
||||||
|
"position_open": self._config[CONF_POSITION_OPEN],
|
||||||
|
"position_closed": self._config[CONF_POSITION_CLOSED],
|
||||||
|
"tilt_min": self._config[CONF_TILT_MIN],
|
||||||
|
"tilt_max": self._config[CONF_TILT_MAX],
|
||||||
|
}
|
||||||
|
payload = template.async_render_with_possible_json_value(
|
||||||
|
payload, variables=variables
|
||||||
|
)
|
||||||
|
|
||||||
if not payload:
|
if not payload:
|
||||||
_LOGGER.debug("Ignoring empty position message from '%s'", msg.topic)
|
_LOGGER.debug(
|
||||||
return
|
"Ignoring empty position message from '%s'", msg.topic
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if payload.isnumeric():
|
try:
|
||||||
|
payload = json_loads(payload)
|
||||||
|
except JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(payload, dict):
|
||||||
|
if "position" not in payload:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Template (position_template) returned JSON without position attribute"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if "tilt_position" in payload:
|
||||||
|
if not self._config.get(CONF_TILT_STATE_OPTIMISTIC):
|
||||||
|
# reset forced set tilt optimistic
|
||||||
|
self._tilt_optimistic = False
|
||||||
|
self.tilt_payload_received(payload["tilt_position"])
|
||||||
|
payload = payload["position"]
|
||||||
|
|
||||||
|
try:
|
||||||
percentage_payload = self.find_percentage_in_range(
|
percentage_payload = self.find_percentage_in_range(
|
||||||
float(payload), COVER_PAYLOAD
|
float(payload), COVER_PAYLOAD
|
||||||
)
|
)
|
||||||
self._position = percentage_payload
|
except ValueError:
|
||||||
if self._config.get(CONF_STATE_TOPIC) is None:
|
|
||||||
self._state = (
|
|
||||||
STATE_CLOSED
|
|
||||||
if percentage_payload == DEFAULT_POSITION_CLOSED
|
|
||||||
else STATE_OPEN
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_LOGGER.warning("Payload '%s' is not numeric", payload)
|
_LOGGER.warning("Payload '%s' is not numeric", payload)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._position = percentage_payload
|
||||||
|
if self._config.get(CONF_STATE_TOPIC) is None:
|
||||||
|
self._state = (
|
||||||
|
STATE_CLOSED
|
||||||
|
if percentage_payload == DEFAULT_POSITION_CLOSED
|
||||||
|
else STATE_OPEN
|
||||||
|
)
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
if self._config.get(CONF_GET_POSITION_TOPIC):
|
if self._config.get(CONF_GET_POSITION_TOPIC):
|
||||||
@ -391,6 +414,7 @@ class MqttCover(MqttEntity, CoverEntity):
|
|||||||
self._optimistic = True
|
self._optimistic = True
|
||||||
|
|
||||||
if self._config.get(CONF_TILT_STATUS_TOPIC) is None:
|
if self._config.get(CONF_TILT_STATUS_TOPIC) is None:
|
||||||
|
# Force into optimistic tilt mode.
|
||||||
self._tilt_optimistic = True
|
self._tilt_optimistic = True
|
||||||
else:
|
else:
|
||||||
self._tilt_value = STATE_UNKNOWN
|
self._tilt_value = STATE_UNKNOWN
|
||||||
@ -550,12 +574,21 @@ class MqttCover(MqttEntity, CoverEntity):
|
|||||||
|
|
||||||
async def async_set_cover_tilt_position(self, **kwargs):
|
async def async_set_cover_tilt_position(self, **kwargs):
|
||||||
"""Move the cover tilt to a specific position."""
|
"""Move the cover tilt to a specific position."""
|
||||||
set_tilt_template = self._config.get(CONF_TILT_COMMAND_TEMPLATE)
|
template = self._config.get(CONF_TILT_COMMAND_TEMPLATE)
|
||||||
tilt = kwargs[ATTR_TILT_POSITION]
|
tilt = kwargs[ATTR_TILT_POSITION]
|
||||||
percentage_tilt = tilt
|
percentage_tilt = tilt
|
||||||
tilt = self.find_in_range_from_percent(tilt)
|
tilt = self.find_in_range_from_percent(tilt)
|
||||||
if set_tilt_template is not None:
|
# Handover the tilt after calculated from percent would make it more consistent with receiving templates
|
||||||
tilt = set_tilt_template.async_render(parse_result=False, **kwargs)
|
if template is not None:
|
||||||
|
variables = {
|
||||||
|
"tilt_position": percentage_tilt,
|
||||||
|
"entity_id": self.entity_id,
|
||||||
|
"position_open": self._config[CONF_POSITION_OPEN],
|
||||||
|
"position_closed": self._config[CONF_POSITION_CLOSED],
|
||||||
|
"tilt_min": self._config[CONF_TILT_MIN],
|
||||||
|
"tilt_max": self._config[CONF_TILT_MAX],
|
||||||
|
}
|
||||||
|
tilt = template.async_render(parse_result=False, variables=variables)
|
||||||
|
|
||||||
mqtt.async_publish(
|
mqtt.async_publish(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -565,17 +598,26 @@ class MqttCover(MqttEntity, CoverEntity):
|
|||||||
self._config[CONF_RETAIN],
|
self._config[CONF_RETAIN],
|
||||||
)
|
)
|
||||||
if self._tilt_optimistic:
|
if self._tilt_optimistic:
|
||||||
|
_LOGGER.debug("Set tilt value optimistic")
|
||||||
self._tilt_value = percentage_tilt
|
self._tilt_value = percentage_tilt
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_cover_position(self, **kwargs):
|
async def async_set_cover_position(self, **kwargs):
|
||||||
"""Move the cover to a specific position."""
|
"""Move the cover to a specific position."""
|
||||||
set_position_template = self._config.get(CONF_SET_POSITION_TEMPLATE)
|
template = self._config.get(CONF_SET_POSITION_TEMPLATE)
|
||||||
position = kwargs[ATTR_POSITION]
|
position = kwargs[ATTR_POSITION]
|
||||||
percentage_position = position
|
percentage_position = position
|
||||||
position = self.find_in_range_from_percent(position, COVER_PAYLOAD)
|
position = self.find_in_range_from_percent(position, COVER_PAYLOAD)
|
||||||
if set_position_template is not None:
|
if template is not None:
|
||||||
position = set_position_template.async_render(parse_result=False, **kwargs)
|
variables = {
|
||||||
|
"position": percentage_position,
|
||||||
|
"entity_id": self.entity_id,
|
||||||
|
"position_open": self._config[CONF_POSITION_OPEN],
|
||||||
|
"position_closed": self._config[CONF_POSITION_CLOSED],
|
||||||
|
"tilt_min": self._config[CONF_TILT_MIN],
|
||||||
|
"tilt_max": self._config[CONF_TILT_MAX],
|
||||||
|
}
|
||||||
|
position = template.async_render(parse_result=False, variables=variables)
|
||||||
|
|
||||||
mqtt.async_publish(
|
mqtt.async_publish(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -650,3 +692,29 @@ class MqttCover(MqttEntity, CoverEntity):
|
|||||||
if range_type == TILT_PAYLOAD and self._config.get(CONF_TILT_INVERT_STATE):
|
if range_type == TILT_PAYLOAD and self._config.get(CONF_TILT_INVERT_STATE):
|
||||||
position = max_range - position + offset
|
position = max_range - position + offset
|
||||||
return position
|
return position
|
||||||
|
|
||||||
|
def tilt_payload_received(self, _payload):
|
||||||
|
"""Set the tilt value."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = int(round(float(_payload)))
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.warning("Payload '%s' is not numeric", _payload)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._config[CONF_TILT_MIN] <= int(payload) <= self._config[CONF_TILT_MAX]
|
||||||
|
or self._config[CONF_TILT_MAX]
|
||||||
|
<= int(payload)
|
||||||
|
<= self._config[CONF_TILT_MIN]
|
||||||
|
):
|
||||||
|
level = self.find_percentage_in_range(payload)
|
||||||
|
self._tilt_value = level
|
||||||
|
self.async_write_ha_state()
|
||||||
|
else:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Payload '%s' is out of range, must be between '%s' and '%s' inclusive",
|
||||||
|
payload,
|
||||||
|
self._config[CONF_TILT_MIN],
|
||||||
|
self._config[CONF_TILT_MAX],
|
||||||
|
)
|
||||||
|
@ -260,6 +260,45 @@ async def test_state_via_template(hass, mqtt_mock):
|
|||||||
assert state.state == STATE_CLOSED
|
assert state.state == STATE_CLOSED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_state_via_template_and_entity_id(hass, mqtt_mock):
|
||||||
|
"""Test the controlling state via topic."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"qos": 0,
|
||||||
|
"value_template": '\
|
||||||
|
{% if value == "open" or value == "closed" %}\
|
||||||
|
{{ value }}\
|
||||||
|
{% else %}\
|
||||||
|
{{ states(entity_id) }}\
|
||||||
|
{% endif %}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("cover.test")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "state-topic", "open")
|
||||||
|
async_fire_mqtt_message(hass, "state-topic", "invalid")
|
||||||
|
|
||||||
|
state = hass.states.get("cover.test")
|
||||||
|
assert state.state == STATE_OPEN
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "state-topic", "closed")
|
||||||
|
async_fire_mqtt_message(hass, "state-topic", "invalid")
|
||||||
|
|
||||||
|
state = hass.states.get("cover.test")
|
||||||
|
assert state.state == STATE_CLOSED
|
||||||
|
|
||||||
|
|
||||||
async def test_state_via_template_with_json_value(hass, mqtt_mock, caplog):
|
async def test_state_via_template_with_json_value(hass, mqtt_mock, caplog):
|
||||||
"""Test the controlling state via topic with JSON value."""
|
"""Test the controlling state via topic with JSON value."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
@ -336,6 +375,47 @@ async def test_position_via_template(hass, mqtt_mock):
|
|||||||
assert state.state == STATE_CLOSED
|
assert state.state == STATE_CLOSED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_position_via_template_and_entity_id(hass, mqtt_mock):
|
||||||
|
"""Test the controlling state via topic."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"qos": 0,
|
||||||
|
"position_template": '\
|
||||||
|
{% if state_attr(entity_id, "current_position") == None %}\
|
||||||
|
{{ value }}\
|
||||||
|
{% else %}\
|
||||||
|
{{ state_attr(entity_id, "current_position") + value | int }}\
|
||||||
|
{% endif %}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("cover.test")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "10")
|
||||||
|
|
||||||
|
current_cover_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position == 10
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "10")
|
||||||
|
|
||||||
|
current_cover_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position == 20
|
||||||
|
|
||||||
|
|
||||||
async def test_optimistic_state_change(hass, mqtt_mock):
|
async def test_optimistic_state_change(hass, mqtt_mock):
|
||||||
"""Test changing state optimistically."""
|
"""Test changing state optimistically."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
@ -712,7 +792,13 @@ async def test_position_update(hass, mqtt_mock):
|
|||||||
assert current_cover_position == 22
|
assert current_cover_position == 22
|
||||||
|
|
||||||
|
|
||||||
async def test_set_position_templated(hass, mqtt_mock):
|
@pytest.mark.parametrize(
|
||||||
|
"pos_template,pos_call,pos_message",
|
||||||
|
[("{{position-1}}", 43, "42"), ("{{100-62}}", 100, "38")],
|
||||||
|
)
|
||||||
|
async def test_set_position_templated(
|
||||||
|
hass, mqtt_mock, pos_template, pos_call, pos_message
|
||||||
|
):
|
||||||
"""Test setting cover position via template."""
|
"""Test setting cover position via template."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
@ -726,7 +812,51 @@ async def test_set_position_templated(hass, mqtt_mock):
|
|||||||
"position_open": 100,
|
"position_open": 100,
|
||||||
"position_closed": 0,
|
"position_closed": 0,
|
||||||
"set_position_topic": "set-position-topic",
|
"set_position_topic": "set-position-topic",
|
||||||
"set_position_template": "{{100-62}}",
|
"set_position_template": pos_template,
|
||||||
|
"payload_open": "OPEN",
|
||||||
|
"payload_close": "CLOSE",
|
||||||
|
"payload_stop": "STOP",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
cover.DOMAIN,
|
||||||
|
SERVICE_SET_COVER_POSITION,
|
||||||
|
{ATTR_ENTITY_ID: "cover.test", ATTR_POSITION: pos_call},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
"set-position-topic", pos_message, 0, False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_position_templated_and_attributes(hass, mqtt_mock):
|
||||||
|
"""Test setting cover position via template and using entities attributes."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"position_open": 100,
|
||||||
|
"position_closed": 0,
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"set_position_template": '\
|
||||||
|
{% if position > 99 %}\
|
||||||
|
{% if state_attr(entity_id, "current_position") == None %}\
|
||||||
|
{{ 5 }}\
|
||||||
|
{% else %}\
|
||||||
|
{{ 23 }}\
|
||||||
|
{% endif %}\
|
||||||
|
{% else %}\
|
||||||
|
{{ 42 }}\
|
||||||
|
{% endif %}',
|
||||||
"payload_open": "OPEN",
|
"payload_open": "OPEN",
|
||||||
"payload_close": "CLOSE",
|
"payload_close": "CLOSE",
|
||||||
"payload_stop": "STOP",
|
"payload_stop": "STOP",
|
||||||
@ -742,8 +872,85 @@ async def test_set_position_templated(hass, mqtt_mock):
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with("set-position-topic", "5", 0, False)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_tilt_templated(hass, mqtt_mock):
|
||||||
|
"""Test setting cover tilt position via template."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"tilt_command_topic": "tilt-command-topic",
|
||||||
|
"position_open": 100,
|
||||||
|
"position_closed": 0,
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"set_position_template": "{{position-1}}",
|
||||||
|
"tilt_command_template": "{{tilt_position+1}}",
|
||||||
|
"payload_open": "OPEN",
|
||||||
|
"payload_close": "CLOSE",
|
||||||
|
"payload_stop": "STOP",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
cover.DOMAIN,
|
||||||
|
SERVICE_SET_COVER_TILT_POSITION,
|
||||||
|
{ATTR_ENTITY_ID: "cover.test", ATTR_TILT_POSITION: 41},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
"set-position-topic", "38", 0, False
|
"tilt-command-topic", "42", 0, False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_tilt_templated_and_attributes(hass, mqtt_mock):
|
||||||
|
"""Test setting cover tilt position via template and using entities attributes."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"tilt_command_topic": "tilt-command-topic",
|
||||||
|
"position_open": 100,
|
||||||
|
"position_closed": 0,
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"set_position_template": "{{position-1}}",
|
||||||
|
"tilt_command_template": '\
|
||||||
|
{% if state_attr(entity_id, "friendly_name") != "test" %}\
|
||||||
|
{{ 5 }}\
|
||||||
|
{% else %}\
|
||||||
|
{{ 23 }}\
|
||||||
|
{% endif %}',
|
||||||
|
"payload_open": "OPEN",
|
||||||
|
"payload_close": "CLOSE",
|
||||||
|
"payload_stop": "STOP",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
cover.DOMAIN,
|
||||||
|
SERVICE_SET_COVER_TILT_POSITION,
|
||||||
|
{ATTR_ENTITY_ID: "cover.test", ATTR_TILT_POSITION: 99},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
"tilt-command-topic", "23", 0, False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -2508,6 +2715,187 @@ async def test_position_via_position_topic_template_json_value(hass, mqtt_mock,
|
|||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_position_template_with_entity_id(hass, mqtt_mock):
|
||||||
|
"""Test position by updating status via position template."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"position_template": '\
|
||||||
|
{% if state_attr(entity_id, "current_position") != None %}\
|
||||||
|
{{ value | int + state_attr(entity_id, "current_position") }} \
|
||||||
|
{% else %} \
|
||||||
|
{{ value }} \
|
||||||
|
{% endif %}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "10")
|
||||||
|
|
||||||
|
current_cover_position_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position_position == 10
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "10")
|
||||||
|
|
||||||
|
current_cover_position_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position_position == 20
|
||||||
|
|
||||||
|
|
||||||
|
async def test_position_via_position_topic_template_return_json(hass, mqtt_mock):
|
||||||
|
"""Test position by updating status via position template and returning json."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"position_template": '{{ {"position" : value} | tojson }}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "55")
|
||||||
|
|
||||||
|
current_cover_position_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position_position == 55
|
||||||
|
|
||||||
|
|
||||||
|
async def test_position_via_position_topic_template_return_json_warning(
|
||||||
|
hass, caplog, mqtt_mock
|
||||||
|
):
|
||||||
|
"""Test position by updating status via position template returning json without position attribute."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"position_template": '{{ {"pos" : value} | tojson }}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "55")
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Template (position_template) returned JSON without position attribute"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_position_and_tilt_via_position_topic_template_return_json(
|
||||||
|
hass, mqtt_mock
|
||||||
|
):
|
||||||
|
"""Test position and tilt by updating the position via position template."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"position_template": '\
|
||||||
|
{{ {"position" : value, "tilt_position" : (value | int / 2)| int } | tojson }}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "0")
|
||||||
|
|
||||||
|
current_cover_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
current_tilt_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_TILT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position == 0 and current_tilt_position == 0
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "99")
|
||||||
|
current_cover_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
current_tilt_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_TILT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position == 99 and current_tilt_position == 49
|
||||||
|
|
||||||
|
|
||||||
|
async def test_position_via_position_topic_template_all_variables(hass, mqtt_mock):
|
||||||
|
"""Test position by updating status via position template."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"tilt_command_topic": "tilt-command-topic",
|
||||||
|
"position_open": 99,
|
||||||
|
"position_closed": 1,
|
||||||
|
"tilt_min": 11,
|
||||||
|
"tilt_max": 22,
|
||||||
|
"position_template": "\
|
||||||
|
{% if value | int < tilt_max %}\
|
||||||
|
{{ tilt_min }}\
|
||||||
|
{% endif %}\
|
||||||
|
{% if value | int > position_closed %}\
|
||||||
|
{{ position_open }}\
|
||||||
|
{% endif %}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "0")
|
||||||
|
|
||||||
|
current_cover_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position == 10
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "55")
|
||||||
|
current_cover_position = hass.states.get("cover.test").attributes[
|
||||||
|
ATTR_CURRENT_POSITION
|
||||||
|
]
|
||||||
|
assert current_cover_position == 100
|
||||||
|
|
||||||
|
|
||||||
async def test_set_state_via_stopped_state_no_position_topic(hass, mqtt_mock):
|
async def test_set_state_via_stopped_state_no_position_topic(hass, mqtt_mock):
|
||||||
"""Test the controlling state via stopped state when no position topic."""
|
"""Test the controlling state via stopped state when no position topic."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
@ -2555,3 +2943,29 @@ async def test_set_state_via_stopped_state_no_position_topic(hass, mqtt_mock):
|
|||||||
|
|
||||||
state = hass.states.get("cover.test")
|
state = hass.states.get("cover.test")
|
||||||
assert state.state == STATE_CLOSED
|
assert state.state == STATE_CLOSED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_position_via_position_topic_template_return_invalid_json(
|
||||||
|
hass, caplog, mqtt_mock
|
||||||
|
):
|
||||||
|
"""Test position by updating status via position template and returning invalid json."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
cover.DOMAIN,
|
||||||
|
{
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"platform": "mqtt",
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"set_position_topic": "set-position-topic",
|
||||||
|
"position_topic": "get-position-topic",
|
||||||
|
"position_template": '{{ {"position" : invalid_json} }}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "get-position-topic", "55")
|
||||||
|
|
||||||
|
assert ("Payload '{'position': Undefined}' is not numeric") in caplog.text
|
||||||
|
Loading…
x
Reference in New Issue
Block a user