Skip to content

Template light#7657

Merged
emlove merged 24 commits into
home-assistant:devfrom
cribbstechnologies:template-light
May 24, 2017
Merged

Template light#7657
emlove merged 24 commits into
home-assistant:devfrom
cribbstechnologies:template-light

Conversation

@cribbstechnologies
Copy link
Copy Markdown
Contributor

@cribbstechnologies cribbstechnologies commented May 19, 2017

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):

light:
  - platform: template
    lights:
      temp_switch:
        value_template: {{1 == 1}}
        turn_on: 
          service: light.turn_on
          entity_id: light.other_light
        turn_off:
          service: light.turn_off
          entity_id: light.other_light
        set_level:
           service: light.turn_on
           data_template: {{brightness}}
        level_template: {{42}}

Checklist:

If user exposed functionality or configuration variables are added/changed:

If the code does not interact with devices:

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass
  • Tests have been added to verify that the new code works.

@cribbstechnologies
Copy link
Copy Markdown
Contributor Author

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.
@cribbstechnologies
Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Contributor

@emlove emlove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefix these with CONF_

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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're actually setting this in the constructor below, so we can drop it here.

self._state = False

@asyncio.coroutine
def async_update(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include should_poll False, as done here.

self._state = None

if self._level_template is not None:
brightness = self._level_template.async_render()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing whitespace

except TemplateError as ex:
_LOGGER.error(ex)
self._state = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line contains whitespace

Copy link
Copy Markdown
Contributor

@emlove emlove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entity_ids = [] can be removed.


if state_template is not None:
temp_ids = state_template.extract_entities()
if str(temp_ids) != '*':
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition should be above line 163

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())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent this so it only runs if there is no template.

Copy link
Copy Markdown
Contributor

@emlove emlove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@emlove emlove merged commit ef4ef2d into home-assistant:dev May 24, 2017
@emlove
Copy link
Copy Markdown
Contributor

emlove commented May 24, 2017

Thanks for your contribution! 🎉

@cribbstechnologies cribbstechnologies deleted the template-light branch May 30, 2017 15:54
@balloob balloob mentioned this pull request Jun 2, 2017
@home-assistant home-assistant locked and limited conversation to collaborators Sep 4, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants