Migrate fan component to async. (#5723)

* Migrate fan component to async.

* Fix lint
This commit is contained in:
Pascal Vizeli 2017-02-02 21:07:00 +01:00 committed by Paulus Schoutsen
parent 574384f446
commit 2fc3dfff67
3 changed files with 107 additions and 60 deletions

View File

@ -4,7 +4,9 @@ Provides functionality to interact with fans.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan/ https://home-assistant.io/components/fan/
""" """
import asyncio
from datetime import timedelta from datetime import timedelta
import functools as ft
import logging import logging
import os import os
@ -24,6 +26,8 @@ import homeassistant.helpers.config_validation as cv
DOMAIN = 'fan' DOMAIN = 'fan'
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)
_LOGGER = logging.getLogger(__name__)
GROUP_NAME_ALL_FANS = 'all fans' GROUP_NAME_ALL_FANS = 'all fans'
ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS) ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS)
@ -88,7 +92,32 @@ FAN_SET_DIRECTION_SCHEMA = vol.Schema({
vol.Optional(ATTR_DIRECTION): cv.string vol.Optional(ATTR_DIRECTION): cv.string
}) # type: dict }) # type: dict
_LOGGER = logging.getLogger(__name__) SERVICE_TO_METHOD = {
SERVICE_TURN_ON: {
'method': 'async_turn_on',
'schema': FAN_TURN_ON_SCHEMA,
},
SERVICE_TURN_OFF: {
'method': 'async_turn_off',
'schema': FAN_TURN_OFF_SCHEMA,
},
SERVICE_TOGGLE: {
'method': 'async_toggle',
'schema': FAN_TOGGLE_SCHEMA,
},
SERVICE_SET_SPEED: {
'method': 'async_set_speed',
'schema': FAN_SET_SPEED_SCHEMA,
},
SERVICE_OSCILLATE: {
'method': 'async_oscillate',
'schema': FAN_OSCILLATE_SCHEMA,
},
SERVICE_SET_DIRECTION: {
'method': 'async_set_direction',
'schema': FAN_SET_DIRECTION_SCHEMA,
},
}
def is_on(hass, entity_id: str=None) -> bool: def is_on(hass, entity_id: str=None) -> bool:
@ -164,60 +193,53 @@ def set_direction(hass, entity_id: str=None, direction: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data) hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data)
def setup(hass, config: dict) -> None: @asyncio.coroutine
def async_setup(hass, config: dict):
"""Expose fan control via statemachine and services.""" """Expose fan control via statemachine and services."""
component = EntityComponent( component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_FANS) _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_FANS)
component.setup(config)
def handle_fan_service(service: str) -> None: yield from component.async_setup(config)
@asyncio.coroutine
def async_handle_fan_service(service):
"""Hande service call for fans.""" """Hande service call for fans."""
# Get the validated data method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy() params = service.data.copy()
# Convert the entity ids to valid fan ids # Convert the entity ids to valid fan ids
target_fans = component.extract_from_service(service) target_fans = component.async_extract_from_service(service)
params.pop(ATTR_ENTITY_ID, None) params.pop(ATTR_ENTITY_ID, None)
service_fun = None
for service_def in [SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_SET_SPEED, SERVICE_OSCILLATE,
SERVICE_SET_DIRECTION]:
if service_def == service.service:
service_fun = service_def
break
if service_fun:
for fan in target_fans: for fan in target_fans:
getattr(fan, service_fun)(**params) yield from getattr(fan, method['method'])(**params)
update_tasks = []
for fan in target_fans: for fan in target_fans:
if fan.should_poll: if not fan.should_poll:
fan.update_ha_state(True) continue
return
update_coro = hass.loop.create_task(
fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
# Listen for fan service calls. # Listen for fan service calls.
descriptions = load_yaml_config_file( descriptions = yield from hass.loop.run_in_executor(
os.path.join(os.path.dirname(__file__), 'services.yaml')) None, load_yaml_config_file, os.path.join(
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_fan_service, os.path.dirname(__file__), 'services.yaml'))
descriptions.get(SERVICE_TURN_ON),
schema=FAN_TURN_ON_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_fan_service, for service_name in SERVICE_TO_METHOD:
descriptions.get(SERVICE_TURN_OFF), schema = SERVICE_TO_METHOD[service_name].get('schema')
schema=FAN_TURN_OFF_SCHEMA) hass.services.async_register(
DOMAIN, service_name, async_handle_fan_service,
hass.services.register(DOMAIN, SERVICE_SET_SPEED, handle_fan_service, descriptions.get(service_name), schema=schema)
descriptions.get(SERVICE_SET_SPEED),
schema=FAN_SET_SPEED_SCHEMA)
hass.services.register(DOMAIN, SERVICE_OSCILLATE, handle_fan_service,
descriptions.get(SERVICE_OSCILLATE),
schema=FAN_OSCILLATE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_SET_DIRECTION, handle_fan_service,
descriptions.get(SERVICE_SET_DIRECTION),
schema=FAN_SET_DIRECTION_SCHEMA)
return True return True
@ -225,34 +247,57 @@ def setup(hass, config: dict) -> None:
class FanEntity(ToggleEntity): class FanEntity(ToggleEntity):
"""Representation of a fan.""" """Representation of a fan."""
# pylint: disable=no-self-use
def set_speed(self: ToggleEntity, speed: str) -> None: def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan.""" """Set the speed of the fan."""
if speed is SPEED_OFF:
self.turn_off()
return
raise NotImplementedError() raise NotImplementedError()
def async_set_speed(self: ToggleEntity, speed: str):
"""Set the speed of the fan.
This method must be run in the event loop and returns a coroutine.
"""
if speed is SPEED_OFF:
return self.async_turn_off()
return self.hass.loop.run_in_executor(None, self.set_speed, speed)
def set_direction(self: ToggleEntity, direction: str) -> None: def set_direction(self: ToggleEntity, direction: str) -> None:
"""Set the direction of the fan.""" """Set the direction of the fan."""
raise NotImplementedError() raise NotImplementedError()
def async_set_direction(self: ToggleEntity, direction: str):
"""Set the direction of the fan.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.set_direction, direction)
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None: def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn on the fan.""" """Turn on the fan."""
if speed is SPEED_OFF:
self.turn_off()
return
raise NotImplementedError() raise NotImplementedError()
def turn_off(self: ToggleEntity, **kwargs) -> None: def async_turn_on(self: ToggleEntity, speed: str=None, **kwargs):
"""Turn off the fan.""" """Turn on the fan.
raise NotImplementedError()
This method must be run in the event loop and returns a coroutine.
"""
if speed is SPEED_OFF:
return self.async_turn_off()
return self.hass.loop.run_in_executor(
None, ft.partial(self.turn_on, speed, **kwargs))
def oscillate(self: ToggleEntity, oscillating: bool) -> None: def oscillate(self: ToggleEntity, oscillating: bool) -> None:
"""Oscillate the fan.""" """Oscillate the fan."""
pass pass
def async_oscillate(self: ToggleEntity, oscillating: bool):
"""Oscillate the fan.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.oscillate, oscillating)
@property @property
def is_on(self): def is_on(self):
"""Return true if the entity is on.""" """Return true if the entity is on."""

