Skip to content

Issue/add template fans#12027

Merged
balloob merged 18 commits intohome-assistant:devfrom
giangvo:issue/add-template-fans
May 2, 2018
Merged

Issue/add template fans#12027
balloob merged 18 commits intohome-assistant:devfrom
giangvo:issue/add-template-fans

Conversation

@giangvo
Copy link
Copy Markdown
Contributor

@giangvo giangvo commented Jan 29, 2018

Description:

Add template fan

Example entry for configuration.yaml:

fan:
  - platform: template
    fans:
      bedroom_fan:
        friendly_name: "Bedroom fan" #Optional
        value_template: "{{ states('input_boolean.state') }}" #Required
        speed_template: "{{ states('input_select.speed') }}" #Optional
        oscillating_template: "{{ states('input_select.osc') }}" #Optional
        turn_on: #Required
          service: script.fan_on
        turn_off: #Required
          service: script.fan_off
        set_speed: #Optional
          service: script.fan_speed
          data_template:
            speed: "{{ speed }}"
        set_oscillating: #Optional
          service: script.fan_oscillating
          data_template:
            oscillating: "{{ oscillating }}"
        speeds: #Optional
          - '1'
          - '2'
          - '3'

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.

@todschmidt
Copy link
Copy Markdown
Contributor

Nice work. Can you add documentation for it as well?

@giangvo
Copy link
Copy Markdown
Contributor Author

giangvo commented Jan 30, 2018

Create PR for adding document here: home-assistant/home-assistant.io#4551

@giangvo
Copy link
Copy Markdown
Contributor Author

giangvo commented Feb 1, 2018

Could someone review this?
Thanks

@thomasdelaet
Copy link
Copy Markdown
Contributor

I am using the previous version of this component and it works perfectly, so I assume this one does too.

Thanks a lot for resubmitting this and I hope it gets merged soon.

@todschmidt
Copy link
Copy Markdown
Contributor

Just a couple small changes and I'll merge this, thanks for adding it.

CONF_OSCILLATING_TEMPLATE = 'oscillating_template'
CONF_ON_ACTION = 'turn_on'
CONF_OFF_ACTION = 'turn_off'
CONF_SET_SPEED_ACTION = 'set_speed'
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 don't think you should add _ACTION here, just CONF_SET_SPEED, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am using same naming convention as in light/template so prefer to keep them if u don't might

CONF_SPEED_LIST = 'speeds'
CONF_SPEED_TEMPLATE = 'speed_template'
CONF_OSCILLATING_TEMPLATE = 'oscillating_template'
CONF_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.

You should use the consts COMMAND_ON and COMMAND_OFF to match the other templates.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

light/template is also using same naming convention and some people might already use my PR so I prefer not to change this.

Comment thread tests/components/fan/test_template.py Outdated

from homeassistant.core import callback
from homeassistant import setup
import homeassistant.components as core
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.

Don't use core here, use component

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

@todschmidt
Copy link
Copy Markdown
Contributor

Sorry, I hadn't committed my comments so they weren't showing up for you. I think this is fine to merge if you can just clean up those couple things. Nice job adding all the tests up front.

@todschmidt
Copy link
Copy Markdown
Contributor

@balloob any objections to merging this?

@colindunn
Copy link
Copy Markdown
Contributor

Was using this as a custom component until merged, but it no longer works with the voluptuous changes for 0.64 just to let you know

_VALID_STATES = [STATE_ON, STATE_OFF, 'true', 'false']

