hass-core/homeassistant/components/dyson/fan.py
etheralm 1d70005b01 Add sensor support for dyson 2018 models (#22578)
fix check for already created entities

remove hepa and carbon filter

add AQI attribute

initial commit

fix check for already created entities

remove hepa and carbon filter

add AQI attribute

add air quality component tests

fix method call tests

fix line lengths

fix pylint issues

fix docstrings

revert fan related changes

remove whitespace change

fix fan update state test

add for loop for platform initialization

add requested changes to aiq platform

change string concatenation to new style string formatting

update air quality tests

update air quality tests

refactor sensor component changes

fix pylint issues

fix debug string in the air quality component

replace failing tests for older devices

fix line length fan tests

remove dependencies const and move imports

move back imports to methods

remove whitespace from blank line
2019-04-30 02:24:05 +02:00

579 lines
19 KiB
Python

"""Support for Dyson Pure Cool link fan.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.dyson/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.fan import (
SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity,
SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH)
from homeassistant.const import ATTR_ENTITY_ID
from . import DYSON_DEVICES
_LOGGER = logging.getLogger(__name__)
ATTR_NIGHT_MODE = 'night_mode'
ATTR_AUTO_MODE = 'auto_mode'
ATTR_ANGLE_LOW = 'angle_low'
ATTR_ANGLE_HIGH = 'angle_high'
ATTR_FLOW_DIRECTION_FRONT = 'flow_direction_front'
ATTR_TIMER = 'timer'
ATTR_HEPA_FILTER = 'hepa_filter'
ATTR_CARBON_FILTER = 'carbon_filter'
ATTR_DYSON_SPEED = 'dyson_speed'
ATTR_DYSON_SPEED_LIST = 'dyson_speed_list'
DYSON_DOMAIN = 'dyson'
DYSON_FAN_DEVICES = 'dyson_fan_devices'
SERVICE_SET_NIGHT_MODE = 'set_night_mode'
SERVICE_SET_AUTO_MODE = 'set_auto_mode'
SERVICE_SET_ANGLE = 'set_angle'
SERVICE_SET_FLOW_DIRECTION_FRONT = 'set_flow_direction_front'
SERVICE_SET_TIMER = 'set_timer'
SERVICE_SET_DYSON_SPEED = 'set_speed'
DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_NIGHT_MODE): cv.boolean,
})
SET_AUTO_MODE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_AUTO_MODE): cv.boolean,
})
SET_ANGLE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_ANGLE_LOW): cv.positive_int,
vol.Required(ATTR_ANGLE_HIGH): cv.positive_int
})
SET_FLOW_DIRECTION_FRONT_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_FLOW_DIRECTION_FRONT): cv.boolean
})
SET_TIMER_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_TIMER): cv.positive_int
})
SET_DYSON_SPEED_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_DYSON_SPEED): cv.positive_int
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Dyson fan components."""
from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
from libpurecool.dyson_pure_cool import DysonPureCool
if discovery_info is None:
return
_LOGGER.debug("Creating new Dyson fans")
if DYSON_FAN_DEVICES not in hass.data:
hass.data[DYSON_FAN_DEVICES] = []
# Get Dyson Devices from parent component
has_purecool_devices = False
device_serials = [device.serial for device in hass.data[DYSON_FAN_DEVICES]]
for device in hass.data[DYSON_DEVICES]:
if device.serial not in device_serials:
if isinstance(device, DysonPureCool):
has_purecool_devices = True
dyson_entity = DysonPureCoolDevice(device)
hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
elif isinstance(device, DysonPureCoolLink):
dyson_entity = DysonPureCoolLinkDevice(hass, device)
hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
add_entities(hass.data[DYSON_FAN_DEVICES])
def service_handle(service):
"""Handle the Dyson services."""
entity_id = service.data[ATTR_ENTITY_ID]
fan_device = next((fan for fan in hass.data[DYSON_FAN_DEVICES] if
fan.entity_id == entity_id), None)
if fan_device is None:
_LOGGER.warning("Unable to find Dyson fan device %s",
str(entity_id))
return
if service.service == SERVICE_SET_NIGHT_MODE:
fan_device.set_night_mode(service.data[ATTR_NIGHT_MODE])
if service.service == SERVICE_SET_AUTO_MODE:
fan_device.set_auto_mode(service.data[ATTR_AUTO_MODE])
if service.service == SERVICE_SET_ANGLE:
fan_device.set_angle(service.data[ATTR_ANGLE_LOW],
service.data[ATTR_ANGLE_HIGH])
if service.service == SERVICE_SET_FLOW_DIRECTION_FRONT:
fan_device.set_flow_direction_front(
service.data[ATTR_FLOW_DIRECTION_FRONT])
if service.service == SERVICE_SET_TIMER:
fan_device.set_timer(service.data[ATTR_TIMER])
if service.service == SERVICE_SET_DYSON_SPEED:
fan_device.set_dyson_speed(service.data[ATTR_DYSON_SPEED])
# Register dyson service(s)
hass.services.register(
DYSON_DOMAIN, SERVICE_SET_NIGHT_MODE, service_handle,
schema=DYSON_SET_NIGHT_MODE_SCHEMA)
if has_purecool_devices:
hass.services.register(
DYSON_DOMAIN, SERVICE_SET_AUTO_MODE, service_handle,
schema=SET_AUTO_MODE_SCHEMA)
hass.services.register(
DYSON_DOMAIN, SERVICE_SET_ANGLE, service_handle,
schema=SET_ANGLE_SCHEMA)
hass.services.register(
DYSON_DOMAIN, SERVICE_SET_FLOW_DIRECTION_FRONT, service_handle,
schema=SET_FLOW_DIRECTION_FRONT_SCHEMA)
hass.services.register(
DYSON_DOMAIN, SERVICE_SET_TIMER, service_handle,
schema=SET_TIMER_SCHEMA)
hass.services.register(
DYSON_DOMAIN, SERVICE_SET_DYSON_SPEED, service_handle,
schema=SET_DYSON_SPEED_SCHEMA)
class DysonPureCoolLinkDevice(FanEntity):
"""Representation of a Dyson fan."""
def __init__(self, hass, device):
"""Initialize the fan."""
_LOGGER.debug("Creating device %s", device.name)
self.hass = hass
self._device = device
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_job(
self._device.add_message_listener, self.on_message)
def on_message(self, message):
"""Call when new messages received from the fan."""
from libpurecool.dyson_pure_state import DysonPureCoolState
if isinstance(message, DysonPureCoolState):
_LOGGER.debug("Message received for fan device %s: %s", self.name,
message)
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the display name of this fan."""
return self._device.name
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan. Never called ??."""
from libpurecool.const import FanSpeed, FanMode
_LOGGER.debug("Set fan speed to: %s", speed)
if speed == FanSpeed.FAN_SPEED_AUTO.value:
self._device.set_configuration(fan_mode=FanMode.AUTO)
else:
fan_speed = FanSpeed('{0:04d}'.format(int(speed)))
self._device.set_configuration(
fan_mode=FanMode.FAN, fan_speed=fan_speed)
def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the fan."""
from libpurecool.const import FanSpeed, FanMode
_LOGGER.debug("Turn on fan %s with speed %s", self.name, speed)
if speed:
if speed == FanSpeed.FAN_SPEED_AUTO.value:
self._device.set_configuration(fan_mode=FanMode.AUTO)
else:
fan_speed = FanSpeed('{0:04d}'.format(int(speed)))
self._device.set_configuration(
fan_mode=FanMode.FAN, fan_speed=fan_speed)
else:
# Speed not set, just turn on
self._device.set_configuration(fan_mode=FanMode.FAN)
def turn_off(self, **kwargs) -> None:
"""Turn off the fan."""
from libpurecool.const import FanMode
_LOGGER.debug("Turn off fan %s", self.name)
self._device.set_configuration(fan_mode=FanMode.OFF)
def oscillate(self, oscillating: bool) -> None:
"""Turn on/off oscillating."""
from libpurecool.const import Oscillation
_LOGGER.debug("Turn oscillation %s for device %s", oscillating,
self.name)
if oscillating:
self._device.set_configuration(
oscillation=Oscillation.OSCILLATION_ON)
else:
self._device.set_configuration(
oscillation=Oscillation.OSCILLATION_OFF)
@property
def oscillating(self):
"""Return the oscillation state."""
return self._device.state and self._device.state.oscillation == "ON"
@property
def is_on(self):
"""Return true if the entity is on."""
if self._device.state:
return self._device.state.fan_mode == "FAN"
return False
@property
def speed(self) -> str:
"""Return the current speed."""
from libpurecool.const import FanSpeed
if self._device.state:
if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
return self._device.state.speed
return int(self._device.state.speed)
return None
@property
def current_direction(self):
"""Return direction of the fan [forward, reverse]."""
return None
@property
def night_mode(self):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
def set_night_mode(self, night_mode: bool) -> None:
"""Turn fan in night mode."""
from libpurecool.const import NightMode
_LOGGER.debug("Set %s night mode %s", self.name, night_mode)
if night_mode:
self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_ON)
else:
self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_OFF)
@property
def auto_mode(self):
"""Return auto mode."""
return self._device.state.fan_mode == "AUTO"
def set_auto_mode(self, auto_mode: bool) -> None:
"""Turn fan in auto mode."""
from libpurecool.const import FanMode
_LOGGER.debug("Set %s auto mode %s", self.name, auto_mode)
if auto_mode:
self._device.set_configuration(fan_mode=FanMode.AUTO)
else:
self._device.set_configuration(fan_mode=FanMode.FAN)
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
from libpurecool.const import FanSpeed
supported_speeds = [
FanSpeed.FAN_SPEED_AUTO.value,
int(FanSpeed.FAN_SPEED_1.value),
int(FanSpeed.FAN_SPEED_2.value),
int(FanSpeed.FAN_SPEED_3.value),
int(FanSpeed.FAN_SPEED_4.value),
int(FanSpeed.FAN_SPEED_5.value),
int(FanSpeed.FAN_SPEED_6.value),
int(FanSpeed.FAN_SPEED_7.value),
int(FanSpeed.FAN_SPEED_8.value),
int(FanSpeed.FAN_SPEED_9.value),
int(FanSpeed.FAN_SPEED_10.value),
]
return supported_speeds
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED
@property
def device_state_attributes(self) -> dict:
"""Return optional state attributes."""
return {
ATTR_NIGHT_MODE: self.night_mode,
ATTR_AUTO_MODE: self.auto_mode
}
class DysonPureCoolDevice(FanEntity):
"""Representation of a Dyson Purecool (TP04/DP04) fan."""
def __init__(self, device):
"""Initialize the fan."""
self._device = device
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_executor_job(
self._device.add_message_listener, self.on_message)
def on_message(self, message):
"""Call when new messages received from the fan."""
from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State
if isinstance(message, DysonPureCoolV2State):
_LOGGER.debug("Message received for fan device %s: %s", self.name,
message)
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the display name of this fan."""
return self._device.name
def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the fan."""
_LOGGER.debug("Turn on fan %s", self.name)
if speed is not None:
self.set_speed(speed)
else:
self._device.turn_on()
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
from libpurecool.const import FanSpeed
if speed == SPEED_LOW:
self._device.set_fan_speed(FanSpeed.FAN_SPEED_4)
elif speed == SPEED_MEDIUM:
self._device.set_fan_speed(FanSpeed.FAN_SPEED_7)
elif speed == SPEED_HIGH:
self._device.set_fan_speed(FanSpeed.FAN_SPEED_10)
def turn_off(self, **kwargs):
"""Turn off the fan."""
_LOGGER.debug("Turn off fan %s", self.name)
self._device.turn_off()
def set_dyson_speed(self, speed: str = None) -> None:
"""Set the exact speed of the purecool fan."""
from libpurecool.const import FanSpeed
_LOGGER.debug("Set exact speed for fan %s", self.name)
fan_speed = FanSpeed('{0:04d}'.format(int(speed)))
self._device.set_fan_speed(fan_speed)
def oscillate(self, oscillating: bool) -> None:
"""Turn on/off oscillating."""
_LOGGER.debug("Turn oscillation %s for device %s", oscillating,
self.name)
if oscillating:
self._device.enable_oscillation()
else:
self._device.disable_oscillation()
def set_night_mode(self, night_mode: bool) -> None:
"""Turn on/off night mode."""
_LOGGER.debug("Turn night mode %s for device %s", night_mode,
self.name)
if night_mode:
self._device.enable_night_mode()
else:
self._device.disable_night_mode()
def set_auto_mode(self, auto_mode: bool) -> None:
"""Turn auto mode on/off."""
_LOGGER.debug("Turn auto mode %s for device %s", auto_mode,
self.name)
if auto_mode:
self._device.enable_auto_mode()
else:
self._device.disable_auto_mode()
def set_angle(self, angle_low: int, angle_high: int) -> None:
"""Set device angle."""
_LOGGER.debug("set low %s and high angle %s for device %s",
angle_low, angle_high, self.name)
self._device.enable_oscillation(angle_low, angle_high)
def set_flow_direction_front(self,
flow_direction_front: bool) -> None:
"""Set frontal airflow direction."""
_LOGGER.debug("Set frontal flow direction to %s for device %s",
flow_direction_front,
self.name)
if flow_direction_front:
self._device.enable_frontal_direction()
else:
self._device.disable_frontal_direction()
def set_timer(self, timer) -> None:
"""Set timer."""
_LOGGER.debug("Set timer to %s for device %s", timer,
self.name)
if timer == 0:
self._device.disable_sleep_timer()
else:
self._device.enable_sleep_timer(timer)
@property
def oscillating(self):
"""Return the oscillation state."""
return self._device.state and self._device.state.oscillation == "OION"
@property
def is_on(self):
"""Return true if the entity is on."""
if self._device.state:
return self._device.state.fan_power == "ON"
@property
def speed(self):
"""Return the current speed."""
from libpurecool.const import FanSpeed
speed_map = {FanSpeed.FAN_SPEED_1.value: SPEED_LOW,
FanSpeed.FAN_SPEED_2.value: SPEED_LOW,
FanSpeed.FAN_SPEED_3.value: SPEED_LOW,
FanSpeed.FAN_SPEED_4.value: SPEED_LOW,
FanSpeed.FAN_SPEED_AUTO.value: SPEED_MEDIUM,
FanSpeed.FAN_SPEED_5.value: SPEED_MEDIUM,
FanSpeed.FAN_SPEED_6.value: SPEED_MEDIUM,
FanSpeed.FAN_SPEED_7.value: SPEED_MEDIUM,
FanSpeed.FAN_SPEED_8.value: SPEED_HIGH,
FanSpeed.FAN_SPEED_9.value: SPEED_HIGH,
FanSpeed.FAN_SPEED_10.value: SPEED_HIGH}
return speed_map[self._device.state.speed]
@property
def dyson_speed(self):
"""Return the current speed."""
from libpurecool.const import FanSpeed
if self._device.state:
if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
return self._device.state.speed
return int(self._device.state.speed)
@property
def night_mode(self):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
@property
def auto_mode(self):
"""Return Auto mode."""
return self._device.state.auto_mode == "ON"
@property
def angle_low(self):
"""Return angle high."""
return int(self._device.state.oscillation_angle_low)
@property
def angle_high(self):
"""Return angle low."""
return int(self._device.state.oscillation_angle_high)
@property
def flow_direction_front(self):
"""Return frontal flow direction."""
return self._device.state.front_direction == 'ON'
@property
def timer(self):
"""Return timer."""
return self._device.state.sleep_timer
@property
def hepa_filter(self):
"""Return the HEPA filter state."""
return int(self._device.state.hepa_filter_state)
@property
def carbon_filter(self):
"""Return the carbon filter state."""
return int(self._device.state.carbon_filter_state)
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
@property
def dyson_speed_list(self) -> list:
"""Get the list of available dyson speeds."""
from libpurecool.const import FanSpeed
return [
int(FanSpeed.FAN_SPEED_1.value),
int(FanSpeed.FAN_SPEED_2.value),
int(FanSpeed.FAN_SPEED_3.value),
int(FanSpeed.FAN_SPEED_4.value),
int(FanSpeed.FAN_SPEED_5.value),
int(FanSpeed.FAN_SPEED_6.value),
int(FanSpeed.FAN_SPEED_7.value),
int(FanSpeed.FAN_SPEED_8.value),
int(FanSpeed.FAN_SPEED_9.value),
int(FanSpeed.FAN_SPEED_10.value),
]
@property
def device_serial(self):
"""Return fan's serial number."""
return self._device.serial
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_OSCILLATE | \
SUPPORT_SET_SPEED
@property
def device_state_attributes(self) -> dict:
"""Return optional state attributes."""
return {
ATTR_NIGHT_MODE: self.night_mode,
ATTR_AUTO_MODE: self.auto_mode,
ATTR_ANGLE_LOW: self.angle_low,
ATTR_ANGLE_HIGH: self.angle_high,
ATTR_FLOW_DIRECTION_FRONT: self.flow_direction_front,
ATTR_TIMER: self.timer,
ATTR_HEPA_FILTER: self.hepa_filter,
ATTR_CARBON_FILTER: self.carbon_filter,
ATTR_DYSON_SPEED: self.dyson_speed,
ATTR_DYSON_SPEED_LIST: self.dyson_speed_list
}