From ea7aa6af590317f9d352e0f25f51c167cdcd4d04 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Jan 2021 23:26:02 -1000 Subject: [PATCH] Update dyson for the new fan entity model (#45762) * Update dyson for the new fan entity model * Fix test * tweak * fix * adj * Update homeassistant/components/dyson/fan.py Co-authored-by: Martin Hjelmare * move percentage is None block * move percentage is None block * no need to list comp Co-authored-by: Martin Hjelmare --- homeassistant/components/dyson/fan.py | 164 +++++++++++++------------- tests/components/dyson/test_fan.py | 51 +++++++- 2 files changed, 128 insertions(+), 87 deletions(-) diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 7a57a75523e..7a403902ee8 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -1,5 +1,6 @@ """Support for Dyson Pure Cool link fan.""" import logging +import math from typing import Optional from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation @@ -9,15 +10,12 @@ from libpurecool.dyson_pure_state import DysonPureCoolState from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State import voluptuous as vol -from homeassistant.components.fan import ( - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, - SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.util.percentage import ( + percentage_to_ranged_value, + ranged_value_to_percentage, +) from . import DYSON_DEVICES, DysonEntity @@ -70,40 +68,30 @@ SET_DYSON_SPEED_SCHEMA = { } -SPEED_LIST_HA = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] +PRESET_MODE_AUTO = "auto" +PRESET_MODES = [PRESET_MODE_AUTO] -SPEED_LIST_DYSON = [ - 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), +ORDERED_DYSON_SPEEDS = [ + FanSpeed.FAN_SPEED_1, + FanSpeed.FAN_SPEED_2, + FanSpeed.FAN_SPEED_3, + FanSpeed.FAN_SPEED_4, + FanSpeed.FAN_SPEED_5, + FanSpeed.FAN_SPEED_6, + FanSpeed.FAN_SPEED_7, + FanSpeed.FAN_SPEED_8, + FanSpeed.FAN_SPEED_9, + FanSpeed.FAN_SPEED_10, ] +DYSON_SPEED_TO_INT_VALUE = {k: int(k.value) for k in ORDERED_DYSON_SPEEDS} +INT_VALUE_TO_DYSON_SPEED = {v: k for k, v in DYSON_SPEED_TO_INT_VALUE.items()} -SPEED_DYSON_TO_HA = { - 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, -} +SPEED_LIST_DYSON = list(DYSON_SPEED_TO_INT_VALUE.values()) -SPEED_HA_TO_DYSON = { - SPEED_LOW: FanSpeed.FAN_SPEED_4, - SPEED_MEDIUM: FanSpeed.FAN_SPEED_7, - SPEED_HIGH: FanSpeed.FAN_SPEED_10, -} +SPEED_RANGE = ( + SPEED_LIST_DYSON[0], + SPEED_LIST_DYSON[-1], +) # off is not included async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -160,14 +148,23 @@ class DysonFanEntity(DysonEntity, FanEntity): """Representation of a Dyson fan.""" @property - def speed(self): - """Return the current speed.""" - return SPEED_DYSON_TO_HA[self._device.state.speed] + def percentage(self): + """Return the current speed percentage.""" + if self.auto_mode: + return None + return ranged_value_to_percentage(SPEED_RANGE, int(self._device.state.speed)) @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return SPEED_LIST_HA + def preset_modes(self): + """Return the available preset modes.""" + return PRESET_MODES + + @property + def preset_mode(self): + """Return the current preset mode.""" + if self.auto_mode: + return PRESET_MODE_AUTO + return None @property def dyson_speed(self): @@ -206,12 +203,25 @@ class DysonFanEntity(DysonEntity, FanEntity): ATTR_DYSON_SPEED_LIST: self.dyson_speed_list, } - def set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - if speed not in SPEED_LIST_HA: - raise ValueError(f'"{speed}" is not a valid speed') - _LOGGER.debug("Set fan speed to: %s", speed) - self.set_dyson_speed(SPEED_HA_TO_DYSON[speed]) + def set_auto_mode(self, auto_mode: bool) -> None: + """Set auto mode.""" + raise NotImplementedError + + def set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + if percentage == 0: + self.turn_off() + return + dyson_speed = INT_VALUE_TO_DYSON_SPEED[ + math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + ] + self.set_dyson_speed(dyson_speed) + + def set_preset_mode(self, preset_mode: str) -> None: + """Set a preset mode on the fan.""" + self._valid_preset_mode_or_raise(preset_mode) + # There currently is only one + self.set_auto_mode(True) def set_dyson_speed(self, speed: FanSpeed) -> None: """Set the exact speed of the fan.""" @@ -225,21 +235,6 @@ class DysonFanEntity(DysonEntity, FanEntity): speed = FanSpeed(f"{int(dyson_speed):04d}") self.set_dyson_speed(speed) - -class DysonPureCoolLinkEntity(DysonFanEntity): - """Representation of a Dyson fan.""" - - def __init__(self, device): - """Initialize the fan.""" - super().__init__(device, DysonPureCoolState) - - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # def turn_on( self, speed: Optional[str] = None, @@ -248,12 +243,22 @@ class DysonPureCoolLinkEntity(DysonFanEntity): **kwargs, ) -> None: """Turn on the fan.""" - _LOGGER.debug("Turn on fan %s with speed %s", self.name, speed) - if speed is not None: - self.set_speed(speed) - else: - # Speed not set, just turn on + _LOGGER.debug("Turn on fan %s with percentage %s", self.name, percentage) + if preset_mode: + self.set_preset_mode(preset_mode) + elif percentage is None: + # percentage not set, just turn on self._device.set_configuration(fan_mode=FanMode.FAN) + else: + self.set_percentage(percentage) + + +class DysonPureCoolLinkEntity(DysonFanEntity): + """Representation of a Dyson fan.""" + + def __init__(self, device): + """Initialize the fan.""" + super().__init__(device, DysonPureCoolState) def turn_off(self, **kwargs) -> None: """Turn off the fan.""" @@ -312,13 +317,6 @@ class DysonPureCoolEntity(DysonFanEntity): """Initialize the fan.""" super().__init__(device, DysonPureCoolV2State) - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # def turn_on( self, speed: Optional[str] = None, @@ -327,12 +325,14 @@ class DysonPureCoolEntity(DysonFanEntity): **kwargs, ) -> None: """Turn on the fan.""" - _LOGGER.debug("Turn on fan %s", self.name) - - if speed is not None: - self.set_speed(speed) - else: + _LOGGER.debug("Turn on fan %s with percentage %s", self.name, percentage) + if preset_mode: + self.set_preset_mode(preset_mode) + elif percentage is None: + # percentage not set, just turn on self._device.turn_on() + else: + self.set_percentage(percentage) def turn_off(self, **kwargs): """Turn off the fan.""" diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index 310d9197133..dacde12c569 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -19,16 +19,18 @@ from homeassistant.components.dyson.fan import ( ATTR_HEPA_FILTER, ATTR_NIGHT_MODE, ATTR_TIMER, + PRESET_MODE_AUTO, SERVICE_SET_ANGLE, SERVICE_SET_AUTO_MODE, SERVICE_SET_DYSON_SPEED, SERVICE_SET_FLOW_DIRECTION_FRONT, SERVICE_SET_NIGHT_MODE, SERVICE_SET_TIMER, - SPEED_LOW, ) from homeassistant.components.fan import ( ATTR_OSCILLATING, + ATTR_PERCENTAGE, + ATTR_PRESET_MODE, ATTR_SPEED, ATTR_SPEED_LIST, DOMAIN as PLATFORM_DOMAIN, @@ -37,7 +39,9 @@ from homeassistant.components.fan import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, SPEED_HIGH, + SPEED_LOW, SPEED_MEDIUM, + SPEED_OFF, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, ) @@ -84,8 +88,16 @@ async def test_state_purecoollink( attributes = state.attributes assert attributes[ATTR_NIGHT_MODE] is True assert attributes[ATTR_OSCILLATING] is True + assert attributes[ATTR_PERCENTAGE] == 10 + assert attributes[ATTR_PRESET_MODE] is None assert attributes[ATTR_SPEED] == SPEED_LOW - assert attributes[ATTR_SPEED_LIST] == [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + assert attributes[ATTR_SPEED_LIST] == [ + SPEED_OFF, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_HIGH, + PRESET_MODE_AUTO, + ] assert attributes[ATTR_DYSON_SPEED] == 1 assert attributes[ATTR_DYSON_SPEED_LIST] == list(range(1, 11)) assert attributes[ATTR_AUTO_MODE] is False @@ -106,7 +118,9 @@ async def test_state_purecoollink( attributes = state.attributes assert attributes[ATTR_NIGHT_MODE] is False assert attributes[ATTR_OSCILLATING] is False - assert attributes[ATTR_SPEED] == SPEED_MEDIUM + assert attributes[ATTR_PERCENTAGE] is None + assert attributes[ATTR_PRESET_MODE] == "auto" + assert attributes[ATTR_SPEED] == PRESET_MODE_AUTO assert attributes[ATTR_DYSON_SPEED] == "AUTO" assert attributes[ATTR_AUTO_MODE] is True @@ -125,8 +139,16 @@ async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> Non assert attributes[ATTR_OSCILLATING] is True assert attributes[ATTR_ANGLE_LOW] == 24 assert attributes[ATTR_ANGLE_HIGH] == 254 + assert attributes[ATTR_PERCENTAGE] == 10 + assert attributes[ATTR_PRESET_MODE] is None assert attributes[ATTR_SPEED] == SPEED_LOW - assert attributes[ATTR_SPEED_LIST] == [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + assert attributes[ATTR_SPEED_LIST] == [ + SPEED_OFF, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_HIGH, + PRESET_MODE_AUTO, + ] assert attributes[ATTR_DYSON_SPEED] == 1 assert attributes[ATTR_DYSON_SPEED_LIST] == list(range(1, 11)) assert attributes[ATTR_AUTO_MODE] is False @@ -148,7 +170,9 @@ async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> Non attributes = state.attributes assert attributes[ATTR_NIGHT_MODE] is False assert attributes[ATTR_OSCILLATING] is False - assert attributes[ATTR_SPEED] == SPEED_MEDIUM + assert attributes[ATTR_PERCENTAGE] is None + assert attributes[ATTR_PRESET_MODE] == "auto" + assert attributes[ATTR_SPEED] == PRESET_MODE_AUTO assert attributes[ATTR_DYSON_SPEED] == "AUTO" assert attributes[ATTR_AUTO_MODE] is True assert attributes[ATTR_FLOW_DIRECTION_FRONT] is False @@ -170,6 +194,11 @@ async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> Non {ATTR_SPEED: SPEED_LOW}, {"fan_mode": FanMode.FAN, "fan_speed": FanSpeed.FAN_SPEED_4}, ), + ( + SERVICE_TURN_ON, + {ATTR_PERCENTAGE: 40}, + {"fan_mode": FanMode.FAN, "fan_speed": FanSpeed.FAN_SPEED_4}, + ), (SERVICE_TURN_OFF, {}, {"fan_mode": FanMode.OFF}), ( SERVICE_OSCILLATE, @@ -229,6 +258,18 @@ async def test_commands_purecoollink( "set_fan_speed", [FanSpeed.FAN_SPEED_4], ), + ( + SERVICE_TURN_ON, + {ATTR_PERCENTAGE: 40}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_4], + ), + ( + SERVICE_TURN_ON, + {ATTR_PRESET_MODE: "auto"}, + "enable_auto_mode", + [], + ), (SERVICE_TURN_OFF, {}, "turn_off", []), (SERVICE_OSCILLATE, {ATTR_OSCILLATING: True}, "enable_oscillation", []), (SERVICE_OSCILLATE, {ATTR_OSCILLATING: False}, "disable_oscillation", []),