parent
574384f446
commit
2fc3dfff67
|
@ -4,7 +4,9 @@ Provides functionality to interact with fans.
|
|||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/fan/
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import functools as ft
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
@ -24,6 +26,8 @@ import homeassistant.helpers.config_validation as cv
|
|||
DOMAIN = 'fan'
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
GROUP_NAME_ALL_FANS = '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
|
||||
}) # 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:
|
||||
|
@ -164,60 +193,53 @@ def set_direction(hass, entity_id: str=None, direction: str=None) -> None:
|
|||
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."""
|
||||
component = EntityComponent(
|
||||
_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."""
|
||||
# Get the validated data
|
||||
method = SERVICE_TO_METHOD.get(service.service)
|
||||
params = service.data.copy()
|
||||
|
||||
# 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)
|
||||
|
||||
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
|
||||
for fan in target_fans:
|
||||
yield from getattr(fan, method['method'])(**params)
|
||||
|
||||
if service_fun:
|
||||
for fan in target_fans:
|
||||
getattr(fan, service_fun)(**params)
|
||||
update_tasks = []
|
||||
|
||||
for fan in target_fans:
|
||||
if fan.should_poll:
|
||||
fan.update_ha_state(True)
|
||||
return
|
||||
for fan in target_fans:
|
||||
if not fan.should_poll:
|
||||
continue
|
||||
|
||||
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.
|
||||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_fan_service,
|
||||
descriptions.get(SERVICE_TURN_ON),
|
||||
schema=FAN_TURN_ON_SCHEMA)
|
||||
descriptions = yield from hass.loop.run_in_executor(
|
||||
None, load_yaml_config_file, os.path.join(
|
||||
os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_fan_service,
|
||||
descriptions.get(SERVICE_TURN_OFF),
|
||||
schema=FAN_TURN_OFF_SCHEMA)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_SET_SPEED, handle_fan_service,
|
||||
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)
|
||||
for service_name in SERVICE_TO_METHOD:
|
||||
schema = SERVICE_TO_METHOD[service_name].get('schema')
|
||||
hass.services.async_register(
|
||||
DOMAIN, service_name, async_handle_fan_service,
|
||||
descriptions.get(service_name), schema=schema)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -225,34 +247,57 @@ def setup(hass, config: dict) -> None:
|
|||
class FanEntity(ToggleEntity):
|
||||
"""Representation of a fan."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
def set_speed(self: ToggleEntity, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
if speed is SPEED_OFF:
|
||||
self.turn_off()
|
||||
return
|
||||
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:
|
||||
"""Set the direction of the fan."""
|
||||
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:
|
||||
"""Turn on the fan."""
|
||||
if speed is SPEED_OFF:
|
||||
self.turn_off()
|
||||
return
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_off(self: ToggleEntity, **kwargs) -> None:
|
||||
"""Turn off the fan."""
|
||||
raise NotImplementedError()
|
||||
def async_turn_on(self: ToggleEntity, speed: str=None, **kwargs):
|
||||
"""Turn on 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, ft.partial(self.turn_on, speed, **kwargs))
|
||||
|
||||
def oscillate(self: ToggleEntity, oscillating: bool) -> None:
|
||||
"""Oscillate the fan."""
|
||||
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
|
||||
def is_on(self):
|
||||
"""Return true if the entity is on."""
|
||||
|
|
|
@ -56,8 +56,10 @@ class DemoFan(FanEntity):
|
|||
"""Get the list of available speeds."""
|
||||
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."""
|
||||
if speed is None:
|
||||
speed = SPEED_MEDIUM
|
||||
self.set_speed(speed)
|
||||
|
||||
def turn_off(self) -> None:
|
||||
|
@ -68,17 +70,17 @@ class DemoFan(FanEntity):
|
|||
def set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
self._speed = speed
|
||||
self.update_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
self.direction = direction
|
||||
self.update_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation."""
|
||||
self.oscillating = oscillating
|
||||
self.update_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def current_direction(self) -> str:
|
||||
|
|
|
@ -150,7 +150,7 @@ class MqttFan(FanEntity):
|
|||
elif payload == self._payload[STATE_OFF]:
|
||||
self._state = False
|
||||
|
||||
self.update_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._topic[CONF_STATE_TOPIC] is not None:
|
||||
mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC],
|
||||
|
@ -165,7 +165,7 @@ class MqttFan(FanEntity):
|
|||
self._speed = SPEED_MEDIUM
|
||||
elif payload == self._payload[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:
|
||||
mqtt.subscribe(self._hass, self._topic[CONF_SPEED_STATE_TOPIC],
|
||||
|
@ -183,7 +183,7 @@ class MqttFan(FanEntity):
|
|||
self._oscillation = True
|
||||
elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]:
|
||||
self._oscillation = False
|
||||
self.update_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._topic[CONF_OSCILLATION_STATE_TOPIC] is not None:
|
||||
mqtt.subscribe(self._hass,
|
||||
|
@ -262,7 +262,7 @@ class MqttFan(FanEntity):
|
|||
self._speed = speed
|
||||
mqtt.publish(self._hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
|
||||
mqtt_payload, self._qos, self._retain)
|
||||
self.update_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation."""
|
||||
|
@ -274,4 +274,4 @@ class MqttFan(FanEntity):
|
|||
mqtt.publish(self._hass,
|
||||
self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
|
||||
payload, self._qos, self._retain)
|
||||
self.update_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
|
Loading…
Reference in New Issue