View File

@ -56,8 +56,10 @@ class DemoFan(FanEntity):
"""Get the list of available speeds.""" """Get the list of available speeds."""
return [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] return [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def turn_on(self, speed: str=SPEED_MEDIUM) -> None: def turn_on(self, speed: str=None) -> None:
"""Turn on the entity.""" """Turn on the entity."""
if speed is None:
speed = SPEED_MEDIUM
self.set_speed(speed) self.set_speed(speed)
def turn_off(self) -> None: def turn_off(self) -> None:
@ -68,17 +70,17 @@ class DemoFan(FanEntity):
def set_speed(self, speed: str) -> None: def set_speed(self, speed: str) -> None:
"""Set the speed of the fan.""" """Set the speed of the fan."""
self._speed = speed self._speed = speed
self.update_ha_state() self.schedule_update_ha_state()
def set_direction(self, direction: str) -> None: def set_direction(self, direction: str) -> None:
"""Set the direction of the fan.""" """Set the direction of the fan."""
self.direction = direction self.direction = direction
self.update_ha_state() self.schedule_update_ha_state()
def oscillate(self, oscillating: bool) -> None: def oscillate(self, oscillating: bool) -> None:
"""Set oscillation.""" """Set oscillation."""
self.oscillating = oscillating self.oscillating = oscillating
self.update_ha_state() self.schedule_update_ha_state()
@property @property
def current_direction(self) -> str: def current_direction(self) -> str:

View File

@ -150,7 +150,7 @@ class MqttFan(FanEntity):
elif payload == self._payload[STATE_OFF]: elif payload == self._payload[STATE_OFF]:
self._state = False self._state = False
self.update_ha_state() self.schedule_update_ha_state()
if self._topic[CONF_STATE_TOPIC] is not None: if self._topic[CONF_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC], mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC],
@ -165,7 +165,7 @@ class MqttFan(FanEntity):
self._speed = SPEED_MEDIUM self._speed = SPEED_MEDIUM
elif payload == self._payload[SPEED_HIGH]: elif payload == self._payload[SPEED_HIGH]:
self._speed = SPEED_HIGH self._speed = SPEED_HIGH
self.update_ha_state() self.schedule_update_ha_state()
if self._topic[CONF_SPEED_STATE_TOPIC] is not None: if self._topic[CONF_SPEED_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_SPEED_STATE_TOPIC], mqtt.subscribe(self._hass, self._topic[CONF_SPEED_STATE_TOPIC],
@ -183,7 +183,7 @@ class MqttFan(FanEntity):
self._oscillation = True self._oscillation = True
elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]: elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]:
self._oscillation = False self._oscillation = False
self.update_ha_state() self.schedule_update_ha_state()
if self._topic[CONF_OSCILLATION_STATE_TOPIC] is not None: if self._topic[CONF_OSCILLATION_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, mqtt.subscribe(self._hass,
@ -262,7 +262,7 @@ class MqttFan(FanEntity):
self._speed = speed self._speed = speed
mqtt.publish(self._hass, self._topic[CONF_SPEED_COMMAND_TOPIC], mqtt.publish(self._hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
mqtt_payload, self._qos, self._retain) mqtt_payload, self._qos, self._retain)
self.update_ha_state() self.schedule_update_ha_state()
def oscillate(self, oscillating: bool) -> None: def oscillate(self, oscillating: bool) -> None:
"""Set oscillation.""" """Set oscillation."""
@ -274,4 +274,4 @@ class MqttFan(FanEntity):
mqtt.publish(self._hass, mqtt.publish(self._hass,
self._topic[CONF_OSCILLATION_COMMAND_TOPIC], self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
payload, self._qos, self._retain) payload, self._qos, self._retain)
self.update_ha_state() self.schedule_update_ha_state()