-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Dyson Pure Cool Link support (#7795)
* Add Dyson Pure Cool Link support * Code review * Improve auto/night mode * Move night_mode to Dyson fan component * Code review * fix asynchrone/sync * Create dyson.py
- Loading branch information
1 parent
bf2fe60
commit 8c0967a
Showing
10 changed files
with
924 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
"""Parent component for Dyson Pure Cool Link devices.""" | ||
|
||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.helpers import discovery | ||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \ | ||
CONF_DEVICES | ||
|
||
REQUIREMENTS = ['libpurecoollink==0.1.5'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
CONF_LANGUAGE = "language" | ||
CONF_RETRY = "retry" | ||
|
||
DEFAULT_TIMEOUT = 5 | ||
DEFAULT_RETRY = 10 | ||
|
||
DOMAIN = "dyson" | ||
|
||
CONFIG_SCHEMA = vol.Schema({ | ||
DOMAIN: vol.Schema({ | ||
vol.Required(CONF_USERNAME): cv.string, | ||
vol.Required(CONF_PASSWORD): cv.string, | ||
vol.Required(CONF_LANGUAGE): cv.string, | ||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, | ||
vol.Optional(CONF_RETRY, default=DEFAULT_RETRY): cv.positive_int, | ||
vol.Optional(CONF_DEVICES, default=[]): | ||
vol.All(cv.ensure_list, [dict]), | ||
}) | ||
}, extra=vol.ALLOW_EXTRA) | ||
|
||
DYSON_DEVICES = "dyson_devices" | ||
|
||
|
||
def setup(hass, config): | ||
"""Set up the Dyson parent component.""" | ||
_LOGGER.info("Creating new Dyson component") | ||
|
||
if DYSON_DEVICES not in hass.data: | ||
hass.data[DYSON_DEVICES] = [] | ||
|
||
from libpurecoollink.dyson import DysonAccount | ||
dyson_account = DysonAccount(config[DOMAIN].get(CONF_USERNAME), | ||
config[DOMAIN].get(CONF_PASSWORD), | ||
config[DOMAIN].get(CONF_LANGUAGE)) | ||
|
||
logged = dyson_account.login() | ||
|
||
timeout = config[DOMAIN].get(CONF_TIMEOUT) | ||
retry = config[DOMAIN].get(CONF_RETRY) | ||
|
||
if not logged: | ||
_LOGGER.error("Not connected to Dyson account. Unable to add devices") | ||
return False | ||
|
||
_LOGGER.info("Connected to Dyson account") | ||
dyson_devices = dyson_account.devices() | ||
if CONF_DEVICES in config[DOMAIN] and config[DOMAIN].get(CONF_DEVICES): | ||
configured_devices = config[DOMAIN].get(CONF_DEVICES) | ||
for device in configured_devices: | ||
dyson_device = next((d for d in dyson_devices if | ||
d.serial == device["device_id"]), None) | ||
if dyson_device: | ||
connected = dyson_device.connect(None, device["device_ip"], | ||
timeout, retry) | ||
if connected: | ||
_LOGGER.info("Connected to device %s", dyson_device) | ||
hass.data[DYSON_DEVICES].append(dyson_device) | ||
else: | ||
_LOGGER.warning("Unable to connect to device %s", | ||
dyson_device) | ||
else: | ||
_LOGGER.warning( | ||
"Unable to find device %s in Dyson account", | ||
device["device_id"]) | ||
else: | ||
# Not yet reliable | ||
for device in dyson_devices: | ||
_LOGGER.info("Trying to connect to device %s with timeout=%i " | ||
"and retry=%i", device, timeout, retry) | ||
connected = device.connect(None, None, timeout, retry) | ||
if connected: | ||
_LOGGER.info("Connected to device %s", device) | ||
hass.data[DYSON_DEVICES].append(device) | ||
else: | ||
_LOGGER.warning("Unable to connect to device %s", device) | ||
|
||
# Start fan/sensors components | ||
if hass.data[DYSON_DEVICES]: | ||
_LOGGER.debug("Starting sensor/fan components") | ||
discovery.load_platform(hass, "sensor", DOMAIN, {}, config) | ||
discovery.load_platform(hass, "fan", DOMAIN, {}, config) | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
"""Support for Dyson Pure Cool link fan.""" | ||
import logging | ||
import asyncio | ||
from os import path | ||
import voluptuous as vol | ||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.components.fan import (FanEntity, SUPPORT_OSCILLATE, | ||
SUPPORT_SET_SPEED, | ||
DOMAIN) | ||
from homeassistant.helpers.entity import ToggleEntity | ||
from homeassistant.components.dyson import DYSON_DEVICES | ||
from homeassistant.config import load_yaml_config_file | ||
|
||
DEPENDENCIES = ['dyson'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
DYSON_FAN_DEVICES = "dyson_fan_devices" | ||
SERVICE_SET_NIGHT_MODE = 'dyson_set_night_mode' | ||
|
||
DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema({ | ||
vol.Required('entity_id'): cv.entity_id, | ||
vol.Required('night_mode'): cv.boolean | ||
}) | ||
|
||
|
||
def setup_platform(hass, config, add_devices, discovery_info=None): | ||
"""Setup the Dyson fan components.""" | ||
_LOGGER.info("Creating new Dyson fans") | ||
if DYSON_FAN_DEVICES not in hass.data: | ||
hass.data[DYSON_FAN_DEVICES] = [] | ||
|
||
# Get Dyson Devices from parent component | ||
for device in hass.data[DYSON_DEVICES]: | ||
dyson_entity = DysonPureCoolLinkDevice(hass, device) | ||
hass.data[DYSON_FAN_DEVICES].append(dyson_entity) | ||
|
||
add_devices(hass.data[DYSON_FAN_DEVICES]) | ||
|
||
descriptions = load_yaml_config_file( | ||
path.join(path.dirname(__file__), 'services.yaml')) | ||
|
||
def service_handle(service): | ||
"""Handle dyson services.""" | ||
entity_id = service.data.get('entity_id') | ||
night_mode = service.data.get('night_mode') | ||
fan_device = next([fan for fan in hass.data[DYSON_FAN_DEVICES] if | ||
fan.entity_id == entity_id].__iter__(), 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.night_mode(night_mode) | ||
|
||
# Register dyson service(s) | ||
hass.services.register(DOMAIN, SERVICE_SET_NIGHT_MODE, | ||
service_handle, | ||
descriptions.get(SERVICE_SET_NIGHT_MODE), | ||
schema=DYSON_SET_NIGHT_MODE_SCHEMA) | ||
|
||
|
||
class DysonPureCoolLinkDevice(FanEntity): | ||
"""Representation of a Dyson fan.""" | ||
|
||
def __init__(self, hass, device): | ||
"""Initialize the fan.""" | ||
_LOGGER.info("Creating device %s", device.name) | ||
self.hass = hass | ||
self._device = device | ||
|
||
@asyncio.coroutine | ||
def async_added_to_hass(self): | ||
"""Callback when entity is added to hass.""" | ||
self.hass.async_add_job( | ||
self._device.add_message_listener(self.on_message)) | ||
|
||
def on_message(self, message): | ||
"""Called when new messages received from the fan.""" | ||
_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: ToggleEntity, speed: str) -> None: | ||
"""Set the speed of the fan. Never called ??.""" | ||
_LOGGER.debug("Set fan speed to: " + speed) | ||
from libpurecoollink.const import FanSpeed, FanMode | ||
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: ToggleEntity, speed: str=None, **kwargs) -> None: | ||
"""Turn on the fan.""" | ||
_LOGGER.debug("Turn on fan %s with speed %s", self.name, speed) | ||
from libpurecoollink.const import FanSpeed, FanMode | ||
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: ToggleEntity, **kwargs) -> None: | ||
"""Turn off the fan.""" | ||
_LOGGER.debug("Turn off fan %s", self.name) | ||
from libpurecoollink.const import FanMode | ||
self._device.set_configuration(fan_mode=FanMode.OFF) | ||
|
||
def oscillate(self: ToggleEntity, oscillating: bool) -> None: | ||
"""Turn on/off oscillating.""" | ||
_LOGGER.debug("Turn oscillation %s for device %s", oscillating, | ||
self.name) | ||
from libpurecoollink.const import Oscillation | ||
|
||
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_state == "FAN" | ||
return False | ||
|
||
@property | ||
def speed(self) -> str: | ||
"""Return the current speed.""" | ||
if self._device.state: | ||
from libpurecoollink.const import FanSpeed | ||
if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value: | ||
return self._device.state.speed | ||
else: | ||
return int(self._device.state.speed) | ||
return None | ||
|
||
@property | ||
def current_direction(self): | ||
"""Return direction of the fan [forward, reverse].""" | ||
return None | ||
|
||
@property | ||
def is_night_mode(self): | ||
"""Return Night mode.""" | ||
return self._device.state.night_mode == "ON" | ||
|
||
def night_mode(self: ToggleEntity, night_mode: bool) -> None: | ||
"""Turn fan in night mode.""" | ||
_LOGGER.debug("Set %s night mode %s", self.name, night_mode) | ||
from libpurecoollink.const import NightMode | ||
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 is_auto_mode(self): | ||
"""Return auto mode.""" | ||
return self._device.state.fan_mode == "AUTO" | ||
|
||
def auto_mode(self: ToggleEntity, auto_mode: bool) -> None: | ||
"""Turn fan in auto mode.""" | ||
_LOGGER.debug("Set %s auto mode %s", self.name, auto_mode) | ||
from libpurecoollink.const import FanMode | ||
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: ToggleEntity) -> list: | ||
"""Get the list of available speeds.""" | ||
from libpurecoollink.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: ToggleEntity) -> int: | ||
"""Flag supported features.""" | ||
return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.