FAN_SCHEMA = vol.Schema({
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove default=None from your schema. It's not allowed to have a default that is not a valid value.



@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We no longer use @asyncio.coroutine, easy to fix by removing the decorator and instead add async in front of func name: async def async_setup_platform(…. All yield from will have to be replaced with await

template_entity_ids = set()

temp_ids = state_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why stringify? you can compare a list with a string.

Also, if it is match_all, shouldn't you make sure that you match all entity ids? Now if 1 is match all but other 2 are not, you will not match all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Honestly, I copied it from light/template so I don't understand it 100%. It is working as light template so I think its good enough to be merged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove the call to str() and compare temp_ids directly to MATCH_ALL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I updated.

)

async_add_devices(fans)
return True
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove this. Platforms don't return a value.

@giangvo
Copy link
Copy Markdown
Contributor Author

giangvo commented Feb 28, 2018

@balloob , I updated the code as your comments (except the "stringify" one). Please review again.
Thanks!


if speed_template:
temp_ids = speed_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

remove str

template_entity_ids |= set(temp_ids)

if oscillating_template:
temp_ids = oscillating_template.extract_entities()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if one is MATCH_ALL, they should all be MATCH_ALL. This is not currently the case. If the oscillating_template returns MATCH_ALL, it will just use extracted entities of the other templates.


if speed_template:
temp_ids = speed_template.extract_entities()
if temp_ids != MATCH_ALL:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This still won't work. The moment you get a MATCH_ALL, you should just match on MATCH_ALL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry but I dont get it, could u give the correct code.
Thanks

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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


if speed_template:
temp_ids = speed_template.extract_entities()
if temp_ids != MATCH_ALL:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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


This method is a coroutine.
"""
self._state = False
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't we only set the state to False once the off script terminates? + If the off script raises an exception I would expect the state to remain in the previous state.

self.async_schedule_update_ha_state(True)

self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, template_fan_startup)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand why we should only call this on EVENT_HOMEASSISTANT_START. This code would break once we start dynamically loading/unloading entities. Maybe have a look at how it's done in the light group platform:

https://github.com/home-assistant/home-assistant/blob/ca5f4709564773c8cebd6ee40aa7cc1095b19105/homeassistant/components/light/group.py#L72-L82

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am doing the same as in light/template and sensor/template. Using the code in light group did not work. Fan is no changed when other entities changed


async def async_update(self):
"""Update the state from the template."""
_LOGGER.info('Updating fan %s', self._name)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems like quite the unnecessary logging statement. Even more it's info and not debug.

self._state = None

# Validate state
if state in _VALID_STATES:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think if the previous call produces a TemplateError, state will not be set, resulting in NameError: name 'state' is not defined.

self._state = None

# Update speed if 'speed_template' is configured
if self._speed_template:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

PEP8 recommend using is not None:

if self._speed_template is not None:

{ATTR_OSCILLATING: oscillating}
)
else:
_LOGGER.error(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This check is already handled by the service schema:

https://github.com/home-assistant/home-assistant/blob/678f284015a2c52f96a7687979cfd9f785e4527a/homeassistant/components/fan/__init__.py#L78-L81

Also if it were needed, it should definitely not go into this platform, but rather in the core fan definition.

async def async_turn_on(self, speed: str = None) -> None:
"""Turn on the fan.

This method is a coroutine.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think by now (especially with async def) we don't need to plaster "This method is a coroutine." everywhere where it's abundantly clear from the context.

}
})

self.hass.start()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need to call self.hass.start() in any of these test cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removing them failed the tests.


with assert_setup_component(2, 'input_select'):
assert setup.setup_component(self.hass, 'input_select', {
'input_select': {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean lots of tests do this, but I don't think tests from one platform should depend on another integration, it's just bad testing (and makes changing those other integrations a pain).

Why not just use the state machine manually?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I want them to be integration tests so we can sure that our scripts run successfully with real results. Please advise an example of state machine way of testing.

@thomasdelaet
Copy link
Copy Markdown
Contributor

Would be really great to have this one merged! I'm happy to create a new pull request covering the requested changes.

@giangvo
Copy link
Copy Markdown
Contributor Author

giangvo commented Apr 30, 2018

@OttoWinter could you please review. I updated the code as your comments.

  • 'on'/'off' are only accepted values for value_template
  • 'True/Falseare only accepted values forosc_template` (because it is defined in the schema)
FAN_OSCILLATE_SCHEMA = vol.Schema({
    vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Required(ATTR_OSCILLATING): cv.boolean
}) 

@balloob balloob dismissed OttoWinter’s stale review May 2, 2018 21:45

Concerns addressed

@balloob
Copy link
Copy Markdown
Member

balloob commented May 2, 2018

Looks good 🐬

@balloob balloob merged commit ef4498e into home-assistant:dev May 2, 2018
@thomasdelaet
Copy link
Copy Markdown
Contributor

Fantastic!

@balloob balloob mentioned this pull request May 11, 2018
@home-assistant home-assistant locked and limited conversation to collaborators Sep 5, 2018
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.

7 participants