mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
optimistic mode for template covers (w/o timed movement) (#8402)
* Emulate set_current_position in cover.template * Add opportunistic mode * Prevent another move when cover is already moving. Add tests for opotunistic/timed-delay mode * Remove timed-move capabilities * Set init state to unknown * cleanup template * Update test_template.py
This commit is contained in:
parent
fb5019e73f
commit
2ec0d25a38
@ -19,7 +19,7 @@ from homeassistant.const import (
|
|||||||
CONF_FRIENDLY_NAME, CONF_ENTITY_ID,
|
CONF_FRIENDLY_NAME, CONF_ENTITY_ID,
|
||||||
EVENT_HOMEASSISTANT_START, MATCH_ALL,
|
EVENT_HOMEASSISTANT_START, MATCH_ALL,
|
||||||
CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE,
|
CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE,
|
||||||
STATE_OPEN, STATE_CLOSED)
|
CONF_OPTIMISTIC, STATE_OPEN, STATE_CLOSED)
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
from homeassistant.helpers.entity import async_generate_entity_id
|
||||||
@ -39,6 +39,8 @@ CLOSE_ACTION = 'close_cover'
|
|||||||
STOP_ACTION = 'stop_cover'
|
STOP_ACTION = 'stop_cover'
|
||||||
POSITION_ACTION = 'set_cover_position'
|
POSITION_ACTION = 'set_cover_position'
|
||||||
TILT_ACTION = 'set_cover_tilt_position'
|
TILT_ACTION = 'set_cover_tilt_position'
|
||||||
|
CONF_TILT_OPTIMISTIC = 'tilt_optimistic'
|
||||||
|
|
||||||
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
|
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
|
||||||
CONF_OPEN_OR_CLOSE = 'open_or_close'
|
CONF_OPEN_OR_CLOSE = 'open_or_close'
|
||||||
|
|
||||||
@ -56,6 +58,8 @@ COVER_SCHEMA = vol.Schema({
|
|||||||
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
|
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
|
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
|
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
|
||||||
|
vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean,
|
||||||
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
|
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
|
||||||
@ -83,11 +87,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
stop_action = device_config.get(STOP_ACTION)
|
stop_action = device_config.get(STOP_ACTION)
|
||||||
position_action = device_config.get(POSITION_ACTION)
|
position_action = device_config.get(POSITION_ACTION)
|
||||||
tilt_action = device_config.get(TILT_ACTION)
|
tilt_action = device_config.get(TILT_ACTION)
|
||||||
|
optimistic = device_config.get(CONF_OPTIMISTIC)
|
||||||
if position_template is None and state_template is None:
|
tilt_optimistic = device_config.get(CONF_TILT_OPTIMISTIC)
|
||||||
_LOGGER.error('Must specify either %s' or '%s',
|
|
||||||
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if position_action is None and open_action is None:
|
if position_action is None and open_action is None:
|
||||||
_LOGGER.error('Must specify at least one of %s' or '%s',
|
_LOGGER.error('Must specify at least one of %s' or '%s',
|
||||||
@ -125,7 +126,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
device, friendly_name, state_template,
|
device, friendly_name, state_template,
|
||||||
position_template, tilt_template, icon_template,
|
position_template, tilt_template, icon_template,
|
||||||
open_action, close_action, stop_action,
|
open_action, close_action, stop_action,
|
||||||
position_action, tilt_action, entity_ids
|
position_action, tilt_action,
|
||||||
|
optimistic, tilt_optimistic, entity_ids
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not covers:
|
if not covers:
|
||||||
@ -142,7 +144,8 @@ class CoverTemplate(CoverDevice):
|
|||||||
def __init__(self, hass, device_id, friendly_name, state_template,
|
def __init__(self, hass, device_id, friendly_name, state_template,
|
||||||
position_template, tilt_template, icon_template,
|
position_template, tilt_template, icon_template,
|
||||||
open_action, close_action, stop_action,
|
open_action, close_action, stop_action,
|
||||||
position_action, tilt_action, entity_ids):
|
position_action, tilt_action,
|
||||||
|
optimistic, tilt_optimistic, entity_ids):
|
||||||
"""Initialize the Template cover."""
|
"""Initialize the Template cover."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entity_id = async_generate_entity_id(
|
self.entity_id = async_generate_entity_id(
|
||||||
@ -167,6 +170,9 @@ class CoverTemplate(CoverDevice):
|
|||||||
self._tilt_script = None
|
self._tilt_script = None
|
||||||
if tilt_action is not None:
|
if tilt_action is not None:
|
||||||
self._tilt_script = Script(hass, tilt_action)
|
self._tilt_script = Script(hass, tilt_action)
|
||||||
|
self._optimistic = (optimistic or
|
||||||
|
(not state_template and not position_template))
|
||||||
|
self._tilt_optimistic = tilt_optimistic or not tilt_template
|
||||||
self._icon = None
|
self._icon = None
|
||||||
self._position = None
|
self._position = None
|
||||||
self._tilt_value = None
|
self._tilt_value = None
|
||||||
@ -260,19 +266,23 @@ class CoverTemplate(CoverDevice):
|
|||||||
def async_open_cover(self, **kwargs):
|
def async_open_cover(self, **kwargs):
|
||||||
"""Move the cover up."""
|
"""Move the cover up."""
|
||||||
if self._open_script:
|
if self._open_script:
|
||||||
self.hass.async_add_job(self._open_script.async_run())
|
yield from self._open_script.async_run()
|
||||||
elif self._position_script:
|
elif self._position_script:
|
||||||
self.hass.async_add_job(self._position_script.async_run(
|
yield from self._position_script.async_run({"position": 100})
|
||||||
{"position": 100}))
|
if self._optimistic:
|
||||||
|
self._position = 100
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_close_cover(self, **kwargs):
|
def async_close_cover(self, **kwargs):
|
||||||
"""Move the cover down."""
|
"""Move the cover down."""
|
||||||
if self._close_script:
|
if self._close_script:
|
||||||
self.hass.async_add_job(self._close_script.async_run())
|
yield from self._close_script.async_run()
|
||||||
elif self._position_script:
|
elif self._position_script:
|
||||||
self.hass.async_add_job(self._position_script.async_run(
|
yield from self._position_script.async_run({"position": 0})
|
||||||
{"position": 0}))
|
if self._optimistic:
|
||||||
|
self._position = 0
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_stop_cover(self, **kwargs):
|
def async_stop_cover(self, **kwargs):
|
||||||
@ -284,29 +294,35 @@ class CoverTemplate(CoverDevice):
|
|||||||
def async_set_cover_position(self, **kwargs):
|
def async_set_cover_position(self, **kwargs):
|
||||||
"""Set cover position."""
|
"""Set cover position."""
|
||||||
self._position = kwargs[ATTR_POSITION]
|
self._position = kwargs[ATTR_POSITION]
|
||||||
self.hass.async_add_job(self._position_script.async_run(
|
yield from self._position_script.async_run(
|
||||||
{"position": self._position}))
|
{"position": self._position})
|
||||||
|
if self._optimistic:
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_open_cover_tilt(self, **kwargs):
|
def async_open_cover_tilt(self, **kwargs):
|
||||||
"""Tilt the cover open."""
|
"""Tilt the cover open."""
|
||||||
self._tilt_value = 100
|
self._tilt_value = 100
|
||||||
self.hass.async_add_job(self._tilt_script.async_run(
|
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
|
||||||
{"tilt": self._tilt_value}))
|
if self._tilt_optimistic:
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_close_cover_tilt(self, **kwargs):
|
def async_close_cover_tilt(self, **kwargs):
|
||||||
"""Tilt the cover closed."""
|
"""Tilt the cover closed."""
|
||||||
self._tilt_value = 0
|
self._tilt_value = 0
|
||||||
self.hass.async_add_job(self._tilt_script.async_run(
|
yield from self._tilt_script.async_run(
|
||||||
{"tilt": self._tilt_value}))
|
{"tilt": self._tilt_value})
|
||||||
|
if self._tilt_optimistic:
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_set_cover_tilt_position(self, **kwargs):
|
def async_set_cover_tilt_position(self, **kwargs):
|
||||||
"""Move the cover tilt to a specific position."""
|
"""Move the cover tilt to a specific position."""
|
||||||
self._tilt_value = kwargs[ATTR_TILT_POSITION]
|
self._tilt_value = kwargs[ATTR_TILT_POSITION]
|
||||||
self.hass.async_add_job(self._tilt_script.async_run(
|
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
|
||||||
{"tilt": self._tilt_value}))
|
if self._tilt_optimistic:
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
def async_update(self):
|
||||||
|
@ -21,7 +21,7 @@ class TestTemplateCover(unittest.TestCase):
|
|||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
def setup_method(self, method):
|
def setup_method(self, method):
|
||||||
"""Setup things to be run when tests are started."""
|
"""Initialize services when tests are started."""
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = get_test_home_assistant()
|
||||||
self.calls = []
|
self.calls = []
|
||||||
|
|
||||||
@ -254,32 +254,6 @@ class TestTemplateCover(unittest.TestCase):
|
|||||||
|
|
||||||
assert self.hass.states.all() == []
|
assert self.hass.states.all() == []
|
||||||
|
|
||||||
def test_template_position_or_value(self):
|
|
||||||
"""Test that at least one of value or position template is used."""
|
|
||||||
with assert_setup_component(1, 'cover'):
|
|
||||||
assert setup.setup_component(self.hass, 'cover', {
|
|
||||||
'cover': {
|
|
||||||
'platform': 'template',
|
|
||||||
'covers': {
|
|
||||||
'test_template_cover': {
|
|
||||||
'open_cover': {
|
|
||||||
'service': 'cover.open_cover',
|
|
||||||
'entity_id': 'cover.test_state'
|
|
||||||
},
|
|
||||||
'close_cover': {
|
|
||||||
'service': 'cover.close_cover',
|
|
||||||
'entity_id': 'cover.test_state'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.hass.start()
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert self.hass.states.all() == []
|
|
||||||
|
|
||||||
def test_template_open_or_position(self):
|
def test_template_open_or_position(self):
|
||||||
"""Test that at least one of open_cover or set_position is used."""
|
"""Test that at least one of open_cover or set_position is used."""
|
||||||
with assert_setup_component(1, 'cover'):
|
with assert_setup_component(1, 'cover'):
|
||||||
@ -590,6 +564,85 @@ class TestTemplateCover(unittest.TestCase):
|
|||||||
|
|
||||||
assert len(self.calls) == 1
|
assert len(self.calls) == 1
|
||||||
|
|
||||||
|
def test_set_position_optimistic(self):
|
||||||
|
"""Test optimistic position mode."""
|
||||||
|
with assert_setup_component(1, 'cover'):
|
||||||
|
assert setup.setup_component(self.hass, 'cover', {
|
||||||
|
'cover': {
|
||||||
|
'platform': 'template',
|
||||||
|
'covers': {
|
||||||
|
'test_template_cover': {
|
||||||
|
'set_cover_position': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.hass.start()
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.attributes.get('current_position') is None
|
||||||
|
|
||||||
|
cover.set_cover_position(self.hass, 42,
|
||||||
|
'cover.test_template_cover')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.attributes.get('current_position') == 42.0
|
||||||
|
|
||||||
|
cover.close_cover(self.hass, 'cover.test_template_cover')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.state == STATE_CLOSED
|
||||||
|
|
||||||
|
cover.open_cover(self.hass, 'cover.test_template_cover')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.state == STATE_OPEN
|
||||||
|
|
||||||
|
def test_set_tilt_position_optimistic(self):
|
||||||
|
"""Test the optimistic tilt_position mode."""
|
||||||
|
with assert_setup_component(1, 'cover'):
|
||||||
|
assert setup.setup_component(self.hass, 'cover', {
|
||||||
|
'cover': {
|
||||||
|
'platform': 'template',
|
||||||
|
'covers': {
|
||||||
|
'test_template_cover': {
|
||||||
|
'position_template':
|
||||||
|
"{{ 100 }}",
|
||||||
|
'set_cover_position': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
},
|
||||||
|
'set_cover_tilt_position': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.hass.start()
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.attributes.get('current_tilt_position') is None
|
||||||
|
|
||||||
|
cover.set_cover_tilt_position(self.hass, 42,
|
||||||
|
'cover.test_template_cover')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.attributes.get('current_tilt_position') == 42.0
|
||||||
|
|
||||||
|
cover.close_cover_tilt(self.hass, 'cover.test_template_cover')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.attributes.get('current_tilt_position') == 0.0
|
||||||
|
|
||||||
|
cover.open_cover_tilt(self.hass, 'cover.test_template_cover')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
state = self.hass.states.get('cover.test_template_cover')
|
||||||
|
assert state.attributes.get('current_tilt_position') == 100.0
|
||||||
|
|
||||||
def test_icon_template(self):
|
def test_icon_template(self):
|
||||||
"""Test icon template."""
|
"""Test icon template."""
|
||||||
with assert_setup_component(1, 'cover'):
|
with assert_setup_component(1, 'cover'):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user