Template light#7657
Conversation
|
I could not figure out how to undo the auto-add and subsequent commit to the polymer submodule, I tried to straighten it out and I may have actually made it worse. I would've just cut a new branch and cherry picked but it was part of a commit that contained useful information |
This reverts commit 852c87f.
|
I fixed it! This should be good to go now. @Landrash requested I write a blog post to accompany this but I haven't had a chance to do that yet. |
emlove
left a comment
There was a problem hiding this comment.
Thanks for the PR. This will be a nice addition! Looks good at the core, just some small stuff here.
| _VALID_STATES = [STATE_ON, STATE_OFF, 'true', 'false'] | ||
|
|
||
| CONF_LIGHTS = 'lights' | ||
| ON_ACTION = 'turn_on' |
| vol.Optional(CONF_VALUE_TEMPLATE, default=None): cv.template, | ||
| vol.Optional(LEVEL_ACTION, default=None): cv.SCRIPT_SCHEMA, | ||
| vol.Optional(LEVEL_TEMPLATE, default=None): cv.template, | ||
| vol.Optional(ATTR_FRIENDLY_NAME, default=None): cv.string, |
There was a problem hiding this comment.
Import CONF_FRIENDLY_NAME for this instead.
| vol.Optional(LEVEL_ACTION, default=None): cv.SCRIPT_SCHEMA, | ||
| vol.Optional(LEVEL_TEMPLATE, default=None): cv.template, | ||
| vol.Optional(ATTR_FRIENDLY_NAME, default=None): cv.string, | ||
| vol.Optional(ATTR_ENTITY_ID): cv.entity_ids |
There was a problem hiding this comment.
Template switch actually also has this wrong, but this should be CONF_ENTITY_ID. They end up the same functionally, but these should technically only be used within their proper domain to be correct.
| off_action = device_config[OFF_ACTION] | ||
| level_action = device_config[LEVEL_ACTION] | ||
| level_template = device_config[LEVEL_TEMPLATE] | ||
| entity_ids = [] |
There was a problem hiding this comment.
If no templates are defined, this list still needs to include the configured entity IDs.
|
|
||
| if state_template is not None: | ||
| entity_ids = (device_config.get(ATTR_ENTITY_ID) or | ||
| state_template.extract_entities()) |
There was a problem hiding this comment.
If no entity ID list is defined, entity_ids needs to be a union of all both template extract_entities() results.
Given this and the comment above, I'd generate the list of entity_ids from the templates, and then set
entity_ids = device_config.get(CONF_ENTITY_ID, template_entity_ids)| if state_template is not None: | ||
| entity_ids = (device_config.get(ATTR_ENTITY_ID) or | ||
| state_template.extract_entities()) | ||
| state_template.hass = hass |
There was a problem hiding this comment.
We're actually setting this in the constructor below, so we can drop it here.
| self._state = False | ||
|
|
||
| @asyncio.coroutine | ||
| def async_update(self): |
| self._state = None | ||
|
|
||
| if self._level_template is not None: | ||
| brightness = self._level_template.async_render() |
There was a problem hiding this comment.
We need error checking here similar to above. If it doesn't match an integer 0-255, we should log a similar error and set self._brightness = None.
|
|
||
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| self._state = None |
There was a problem hiding this comment.
Since there are two renders here, wrap each one separately, and set the appropriate state to None. It's safe to have this try only wrap the lines with async_render().
| @asyncio.coroutine | ||
| def async_turn_on(self, **kwargs): | ||
| """Turn the light on.""" | ||
| self._state = True |
There was a problem hiding this comment.
Only optimistically set the status if there is no template defined. Same for brightness and turn_off below. If the status is optimistically updated, run self.hass.async_add_job(self.async_update_ha_state()) to update the state. Note that we don't need to pass True, since async_update isn't necessary.
| brightness = self._level_template.async_render() | ||
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| self._state = None |
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| self._state = None | ||
|
|
emlove
left a comment
There was a problem hiding this comment.
Next Round.
It would also be good to add some tests for the optimistic state logic, as it almost slipped through only halfway done.
| level_template = device_config[CONF_LEVEL_TEMPLATE] | ||
|
|
||
| template_entity_ids = [] | ||
| entity_ids = [] |
There was a problem hiding this comment.
entity_ids = [] can be removed.
|
|
||
| if state_template is not None: | ||
| temp_ids = state_template.extract_entities() | ||
| if str(temp_ids) != '*': |
There was a problem hiding this comment.
Instead, check if temp_ids != MATCH_ALL:, where MATCH_ALL is imported from homeassistant.const.
| if state_template is not None: | ||
| temp_ids = state_template.extract_entities() | ||
| if str(temp_ids) != '*': | ||
| template_entity_ids = list(set(template_entity_ids) | |
There was a problem hiding this comment.
Looking at this, now, we can actually make this even simpler. Let's initialize template_entity_ids, as an empty set, then here we can just write template_entity_ids |= set(temp_ids). We can actually pass the set right to async_track_state_change without casting it back. Also though, since we're now filtering out MATCH_ALL, we need to catch the scenario where none of the templates have matches, so at the end you'll need to check
if not template_entity_ids:
template_entity_ids = MATCH_ALL| self._state = False | ||
| self._brightness = None | ||
| self._entities = entity_ids | ||
| self._entities.append(self.entity_id) |
There was a problem hiding this comment.
Why was this added? We don't want to recursively update when this entity is changed.
| # Determine need to evaluate template before returning. | ||
| yield_for_render = (self._template is not None or | ||
| self._level_template is not None) | ||
| self.hass.async_add_job( |
There was a problem hiding this comment.
I think I understand the confusion now. This can always be async_update_ha_state(True), since if we don't change the internal attributes here, updating the ha state will never do anything.
But, you're right that we shouldn't ignore it either. What we should do is if self._template and self._level_template are both None, we shouldn't even schedule these updates here. There's no reason to listen for any state changes if there are no templates that need to be re-rendered.
| # Determine need to evaluate template before returning. | ||
| yield_for_render = (self._template is not None or | ||
| self._level_template is not None) | ||
| self.hass.async_add_job( |
There was a problem hiding this comment.
I think here we should just run it with async_update_ha_state(True). This one is only run once, so it's not a huge deal. The big concern was the updates in the service methods. Sorry for the miscommunication.
| """Handle target device state changes.""" | ||
| if ( | ||
| self._template is not None or | ||
| self._level_template is not None): |
| self.hass.async_add_job(self._off_script.async_run()) | ||
| if self._template is None: | ||
| self._state = False | ||
| self.hass.async_add_job(self.async_update_ha_state()) |
There was a problem hiding this comment.
Indent this so it only runs if there is no template.
|
Thanks for your contribution! 🎉 |
Description:
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#2661
Example entry for
configuration.yaml(if applicable):Checklist:
If user exposed functionality or configuration variables are added/changed:
If the code does not interact with devices:
toxrun successfully. Your PR cannot be merged unless tests pass