From ec793a1137b2d103d142310094e4bcd47cdb7140 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 18 Aug 2021 11:05:36 +0930 Subject: [PATCH 01/65] Update en.json --- custom_components/localtuya/translations/en.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 6ce233a..2b12013 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -72,9 +72,8 @@ "scene": "Scene", "fan_speed_control": "Fan Speed Control", "fan_oscillating_control": "Fan Oscillating Control", - "fan_speed_low": "Fan Low Speed Setting", - "fan_speed_medium": "Fan Medium Speed Setting", - "fan_speed_high": "Fan High Speed Setting" + "fan_direction": "Fan Direction Control", + "fan_speed_count": "Fan Speed Count" } } } From 5217014f224002ee169657746d3d3ad3b4eb981f Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 18 Aug 2021 11:06:41 +0930 Subject: [PATCH 02/65] Update const.py --- custom_components/localtuya/const.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index bd8a5d3..b110ed4 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -33,10 +33,10 @@ CONF_SPAN_TIME = "span_time" # fan CONF_FAN_SPEED_CONTROL = "fan_speed_control" -CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control" -CONF_FAN_SPEED_LOW = "fan_speed_low" -CONF_FAN_SPEED_MEDIUM = "fan_speed_medium" -CONF_FAN_SPEED_HIGH = "fan_speed_high" +# CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control" +CONF_FAN_DIRECTION = "fan_direction" +CONF_FAN_SPEED_COUNT = "fan_speed_count" + # sensor CONF_SCALING = "scaling" From 761a5f133aa00710302d610a40a0c21623d55573 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 18 Aug 2021 11:07:17 +0930 Subject: [PATCH 03/65] Update fan.py --- custom_components/localtuya/fan.py | 170 ++++++++++++++++++++--------- 1 file changed, 120 insertions(+), 50 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index b45567e..f4bf87a 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -1,3 +1,9 @@ +# DPS [1] VALUE [True] --> fan on,off > True, False +# DPS [3] VALUE [6] --> fan speed > 1-6 +# DPS [4] VALUE [forward] --> fan direction > forward, reverse +# DPS [102] VALUE [normal] --> preset mode > normal, sleep, nature +# DPS [103] VALUE [off] --> timer > off, 1hour, 2hour, 4hour, 8hour + """Platform to locally control Tuya-based fan devices.""" import logging from functools import partial @@ -5,10 +11,9 @@ from functools import partial import voluptuous as vol from homeassistant.components.fan import ( DOMAIN, - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, - SPEED_OFF, + DIRECTION_FORWARD, + DIRECTION_REVERSE, + SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity, @@ -18,9 +23,16 @@ from .common import LocalTuyaEntity, async_setup_entry from .const import ( CONF_FAN_OSCILLATING_CONTROL, CONF_FAN_SPEED_CONTROL, - CONF_FAN_SPEED_HIGH, - CONF_FAN_SPEED_LOW, - CONF_FAN_SPEED_MEDIUM, + CONF_FAN_DIRECTION, + CONF_FAN_SPEED_COUNT +) + +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, + percentage_to_ranged_value, + ranged_value_to_percentage, + int_states_in_range ) _LOGGER = logging.getLogger(__name__) @@ -31,17 +43,16 @@ def flow_schema(dps): return { vol.Optional(CONF_FAN_SPEED_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_OSCILLATING_CONTROL): vol.In(dps), - vol.Optional(CONF_FAN_SPEED_LOW, default=SPEED_LOW): vol.In( - [SPEED_LOW, "1", "2", "small"] - ), - vol.Optional(CONF_FAN_SPEED_MEDIUM, default=SPEED_MEDIUM): vol.In( - [SPEED_MEDIUM, "mid", "2", "3"] - ), - vol.Optional(CONF_FAN_SPEED_HIGH, default=SPEED_HIGH): vol.In( - [SPEED_HIGH, "auto", "3", "4", "large"] - ), + vol.Optional(CONF_FAN_DIRECTION): vol.In(dps), + vol.Optional(CONF_FAN_SPEED_COUNT, default=3): cv.positive_int } +# make this configurable later +DIRECTION_TO_TUYA = { + DIRECTION_REVERSE: "reverse", + DIRECTION_FORWARD: "forward", +} +TUYA_DIRECTION_TO_HA = {v: k for (k, v) in DIRECTION_TO_TUYA.items()} class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Representation of a Tuya fan.""" @@ -56,28 +67,40 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Initialize the entity.""" super().__init__(device, config_entry, fanid, _LOGGER, **kwargs) self._is_on = False - self._speed = None self._oscillating = None + self._direction = None + self._percentage = None @property def oscillating(self): """Return current oscillating status.""" return self._oscillating + @property + def current_direction(self): + """Return the current direction of the fan.""" + direction = self.service.value(CharacteristicsTypes.ROTATION_DIRECTION) + return TUYA_DIRECTION_TO_HA[direction] + @property def is_on(self): """Check if Tuya fan is on.""" return self._is_on @property - def speed(self) -> str: - """Return the current speed.""" - return self._speed + def percentage(self): + """Return the current percentage.""" + return self._percentage - @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + # @property + # def speed(self) -> str: + # """Return the current speed.""" + # return self._speed + + # @property + # def speed_list(self) -> list: + # """Get the list of available speeds.""" + # return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] async def async_turn_on(self, speed: str = None, **kwargs) -> None: """Turn on the entity.""" @@ -92,22 +115,38 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): await self._device.set_dp(False, self._dp_id) self.schedule_update_ha_state() - async def async_set_speed(self, speed: str) -> None: + async def async_set_percentage(self, percentage): """Set the speed of the fan.""" - mapping = { - SPEED_LOW: self._config.get(CONF_FAN_SPEED_LOW), - SPEED_MEDIUM: self._config.get(CONF_FAN_SPEED_MEDIUM), - SPEED_HIGH: self._config.get(CONF_FAN_SPEED_HIGH), - } + if percentage == 0: + return await self.async_turn_off() - if speed == SPEED_OFF: - await self._device.set_dp(False, self._dp_id) - else: + if not self.is_on: + await self.async_turn_on() + + if percentage is not None await self._device.set_dp( - mapping.get(speed), self._config.get(CONF_FAN_SPEED_CONTROL) - ) + math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + ), + self._config.get(CONF_FAN_SPEED_CONTROL) + ) - self.schedule_update_ha_state() + # async def async_set_speed(self, speed: str) -> None: + # """Set the speed of the fan.""" + # mapping = { + # SPEED_LOW: self._config.get(CONF_FAN_SPEED_LOW), + # SPEED_MEDIUM: self._config.get(CONF_FAN_SPEED_MEDIUM), + # SPEED_HIGH: self._config.get(CONF_FAN_SPEED_HIGH), + # } + + # if speed == SPEED_OFF: + # await self._device.set_dp(False, self._dp_id) + # else: + # await self._device.set_dp( + # mapping.get(speed), self._config.get(CONF_FAN_SPEED_CONTROL) + # ) + + # self.schedule_update_ha_state() async def async_oscillate(self, oscillating: bool) -> None: """Set oscillation.""" @@ -116,38 +155,69 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): ) self.schedule_update_ha_state() + async def async_set_direction(self, direction): + """Set the direction of the fan.""" + await self.async_put_characteristics( + {CharacteristicsTypes.ROTATION_DIRECTION: DIRECTION_TO_TUYA[direction]} + ) + @property def supported_features(self) -> int: """Flag supported features.""" - supports = 0 + features = 0 if self.has_config(CONF_FAN_OSCILLATING_CONTROL): - supports |= SUPPORT_OSCILLATE - if self.has_config(CONF_FAN_SPEED_CONTROL): - supports |= SUPPORT_SET_SPEED + features |= SUPPORT_OSCILLATE - return supports + if self.has_config(CONF_FAN_SPEED_CONTROL): + features |= SUPPORT_SET_SPEED + + if self.has_config(CONF_FAN_DIRECTION): + features |= SUPPORT_DIRECTION + + return features + + @property + def speed_count(self) -> int: + """Speed count for the fan.""" + return self.has_config(CONF_FAN_SPEED_COUNT) + ) def status_updated(self): """Get state of Tuya fan.""" - mappings = { - self._config.get(CONF_FAN_SPEED_LOW): SPEED_LOW, - self._config.get(CONF_FAN_SPEED_MEDIUM): SPEED_MEDIUM, - self._config.get(CONF_FAN_SPEED_HIGH): SPEED_HIGH, - } self._is_on = self.dps(self._dp_id) - if self.has_config(CONF_FAN_SPEED_CONTROL): - self._speed = mappings.get(self.dps_conf(CONF_FAN_SPEED_CONTROL)) - if self.speed is None: + if self.has_config(CONF_FAN_SPEED_COUNT): + self._percentage = ranged_value_to_percentage( + self._speed_range, self.dps_conf(CONF_FAN_SPEED_CONTROL) + ) + if self._percentage is None: self.warning( "%s/%s: Ignoring unknown fan controller state: %s", self.name, self.entity_id, self.dps_conf(CONF_FAN_SPEED_CONTROL), ) - self._speed = None + self._percentage = None + + + # mappings = { + # self._config.get(CONF_FAN_SPEED_LOW): SPEED_LOW, + # self._config.get(CONF_FAN_SPEED_MEDIUM): SPEED_MEDIUM, + # self._config.get(CONF_FAN_SPEED_HIGH): SPEED_HIGH, + # } + + # if self.has_config(CONF_FAN_SPEED_CONTROL): + # self._speed = mappings.get(self.dps_conf(CONF_FAN_SPEED_CONTROL)) + # if self.speed is None: + # self.warning( + # "%s/%s: Ignoring unknown fan controller state: %s", + # self.name, + # self.entity_id, + # self.dps_conf(CONF_FAN_SPEED_CONTROL), + # ) + # self._speed = None if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) From 4eb1806eb4a5e58849ae2a60115b191b1e3bcd50 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 18 Aug 2021 11:24:52 +0930 Subject: [PATCH 04/65] Update fan.py --- custom_components/localtuya/fan.py | 52 +++++++----------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index f4bf87a..859b204 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -79,8 +79,8 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): @property def current_direction(self): """Return the current direction of the fan.""" - direction = self.service.value(CharacteristicsTypes.ROTATION_DIRECTION) - return TUYA_DIRECTION_TO_HA[direction] + return self._direction + @property def is_on(self): @@ -123,7 +123,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if not self.is_on: await self.async_turn_on() - if percentage is not None + if percentage is not None: await self._device.set_dp( math.ceil( percentage_to_ranged_value(self._speed_range, percentage) @@ -131,22 +131,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._config.get(CONF_FAN_SPEED_CONTROL) ) - # async def async_set_speed(self, speed: str) -> None: - # """Set the speed of the fan.""" - # mapping = { - # SPEED_LOW: self._config.get(CONF_FAN_SPEED_LOW), - # SPEED_MEDIUM: self._config.get(CONF_FAN_SPEED_MEDIUM), - # SPEED_HIGH: self._config.get(CONF_FAN_SPEED_HIGH), - # } - - # if speed == SPEED_OFF: - # await self._device.set_dp(False, self._dp_id) - # else: - # await self._device.set_dp( - # mapping.get(speed), self._config.get(CONF_FAN_SPEED_CONTROL) - # ) - - # self.schedule_update_ha_state() async def async_oscillate(self, oscillating: bool) -> None: """Set oscillation.""" @@ -157,9 +141,10 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): async def async_set_direction(self, direction): """Set the direction of the fan.""" - await self.async_put_characteristics( - {CharacteristicsTypes.ROTATION_DIRECTION: DIRECTION_TO_TUYA[direction]} + await self._device.set_dp( + DIRECTION_TO_TUYA[direction], self._config.get(CONF_FAN_DIRECTION) ) + self.schedule_update_ha_state() @property def supported_features(self) -> int: @@ -181,7 +166,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): def speed_count(self) -> int: """Speed count for the fan.""" return self.has_config(CONF_FAN_SPEED_COUNT) - ) + def status_updated(self): """Get state of Tuya fan.""" @@ -201,26 +186,13 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): ) self._percentage = None - - # mappings = { - # self._config.get(CONF_FAN_SPEED_LOW): SPEED_LOW, - # self._config.get(CONF_FAN_SPEED_MEDIUM): SPEED_MEDIUM, - # self._config.get(CONF_FAN_SPEED_HIGH): SPEED_HIGH, - # } - - # if self.has_config(CONF_FAN_SPEED_CONTROL): - # self._speed = mappings.get(self.dps_conf(CONF_FAN_SPEED_CONTROL)) - # if self.speed is None: - # self.warning( - # "%s/%s: Ignoring unknown fan controller state: %s", - # self.name, - # self.entity_id, - # self.dps_conf(CONF_FAN_SPEED_CONTROL), - # ) - # self._speed = None - if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) + if self.has_config(CONF_FAN_DIRECTION): + self._oscillating = TUYA_DIRECTION_TO_HA[ + self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) + ] + async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From b8acadc1956871d2e368fda4f135b287008d884e Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 18 Aug 2021 13:15:04 +0930 Subject: [PATCH 05/65] working add more config options and presets in future --- custom_components/localtuya/fan.py | 34 ++++++++++++++---------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 859b204..b0e1008 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -1,13 +1,9 @@ -# DPS [1] VALUE [True] --> fan on,off > True, False -# DPS [3] VALUE [6] --> fan speed > 1-6 -# DPS [4] VALUE [forward] --> fan direction > forward, reverse -# DPS [102] VALUE [normal] --> preset mode > normal, sleep, nature -# DPS [103] VALUE [off] --> timer > off, 1hour, 2hour, 4hour, 8hour - """Platform to locally control Tuya-based fan devices.""" import logging from functools import partial +import math +import homeassistant.helpers.config_validation as cv import voluptuous as vol from homeassistant.components.fan import ( DOMAIN, @@ -70,6 +66,9 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._oscillating = None self._direction = None self._percentage = None + self._speed_range = ( + 1, self._config.get(CONF_FAN_SPEED_COUNT), + ) @property def oscillating(self): @@ -125,9 +124,9 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if percentage is not None: await self._device.set_dp( - math.ceil( + str(math.ceil( percentage_to_ranged_value(self._speed_range, percentage) - ), + )), self._config.get(CONF_FAN_SPEED_CONTROL) ) @@ -165,7 +164,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): @property def speed_count(self) -> int: """Speed count for the fan.""" - return self.has_config(CONF_FAN_SPEED_COUNT) + return int_states_in_range(self._speed_range) def status_updated(self): @@ -174,25 +173,24 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._is_on = self.dps(self._dp_id) if self.has_config(CONF_FAN_SPEED_COUNT): - self._percentage = ranged_value_to_percentage( - self._speed_range, self.dps_conf(CONF_FAN_SPEED_CONTROL) - ) - if self._percentage is None: + value = int(self.dps_conf(CONF_FAN_SPEED_CONTROL)) + if value is not None: + self._percentage = ranged_value_to_percentage(self._speed_range, value) + else: self.warning( "%s/%s: Ignoring unknown fan controller state: %s", self.name, self.entity_id, - self.dps_conf(CONF_FAN_SPEED_CONTROL), + value, ) - self._percentage = None if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) if self.has_config(CONF_FAN_DIRECTION): - self._oscillating = TUYA_DIRECTION_TO_HA[ - self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) - ] + value = self.dps_conf(CONF_FAN_DIRECTION) + if value is not None: + self._direction = TUYA_DIRECTION_TO_HA[value] async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From e61307597123ee8da9a0c858e2e191ed96767373 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 19 Aug 2021 19:53:32 +0930 Subject: [PATCH 06/65] update to new fan entity percentage configuration --- .../localtuya/translations/en.json | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 2b12013..de40762 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -70,10 +70,14 @@ "color_temp_max_kelvin": "Maximum Color Temperature in K", "music_mode": "Music mode available", "scene": "Scene", - "fan_speed_control": "Fan Speed Control", - "fan_oscillating_control": "Fan Oscillating Control", - "fan_direction": "Fan Direction Control", - "fan_speed_count": "Fan Speed Count" + "fan_speed_control": "Fan Speed Control dps", + "fan_oscillating_control": "Fan Oscillating Control dps", + "fan_speed_min": "minimum fan speed integer", + "fan_speed_max": "maximum fan speed integer", + "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", + "fan_direction":"fan direction dps", + "fan_direction_forward": "forward dps string", + "fan_direction_reverse": "reverser dps string" } } } @@ -121,11 +125,14 @@ "color_temp_max_kelvin": "Maximum Color Temperature in K", "music_mode": "Music mode available", "scene": "Scene", - "fan_speed_control": "Fan Speed Control", - "fan_oscillating_control": "Fan Oscillating Control", - "fan_speed_low": "Fan Low Speed Setting", - "fan_speed_medium": "Fan Medium Speed Setting", - "fan_speed_high": "Fan High Speed Setting" + "fan_speed_control": "Fan Speed Control dps", + "fan_oscillating_control": "Fan Oscillating Control dps", + "fan_speed_min": "minimum fan speed integer", + "fan_speed_max": "maximum fan speed integer", + "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", + "fan_direction":"fan direction dps", + "fan_direction_forward": "forward dps string", + "fan_direction_reverse": "reverser dps string" } }, "yaml_import": { From 94c3af1c7d49533f8a66b9e5490aae575bf458dc Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 19 Aug 2021 19:56:54 +0930 Subject: [PATCH 07/65] update to new fan entity percentage --- custom_components/localtuya/const.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index b110ed4..9662b3b 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -33,10 +33,13 @@ CONF_SPAN_TIME = "span_time" # fan CONF_FAN_SPEED_CONTROL = "fan_speed_control" -# CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control" +CONF_FAN_OSCILLATING_CONTROL = "fan_oscillating_control" +CONF_FAN_SPEED_MIN = "fan_speed_min" +CONF_FAN_SPEED_MAX = "fan_speed_max" +CONF_FAN_ORDERED_LIST = "fan_speed_ordered_list" CONF_FAN_DIRECTION = "fan_direction" -CONF_FAN_SPEED_COUNT = "fan_speed_count" - +CONF_FAN_DIRECTION_FWD = "fan_direction_forward" +CONF_FAN_DIRECTION_REV = "fan_direction_reverse" # sensor CONF_SCALING = "scaling" From 4cc7ffc07273838236480031045051f232f6b2d4 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 19 Aug 2021 19:57:51 +0930 Subject: [PATCH 08/65] update to new fan entity configuration --- custom_components/localtuya/fan.py | 129 ++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index b0e1008..4e2af6b 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -1,3 +1,9 @@ +# DPS [1] VALUE [True] --> fan on,off > True, False +# DPS [3] VALUE [6] --> fan speed > 1-6 +# DPS [4] VALUE [forward] --> fan direction > forward, reverse +# DPS [102] VALUE [normal] --> preset mode > normal, sleep, nature +# DPS [103] VALUE [off] --> timer > off, 1hour, 2hour, 4hour, 8hour + """Platform to locally control Tuya-based fan devices.""" import logging from functools import partial @@ -20,7 +26,11 @@ from .const import ( CONF_FAN_OSCILLATING_CONTROL, CONF_FAN_SPEED_CONTROL, CONF_FAN_DIRECTION, - CONF_FAN_SPEED_COUNT + CONF_FAN_DIRECTION_FWD, + CONF_FAN_DIRECTION_REV, + CONF_FAN_SPEED_MIN, + CONF_FAN_SPEED_MAX, + CONF_FAN_ORDERED_LIST ) from homeassistant.util.percentage import ( @@ -40,15 +50,13 @@ def flow_schema(dps): vol.Optional(CONF_FAN_SPEED_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_OSCILLATING_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION): vol.In(dps), - vol.Optional(CONF_FAN_SPEED_COUNT, default=3): cv.positive_int + vol.Optional(CONF_FAN_DIRECTION_FWD, default="forward"): cv.string, + vol.Optional(CONF_FAN_DIRECTION_REV, default="reverse"): cv.string, + vol.Optional(CONF_FAN_SPEED_MIN, default=1): cv.positive_int, + vol.Optional(CONF_FAN_SPEED_MAX, default=9): cv.positive_int, + vol.Optional(CONF_FAN_ORDERED_LIST, default="disabled"): cv.string, } -# make this configurable later -DIRECTION_TO_TUYA = { - DIRECTION_REVERSE: "reverse", - DIRECTION_FORWARD: "forward", -} -TUYA_DIRECTION_TO_HA = {v: k for (k, v) in DIRECTION_TO_TUYA.items()} class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Representation of a Tuya fan.""" @@ -67,8 +75,20 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._direction = None self._percentage = None self._speed_range = ( - 1, self._config.get(CONF_FAN_SPEED_COUNT), + self._config.get(CONF_FAN_SPEED_MIN), + self._config.get(CONF_FAN_SPEED_MAX), ) + self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") + self._ordered_list_mode = None + + if (type(self._ordered_list) is list and len(self._ordered_list) > 1): + self._use_ordered_list = True + _LOGGER.debug("Fan _use_ordered_list: %s > %s", self._use_ordered_list, self._ordered_list) + + else: + self._use_ordered_list = False + _LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list) + @property def oscillating(self): @@ -80,7 +100,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Return the current direction of the fan.""" return self._direction - @property def is_on(self): """Check if Tuya fan is on.""" @@ -91,18 +110,9 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Return the current percentage.""" return self._percentage - # @property - # def speed(self) -> str: - # """Return the current speed.""" - # return self._speed - - # @property - # def speed_list(self) -> list: - # """Get the list of available speeds.""" - # return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] - async def async_turn_on(self, speed: str = None, **kwargs) -> None: """Turn on the entity.""" + _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) if speed is not None: await self.async_set_speed(speed) @@ -111,11 +121,17 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): async def async_turn_off(self, **kwargs) -> None: """Turn off the entity.""" + + _LOGGER.debug("Fan async_turn_on") + await self._device.set_dp(False, self._dp_id) self.schedule_update_ha_state() async def async_set_percentage(self, percentage): """Set the speed of the fan.""" + + _LOGGER.debug("Fan async_set_percentage: %s", percentage) + if percentage == 0: return await self.async_turn_off() @@ -123,16 +139,28 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): await self.async_turn_on() if percentage is not None: - await self._device.set_dp( - str(math.ceil( - percentage_to_ranged_value(self._speed_range, percentage) - )), - self._config.get(CONF_FAN_SPEED_CONTROL) - ) + if self._use_ordered_list: + await self._device.set_dp( + str( + percentage_to_ordered_list_item(self._ordered_list, percentage) + ), + self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) + + else: + await self._device.set_dp( + str(math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + )), + self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._ordered_list, percentage)) async def async_oscillate(self, oscillating: bool) -> None: """Set oscillation.""" + _LOGGER.debug("Fan async_oscillate: %s", oscillating) await self._device.set_dp( oscillating, self._config.get(CONF_FAN_OSCILLATING_CONTROL) ) @@ -140,8 +168,16 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): async def async_set_direction(self, direction): """Set the direction of the fan.""" + _LOGGER.debug("Fan async_set_direction: %s", direction) + + if direction == DIRECTION_FORWARD: + value = self._config.get(CONF_FAN_DIRECTION_FWD) + + if direction == DIRECTION_REVERSE: + value = self._config.get(CONF_FAN_DIRECTION_REV) + await self._device.set_dp( - DIRECTION_TO_TUYA[direction], self._config.get(CONF_FAN_DIRECTION) + value, self._config.get(CONF_FAN_DIRECTION) ) self.schedule_update_ha_state() @@ -164,7 +200,9 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): @property def speed_count(self) -> int: """Speed count for the fan.""" - return int_states_in_range(self._speed_range) + speed_count = int_states_in_range(self._speed_range) + _LOGGER.debug("Fan speed_count: %s", speed_count) + return speed_count def status_updated(self): @@ -172,25 +210,34 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._is_on = self.dps(self._dp_id) - if self.has_config(CONF_FAN_SPEED_COUNT): - value = int(self.dps_conf(CONF_FAN_SPEED_CONTROL)) - if value is not None: - self._percentage = ranged_value_to_percentage(self._speed_range, value) - else: - self.warning( - "%s/%s: Ignoring unknown fan controller state: %s", - self.name, - self.entity_id, - value, - ) + current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) + if self._use_ordered_list: + _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) + if current_speed is not None: + self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) + + else: + _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", current_speed, self._speed_range) + if current_speed is not None: + self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) + + _LOGGER.debug("Fan current_percentage: %s", self._percentage) if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) + _LOGGER.debug("Fan current_oscillating : %s", self._oscillating) + if self.has_config(CONF_FAN_DIRECTION): value = self.dps_conf(CONF_FAN_DIRECTION) if value is not None: - self._direction = TUYA_DIRECTION_TO_HA[value] - + if value == self._config.get(CONF_FAN_DIRECTION_FWD): + self._direction = DIRECTION_FORWARD + + if value == self._config.get(CONF_FAN_DIRECTION_REV): + self._direction = DIRECTION_REVERSE + + _LOGGER.debug("Fan current_direction : %s > %s > %s", value, self._direction, self._config.get(CONF_FAN_DIRECTION_FWD)) + async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From 5cbf556b9136fdacbc435bc8b023b5fd3086518a Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 19 Aug 2021 19:59:20 +0930 Subject: [PATCH 09/65] remove comments --- custom_components/localtuya/fan.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 4e2af6b..b4dad12 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -1,9 +1,3 @@ -# DPS [1] VALUE [True] --> fan on,off > True, False -# DPS [3] VALUE [6] --> fan speed > 1-6 -# DPS [4] VALUE [forward] --> fan direction > forward, reverse -# DPS [102] VALUE [normal] --> preset mode > normal, sleep, nature -# DPS [103] VALUE [off] --> timer > off, 1hour, 2hour, 4hour, 8hour - """Platform to locally control Tuya-based fan devices.""" import logging from functools import partial @@ -237,7 +231,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if value == self._config.get(CONF_FAN_DIRECTION_REV): self._direction = DIRECTION_REVERSE - _LOGGER.debug("Fan current_direction : %s > %s > %s", value, self._direction, self._config.get(CONF_FAN_DIRECTION_FWD)) + _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From 81967039c246646e6ae6900e8417c91b63158222 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 2 Sep 2021 10:35:34 +0930 Subject: [PATCH 10/65] fixed logger error --- custom_components/localtuya/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index b4dad12..e1e2b1f 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -149,7 +149,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): )), self._config.get(CONF_FAN_SPEED_CONTROL) ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._ordered_list, percentage)) + _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) async def async_oscillate(self, oscillating: bool) -> None: From 3eacbbda5a8bbcf5b877661c7120912b056417cb Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 5 Oct 2021 15:57:42 +1030 Subject: [PATCH 11/65] spelling error --- custom_components/localtuya/translations/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index de40762..2cb903e 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -77,7 +77,7 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverser dps string" + "fan_direction_reverse": "reverse dps string" } } } @@ -132,7 +132,7 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverser dps string" + "fan_direction_reverse": "reverse dps string" } }, "yaml_import": { From 3fca5566bb38d5dfb4131a093a3672b61743faf5 Mon Sep 17 00:00:00 2001 From: Prasad Bankar <22224770+psbankar@users.noreply.github.com> Date: Wed, 6 Oct 2021 21:37:17 +0530 Subject: [PATCH 12/65] Update fan.py Changes: Changed set_speed in turn_on method Changed set_percentage method for better flow Logger changes --- custom_components/localtuya/fan.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index e1e2b1f..43377c9 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -104,35 +104,33 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Return the current percentage.""" return self._percentage - async def async_turn_on(self, speed: str = None, **kwargs) -> None: + async def async_turn_on(self, percentage: str = None, **kwargs) -> None: """Turn on the entity.""" _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) - if speed is not None: - await self.async_set_speed(speed) + if percentage is not None: + await self.async_set_percentage(speed) else: self.schedule_update_ha_state() async def async_turn_off(self, **kwargs) -> None: """Turn off the entity.""" - - _LOGGER.debug("Fan async_turn_on") - + _LOGGER.debug("Fan async_turn_off") + await self._device.set_dp(False, self._dp_id) self.schedule_update_ha_state() async def async_set_percentage(self, percentage): """Set the speed of the fan.""" - - _LOGGER.debug("Fan async_set_percentage: %s", percentage) - - if percentage == 0: - return await self.async_turn_off() - if not self.is_on: - await self.async_turn_on() + _LOGGER.debug("Fan async_set_percentage: %s", percentage) + if percentage is not None: + if percentage == 0: + return await self.async_turn_off() + elif not self.is_on: + await self.async_turn_on() if self._use_ordered_list: await self._device.set_dp( str( @@ -150,6 +148,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._config.get(CONF_FAN_SPEED_CONTROL) ) _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) + self.schedule_update_ha_state() async def async_oscillate(self, oscillating: bool) -> None: @@ -230,8 +229,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if value == self._config.get(CONF_FAN_DIRECTION_REV): self._direction = DIRECTION_REVERSE - - _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) + _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From ec223ba73d35d7ea2de64eff11ca647468fb43ae Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 26 Oct 2021 15:32:23 +1030 Subject: [PATCH 13/65] add presets and integer/string option --- custom_components/localtuya/const.py | 4 +- custom_components/localtuya/fan.py | 111 +++++++++++++----- .../localtuya/translations/en.json | 10 +- 3 files changed, 91 insertions(+), 34 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index 9662b3b..3d3ac05 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -40,7 +40,9 @@ CONF_FAN_ORDERED_LIST = "fan_speed_ordered_list" CONF_FAN_DIRECTION = "fan_direction" CONF_FAN_DIRECTION_FWD = "fan_direction_forward" CONF_FAN_DIRECTION_REV = "fan_direction_reverse" - +CONF_FAN_SPEED_DPS_TYPE = "fan_speed_dps_type" +CONF_FAN_PRESET_CONTROL = "fan_preset_control" +CONF_FAN_PRESET_LIST = "fan_preset_list" # sensor CONF_SCALING = "scaling" diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 43377c9..4693764 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -24,7 +24,8 @@ from .const import ( CONF_FAN_DIRECTION_REV, CONF_FAN_SPEED_MIN, CONF_FAN_SPEED_MAX, - CONF_FAN_ORDERED_LIST + CONF_FAN_ORDERED_LIST, + CONF_FAN_SPEED_DPS_TYPE ) from homeassistant.util.percentage import ( @@ -44,11 +45,15 @@ def flow_schema(dps): vol.Optional(CONF_FAN_SPEED_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_OSCILLATING_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION): vol.In(dps), + vol.Optional(CONF_FAN_PRESET_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION_FWD, default="forward"): cv.string, vol.Optional(CONF_FAN_DIRECTION_REV, default="reverse"): cv.string, vol.Optional(CONF_FAN_SPEED_MIN, default=1): cv.positive_int, vol.Optional(CONF_FAN_SPEED_MAX, default=9): cv.positive_int, - vol.Optional(CONF_FAN_ORDERED_LIST, default="disabled"): cv.string, + vol.Optional(CONF_FAN_ORDERED_LIST): cv.string, + vol.Optional(CONF_FAN_PRESET_LIST): cv.string, + vol.Optional(CONF_FAN_SPEED_DPS_TYPE, default="string"): vol.In( + ["string", "integer", "list"]), } @@ -68,21 +73,21 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._oscillating = None self._direction = None self._percentage = None + self._preset = None self._speed_range = ( self._config.get(CONF_FAN_SPEED_MIN), self._config.get(CONF_FAN_SPEED_MAX), ) - self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") - self._ordered_list_mode = None - - if (type(self._ordered_list) is list and len(self._ordered_list) > 1): - self._use_ordered_list = True - _LOGGER.debug("Fan _use_ordered_list: %s > %s", self._use_ordered_list, self._ordered_list) - - else: - self._use_ordered_list = False - _LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list) + self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).replace(" ","").split(",") + self._preset_list = self._config.get(CONF_FAN_PRESET_LIST).replace(" ","").split(",") + self._ordered_speed_dps_type = self._config.get(CONF_FAN_SPEED_DPS_TYPE) + if (self._ordered_speed_dps_type == "list" and type(self._ordered_list) is list + and len(self._ordered_list) > 1): + _LOGGER.debug("Fan _use_ordered_list: %s", self._ordered_list) + else: + _LOGGER.debug("Fan _use_ordered_list: Not a valid list") + @property def oscillating(self): @@ -125,13 +130,32 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan async_set_percentage: %s", percentage) - + if percentage is not None: if percentage == 0: return await self.async_turn_off() elif not self.is_on: await self.async_turn_on() - if self._use_ordered_list: + + if self._ordered_speed_dps_type == "string": + await self._device.set_dp( + str(math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + )), + self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) + + elif self._ordered_speed_dps_type == "integer": + await self._device.set_dp( + int(math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + )), + self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) + + elif self._ordered_speed_dps_type == "list": await self._device.set_dp( str( percentage_to_ordered_list_item(self._ordered_list, percentage) @@ -140,14 +164,23 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): ) _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) - else: - await self._device.set_dp( - str(math.ceil( - percentage_to_ranged_value(self._speed_range, percentage) - )), - self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) + # if self._use_ordered_list: + # await self._device.set_dp( + # str( + # percentage_to_ordered_list_item(self._ordered_list, percentage) + # ), + # self._config.get(CONF_FAN_SPEED_CONTROL) + # ) + # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) + + # else: + # await self._device.set_dp( + # str(math.ceil( + # percentage_to_ranged_value(self._speed_range, percentage) + # )), + # self._config.get(CONF_FAN_SPEED_CONTROL) + # ) + # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) self.schedule_update_ha_state() @@ -172,7 +205,15 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): await self._device.set_dp( value, self._config.get(CONF_FAN_DIRECTION) ) - self.schedule_update_ha_state() + self.schedule_update_ha_state() + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode of the fan.""" + _LOGGER.debug("Fan set preset: %s", preset_mode) + await self._device.set_dp( + preset_mode, self._config.get(CONF_FAN_PRESET_CONTROL) + ) + self.schedule_update_ha_state() @property def supported_features(self) -> int: @@ -188,6 +229,9 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if self.has_config(CONF_FAN_DIRECTION): features |= SUPPORT_DIRECTION + if self.has_config(CONF_FAN_PRESET_CONTROL): + features |= SUPPORT_PRESET_MODE + return features @property @@ -203,18 +247,23 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._is_on = self.dps(self._dp_id) + # check for speed and preset. current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) - if self._use_ordered_list: - _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) - if current_speed is not None: + if current_speed is not None: + + if current_speed in self._preset_list: + _LOGGER.debug("Fan current_speed in preset list: %s from %s", current_speed, self._preset_list) + self._preset = current_speed + + if self._ordered_speed_dps_type == "list": + _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) - - else: - _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", current_speed, self._speed_range) - if current_speed is not None: + + elif self._ordered_speed_dps_type == "string" or self._ordered_speed_dps_type == "integer" : + _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", current_speed, self._speed_range) self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) - _LOGGER.debug("Fan current_percentage: %s", self._percentage) + _LOGGER.debug("Fan current_percentage: %s", self._percentage) if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 2cb903e..e465784 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -77,7 +77,10 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string" + "fan_direction_reverse": "reverse dps string", + "fan_speed_dps_type": "type of speed dps control. integer/string/list", + "fan_preset_control": "Fan preset dps. CAn be the same as speed", + "fan_preset_list": "FAn preset list. Comma separated" } } } @@ -132,7 +135,10 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string" + "fan_direction_reverse": "reverse dps string", + "fan_speed_dps_type": "type of speed dps control. integer/string/list", + "fan_preset_control": "Fan preset dps. CAn be the same as speed", + "fan_preset_list": "FAn preset list. Comma separated" } }, "yaml_import": { From 6f281a08fc0825e6b718f251b7a36ad30c88767f Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 26 Oct 2021 15:35:18 +1030 Subject: [PATCH 14/65] missing imports --- custom_components/localtuya/fan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 4693764..4aa2f45 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -12,6 +12,7 @@ from homeassistant.components.fan import ( SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, + SUPPORT_PRESET_MODE, FanEntity, ) @@ -25,7 +26,8 @@ from .const import ( CONF_FAN_SPEED_MIN, CONF_FAN_SPEED_MAX, CONF_FAN_ORDERED_LIST, - CONF_FAN_SPEED_DPS_TYPE + CONF_FAN_SPEED_DPS_TYPE, + CONF_FAN_PRESET_LIST ) from homeassistant.util.percentage import ( From 0c59ddc7e0e008de8236658eae51ccf8ac56c485 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 26 Oct 2021 16:09:39 +1030 Subject: [PATCH 15/65] correctly split preset and speed in updates --- custom_components/localtuya/fan.py | 35 ++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 4aa2f45..89024c3 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -20,6 +20,7 @@ from .common import LocalTuyaEntity, async_setup_entry from .const import ( CONF_FAN_OSCILLATING_CONTROL, CONF_FAN_SPEED_CONTROL, + CONF_FAN_PRESET_CONTROL, CONF_FAN_DIRECTION, CONF_FAN_DIRECTION_FWD, CONF_FAN_DIRECTION_REV, @@ -84,7 +85,8 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._preset_list = self._config.get(CONF_FAN_PRESET_LIST).replace(" ","").split(",") self._ordered_speed_dps_type = self._config.get(CONF_FAN_SPEED_DPS_TYPE) - if (self._ordered_speed_dps_type == "list" and type(self._ordered_list) is list + if (self._ordered_speed_dps_type == "list" + and type(self._ordered_list) is list and len(self._ordered_list) > 1): _LOGGER.debug("Fan _use_ordered_list: %s", self._ordered_list) else: @@ -166,23 +168,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): ) _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) - # if self._use_ordered_list: - # await self._device.set_dp( - # str( - # percentage_to_ordered_list_item(self._ordered_list, percentage) - # ), - # self._config.get(CONF_FAN_SPEED_CONTROL) - # ) - # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) - - # else: - # await self._device.set_dp( - # str(math.ceil( - # percentage_to_ranged_value(self._speed_range, percentage) - # )), - # self._config.get(CONF_FAN_SPEED_CONTROL) - # ) - # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) self.schedule_update_ha_state() @@ -249,15 +234,22 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._is_on = self.dps(self._dp_id) - # check for speed and preset. + if self.has_config(CONF_FAN_PRESET_CONTROL): + current_preset = self.dps_conf(CONF_FAN_PRESET_CONTROL) + if current_preset is not None and current_preset in self._preset_list: + _LOGGER.debug("Fan current_preset in preset list: %s from %s", current_preset, self._preset_list) + self._preset = current_preset + current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) if current_speed is not None: - if current_speed in self._preset_list: + if (self.has_config(CONF_FAN_PRESET_CONTROL) + and (CONF_FAN_SPEED_CONTROL == CONF_FAN_PRESET_CONTROL) + and (current_speed in self._preset_list)): _LOGGER.debug("Fan current_speed in preset list: %s from %s", current_speed, self._preset_list) self._preset = current_speed - if self._ordered_speed_dps_type == "list": + elif self._ordered_speed_dps_type == "list": _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) @@ -266,6 +258,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) _LOGGER.debug("Fan current_percentage: %s", self._percentage) + _LOGGER.debug("Fan current_preset: %s", self._preset) if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) From 7ac4fe5a119c7b1f6ff9c0b835e273a57b3b7362 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 26 Oct 2021 21:41:51 +1030 Subject: [PATCH 16/65] Revert "correctly split preset and speed in updates" This reverts commit 0c59ddc7e0e008de8236658eae51ccf8ac56c485. --- custom_components/localtuya/fan.py | 35 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 89024c3..4aa2f45 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -20,7 +20,6 @@ from .common import LocalTuyaEntity, async_setup_entry from .const import ( CONF_FAN_OSCILLATING_CONTROL, CONF_FAN_SPEED_CONTROL, - CONF_FAN_PRESET_CONTROL, CONF_FAN_DIRECTION, CONF_FAN_DIRECTION_FWD, CONF_FAN_DIRECTION_REV, @@ -85,8 +84,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._preset_list = self._config.get(CONF_FAN_PRESET_LIST).replace(" ","").split(",") self._ordered_speed_dps_type = self._config.get(CONF_FAN_SPEED_DPS_TYPE) - if (self._ordered_speed_dps_type == "list" - and type(self._ordered_list) is list + if (self._ordered_speed_dps_type == "list" and type(self._ordered_list) is list and len(self._ordered_list) > 1): _LOGGER.debug("Fan _use_ordered_list: %s", self._ordered_list) else: @@ -168,6 +166,23 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): ) _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) + # if self._use_ordered_list: + # await self._device.set_dp( + # str( + # percentage_to_ordered_list_item(self._ordered_list, percentage) + # ), + # self._config.get(CONF_FAN_SPEED_CONTROL) + # ) + # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) + + # else: + # await self._device.set_dp( + # str(math.ceil( + # percentage_to_ranged_value(self._speed_range, percentage) + # )), + # self._config.get(CONF_FAN_SPEED_CONTROL) + # ) + # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) self.schedule_update_ha_state() @@ -234,22 +249,15 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._is_on = self.dps(self._dp_id) - if self.has_config(CONF_FAN_PRESET_CONTROL): - current_preset = self.dps_conf(CONF_FAN_PRESET_CONTROL) - if current_preset is not None and current_preset in self._preset_list: - _LOGGER.debug("Fan current_preset in preset list: %s from %s", current_preset, self._preset_list) - self._preset = current_preset - + # check for speed and preset. current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) if current_speed is not None: - if (self.has_config(CONF_FAN_PRESET_CONTROL) - and (CONF_FAN_SPEED_CONTROL == CONF_FAN_PRESET_CONTROL) - and (current_speed in self._preset_list)): + if current_speed in self._preset_list: _LOGGER.debug("Fan current_speed in preset list: %s from %s", current_speed, self._preset_list) self._preset = current_speed - elif self._ordered_speed_dps_type == "list": + if self._ordered_speed_dps_type == "list": _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) @@ -258,7 +266,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) _LOGGER.debug("Fan current_percentage: %s", self._percentage) - _LOGGER.debug("Fan current_preset: %s", self._preset) if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) From 0f43923c2e3d9c652306909313db170b7fa7f0e0 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 26 Oct 2021 21:41:56 +1030 Subject: [PATCH 17/65] Revert "missing imports" This reverts commit 6f281a08fc0825e6b718f251b7a36ad30c88767f. --- custom_components/localtuya/fan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 4aa2f45..4693764 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -12,7 +12,6 @@ from homeassistant.components.fan import ( SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, - SUPPORT_PRESET_MODE, FanEntity, ) @@ -26,8 +25,7 @@ from .const import ( CONF_FAN_SPEED_MIN, CONF_FAN_SPEED_MAX, CONF_FAN_ORDERED_LIST, - CONF_FAN_SPEED_DPS_TYPE, - CONF_FAN_PRESET_LIST + CONF_FAN_SPEED_DPS_TYPE ) from homeassistant.util.percentage import ( From c8e660dfac689440a4bcbd3a68c45af39b775451 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 26 Oct 2021 21:41:59 +1030 Subject: [PATCH 18/65] Revert "add presets and integer/string option" This reverts commit ec223ba73d35d7ea2de64eff11ca647468fb43ae. --- custom_components/localtuya/const.py | 4 +- custom_components/localtuya/fan.py | 111 +++++------------- .../localtuya/translations/en.json | 10 +- 3 files changed, 34 insertions(+), 91 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index 3d3ac05..9662b3b 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -40,9 +40,7 @@ CONF_FAN_ORDERED_LIST = "fan_speed_ordered_list" CONF_FAN_DIRECTION = "fan_direction" CONF_FAN_DIRECTION_FWD = "fan_direction_forward" CONF_FAN_DIRECTION_REV = "fan_direction_reverse" -CONF_FAN_SPEED_DPS_TYPE = "fan_speed_dps_type" -CONF_FAN_PRESET_CONTROL = "fan_preset_control" -CONF_FAN_PRESET_LIST = "fan_preset_list" + # sensor CONF_SCALING = "scaling" diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 4693764..43377c9 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -24,8 +24,7 @@ from .const import ( CONF_FAN_DIRECTION_REV, CONF_FAN_SPEED_MIN, CONF_FAN_SPEED_MAX, - CONF_FAN_ORDERED_LIST, - CONF_FAN_SPEED_DPS_TYPE + CONF_FAN_ORDERED_LIST ) from homeassistant.util.percentage import ( @@ -45,15 +44,11 @@ def flow_schema(dps): vol.Optional(CONF_FAN_SPEED_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_OSCILLATING_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION): vol.In(dps), - vol.Optional(CONF_FAN_PRESET_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION_FWD, default="forward"): cv.string, vol.Optional(CONF_FAN_DIRECTION_REV, default="reverse"): cv.string, vol.Optional(CONF_FAN_SPEED_MIN, default=1): cv.positive_int, vol.Optional(CONF_FAN_SPEED_MAX, default=9): cv.positive_int, - vol.Optional(CONF_FAN_ORDERED_LIST): cv.string, - vol.Optional(CONF_FAN_PRESET_LIST): cv.string, - vol.Optional(CONF_FAN_SPEED_DPS_TYPE, default="string"): vol.In( - ["string", "integer", "list"]), + vol.Optional(CONF_FAN_ORDERED_LIST, default="disabled"): cv.string, } @@ -73,21 +68,21 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._oscillating = None self._direction = None self._percentage = None - self._preset = None self._speed_range = ( self._config.get(CONF_FAN_SPEED_MIN), self._config.get(CONF_FAN_SPEED_MAX), ) - self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).replace(" ","").split(",") - self._preset_list = self._config.get(CONF_FAN_PRESET_LIST).replace(" ","").split(",") - self._ordered_speed_dps_type = self._config.get(CONF_FAN_SPEED_DPS_TYPE) - - if (self._ordered_speed_dps_type == "list" and type(self._ordered_list) is list - and len(self._ordered_list) > 1): - _LOGGER.debug("Fan _use_ordered_list: %s", self._ordered_list) + self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") + self._ordered_list_mode = None + + if (type(self._ordered_list) is list and len(self._ordered_list) > 1): + self._use_ordered_list = True + _LOGGER.debug("Fan _use_ordered_list: %s > %s", self._use_ordered_list, self._ordered_list) + else: - _LOGGER.debug("Fan _use_ordered_list: Not a valid list") - + self._use_ordered_list = False + _LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list) + @property def oscillating(self): @@ -130,32 +125,13 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan async_set_percentage: %s", percentage) - + if percentage is not None: if percentage == 0: return await self.async_turn_off() elif not self.is_on: await self.async_turn_on() - - if self._ordered_speed_dps_type == "string": - await self._device.set_dp( - str(math.ceil( - percentage_to_ranged_value(self._speed_range, percentage) - )), - self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) - - elif self._ordered_speed_dps_type == "integer": - await self._device.set_dp( - int(math.ceil( - percentage_to_ranged_value(self._speed_range, percentage) - )), - self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) - - elif self._ordered_speed_dps_type == "list": + if self._use_ordered_list: await self._device.set_dp( str( percentage_to_ordered_list_item(self._ordered_list, percentage) @@ -164,23 +140,14 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): ) _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) - # if self._use_ordered_list: - # await self._device.set_dp( - # str( - # percentage_to_ordered_list_item(self._ordered_list, percentage) - # ), - # self._config.get(CONF_FAN_SPEED_CONTROL) - # ) - # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) - - # else: - # await self._device.set_dp( - # str(math.ceil( - # percentage_to_ranged_value(self._speed_range, percentage) - # )), - # self._config.get(CONF_FAN_SPEED_CONTROL) - # ) - # _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) + else: + await self._device.set_dp( + str(math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + )), + self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) self.schedule_update_ha_state() @@ -205,15 +172,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): await self._device.set_dp( value, self._config.get(CONF_FAN_DIRECTION) ) - self.schedule_update_ha_state() - - async def async_set_preset_mode(self, preset_mode: str) -> None: - """Set the preset mode of the fan.""" - _LOGGER.debug("Fan set preset: %s", preset_mode) - await self._device.set_dp( - preset_mode, self._config.get(CONF_FAN_PRESET_CONTROL) - ) - self.schedule_update_ha_state() + self.schedule_update_ha_state() @property def supported_features(self) -> int: @@ -229,9 +188,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if self.has_config(CONF_FAN_DIRECTION): features |= SUPPORT_DIRECTION - if self.has_config(CONF_FAN_PRESET_CONTROL): - features |= SUPPORT_PRESET_MODE - return features @property @@ -247,23 +203,18 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._is_on = self.dps(self._dp_id) - # check for speed and preset. current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) - if current_speed is not None: - - if current_speed in self._preset_list: - _LOGGER.debug("Fan current_speed in preset list: %s from %s", current_speed, self._preset_list) - self._preset = current_speed - - if self._ordered_speed_dps_type == "list": - _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) + if self._use_ordered_list: + _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) + if current_speed is not None: self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) - - elif self._ordered_speed_dps_type == "string" or self._ordered_speed_dps_type == "integer" : - _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", current_speed, self._speed_range) + + else: + _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", current_speed, self._speed_range) + if current_speed is not None: self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) - _LOGGER.debug("Fan current_percentage: %s", self._percentage) + _LOGGER.debug("Fan current_percentage: %s", self._percentage) if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index e465784..2cb903e 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -77,10 +77,7 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string", - "fan_speed_dps_type": "type of speed dps control. integer/string/list", - "fan_preset_control": "Fan preset dps. CAn be the same as speed", - "fan_preset_list": "FAn preset list. Comma separated" + "fan_direction_reverse": "reverse dps string" } } } @@ -135,10 +132,7 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string", - "fan_speed_dps_type": "type of speed dps control. integer/string/list", - "fan_preset_control": "Fan preset dps. CAn be the same as speed", - "fan_preset_list": "FAn preset list. Comma separated" + "fan_direction_reverse": "reverse dps string" } }, "yaml_import": { From c99a93bd6c3f43ba17107720a97bd289a79c59e6 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 10 Nov 2021 06:59:58 +1030 Subject: [PATCH 19/65] Fix speed variable error --- custom_components/localtuya/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 43377c9..37ff370 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -109,7 +109,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) if percentage is not None: - await self.async_set_percentage(speed) + await self.async_set_percentage(percentage) else: self.schedule_update_ha_state() From 9bf9b9f55dc1b4942fdfcc48dc3d6524a7f0aabe Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 11:12:43 +1030 Subject: [PATCH 20/65] fix formatting errors --- .vscode/settings.json | 5 ++++ custom_components/localtuya/fan.py | 40 ++++++++++++++++-------------- 2 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a04b218 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.yaml": "home-assistant" + } +} \ No newline at end of file diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 37ff370..1a5b8d8 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -69,7 +69,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._direction = None self._percentage = None self._speed_range = ( - self._config.get(CONF_FAN_SPEED_MIN), + self._config.get(CONF_FAN_SPEED_MIN), self._config.get(CONF_FAN_SPEED_MAX), ) self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") @@ -77,12 +77,12 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if (type(self._ordered_list) is list and len(self._ordered_list) > 1): self._use_ordered_list = True - _LOGGER.debug("Fan _use_ordered_list: %s > %s", self._use_ordered_list, self._ordered_list) + _LOGGER.debug("Fan _use_ordered_list: %s > %s", + self._use_ordered_list, self._ordered_list) else: self._use_ordered_list = False _LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list) - @property def oscillating(self): @@ -125,7 +125,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan async_set_percentage: %s", percentage) - if percentage is not None: if percentage == 0: return await self.async_turn_off() @@ -137,8 +136,9 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): percentage_to_ordered_list_item(self._ordered_list, percentage) ), self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) + ) + _LOGGER.debug("Fan async_set_percentage: %s > %s", + percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) else: await self._device.set_dp( @@ -146,11 +146,11 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): percentage_to_ranged_value(self._speed_range, percentage) )), self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) + ) + _LOGGER.debug("Fan async_set_percentage: %s > %s", + percentage, percentage_to_ranged_value(self._speed_range, percentage)) self.schedule_update_ha_state() - async def async_oscillate(self, oscillating: bool) -> None: """Set oscillation.""" _LOGGER.debug("Fan async_oscillate: %s", oscillating) @@ -165,14 +165,14 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if direction == DIRECTION_FORWARD: value = self._config.get(CONF_FAN_DIRECTION_FWD) - + if direction == DIRECTION_REVERSE: value = self._config.get(CONF_FAN_DIRECTION_REV) await self._device.set_dp( value, self._config.get(CONF_FAN_DIRECTION) ) - self.schedule_update_ha_state() + self.schedule_update_ha_state() @property def supported_features(self) -> int: @@ -188,7 +188,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if self.has_config(CONF_FAN_DIRECTION): features |= SUPPORT_DIRECTION - return features + return features @property def speed_count(self) -> int: @@ -197,7 +197,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan speed_count: %s", speed_count) return speed_count - def status_updated(self): """Get state of Tuya fan.""" @@ -205,12 +204,17 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) if self._use_ordered_list: - _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) + _LOGGER.debug( + "Fan current_speed ordered_list_item_to_percentage: %s from %s", + current_speed, self._ordered_list + ) if current_speed is not None: - self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) + self._percentage = ordered_list_item_to_percentage( + self._ordered_list, current_speed) else: - _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", current_speed, self._speed_range) + _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", + current_speed, self._speed_range) if current_speed is not None: self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) @@ -220,7 +224,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) _LOGGER.debug("Fan current_oscillating : %s", self._oscillating) - if self.has_config(CONF_FAN_DIRECTION): value = self.dps_conf(CONF_FAN_DIRECTION) if value is not None: @@ -230,6 +233,5 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if value == self._config.get(CONF_FAN_DIRECTION_REV): self._direction = DIRECTION_REVERSE _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) - -async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) +async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) \ No newline at end of file From af9f11ad10707d1e1dbaf686ea930727cfedf8f5 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 11:12:43 +1030 Subject: [PATCH 21/65] fix formatting errors --- .vscode/settings.json | 5 ++ custom_components/localtuya/fan.py | 114 +++++++++++++++++------------ 2 files changed, 71 insertions(+), 48 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a04b218 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.yaml": "home-assistant" + } +} \ No newline at end of file diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 37ff370..51a2191 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -1,38 +1,37 @@ """Platform to locally control Tuya-based fan devices.""" import logging -from functools import partial import math +from functools import partial import homeassistant.helpers.config_validation as cv import voluptuous as vol from homeassistant.components.fan import ( - DOMAIN, DIRECTION_FORWARD, DIRECTION_REVERSE, + DOMAIN, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity, ) - -from .common import LocalTuyaEntity, async_setup_entry -from .const import ( - CONF_FAN_OSCILLATING_CONTROL, - CONF_FAN_SPEED_CONTROL, - CONF_FAN_DIRECTION, - CONF_FAN_DIRECTION_FWD, - CONF_FAN_DIRECTION_REV, - CONF_FAN_SPEED_MIN, - CONF_FAN_SPEED_MAX, - CONF_FAN_ORDERED_LIST -) - from homeassistant.util.percentage import ( + int_states_in_range, ordered_list_item_to_percentage, percentage_to_ordered_list_item, percentage_to_ranged_value, ranged_value_to_percentage, - int_states_in_range +) + +from .common import LocalTuyaEntity, async_setup_entry +from .const import ( + CONF_FAN_DIRECTION, + CONF_FAN_DIRECTION_FWD, + CONF_FAN_DIRECTION_REV, + CONF_FAN_ORDERED_LIST, + CONF_FAN_OSCILLATING_CONTROL, + CONF_FAN_SPEED_CONTROL, + CONF_FAN_SPEED_MAX, + CONF_FAN_SPEED_MIN, ) _LOGGER = logging.getLogger(__name__) @@ -69,20 +68,23 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._direction = None self._percentage = None self._speed_range = ( - self._config.get(CONF_FAN_SPEED_MIN), + self._config.get(CONF_FAN_SPEED_MIN), self._config.get(CONF_FAN_SPEED_MAX), ) self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") self._ordered_list_mode = None - if (type(self._ordered_list) is list and len(self._ordered_list) > 1): + if type(self._ordered_list) is list and len(self._ordered_list) > 1: self._use_ordered_list = True - _LOGGER.debug("Fan _use_ordered_list: %s > %s", self._use_ordered_list, self._ordered_list) + _LOGGER.debug( + "Fan _use_ordered_list: %s > %s", + self._use_ordered_list, + self._ordered_list, + ) else: self._use_ordered_list = False _LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list) - @property def oscillating(self): @@ -125,7 +127,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan async_set_percentage: %s", percentage) - if percentage is not None: if percentage == 0: return await self.async_turn_off() @@ -135,22 +136,31 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): await self._device.set_dp( str( percentage_to_ordered_list_item(self._ordered_list, percentage) - ), - self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ordered_list_item(self._ordered_list, percentage)) + ), + self._config.get(CONF_FAN_SPEED_CONTROL), + ) + _LOGGER.debug( + "Fan async_set_percentage: %s > %s", + percentage, + percentage_to_ordered_list_item(self._ordered_list, percentage), + ) else: await self._device.set_dp( - str(math.ceil( - percentage_to_ranged_value(self._speed_range, percentage) - )), - self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug("Fan async_set_percentage: %s > %s", percentage, percentage_to_ranged_value(self._speed_range, percentage)) + str( + math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + ) + ), + self._config.get(CONF_FAN_SPEED_CONTROL), + ) + _LOGGER.debug( + "Fan async_set_percentage: %s > %s", + percentage, + percentage_to_ranged_value(self._speed_range, percentage), + ) self.schedule_update_ha_state() - async def async_oscillate(self, oscillating: bool) -> None: """Set oscillation.""" _LOGGER.debug("Fan async_oscillate: %s", oscillating) @@ -165,14 +175,12 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if direction == DIRECTION_FORWARD: value = self._config.get(CONF_FAN_DIRECTION_FWD) - + if direction == DIRECTION_REVERSE: value = self._config.get(CONF_FAN_DIRECTION_REV) - await self._device.set_dp( - value, self._config.get(CONF_FAN_DIRECTION) - ) - self.schedule_update_ha_state() + await self._device.set_dp(value, self._config.get(CONF_FAN_DIRECTION)) + self.schedule_update_ha_state() @property def supported_features(self) -> int: @@ -188,8 +196,8 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if self.has_config(CONF_FAN_DIRECTION): features |= SUPPORT_DIRECTION - return features - + return features + @property def speed_count(self) -> int: """Speed count for the fan.""" @@ -197,7 +205,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan speed_count: %s", speed_count) return speed_count - def status_updated(self): """Get state of Tuya fan.""" @@ -205,14 +212,26 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) if self._use_ordered_list: - _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", current_speed, self._ordered_list) + _LOGGER.debug( + "Fan current_speed ordered_list_item_to_percentage: %s from %s", + current_speed, + self._ordered_list, + ) if current_speed is not None: - self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) - + self._percentage = ordered_list_item_to_percentage( + self._ordered_list, current_speed + ) + else: - _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", current_speed, self._speed_range) + _LOGGER.debug( + "Fan current_speed ranged_value_to_percentage: %s from %s", + current_speed, + self._speed_range, + ) if current_speed is not None: - self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) + self._percentage = ranged_value_to_percentage( + self._speed_range, int(current_speed) + ) _LOGGER.debug("Fan current_percentage: %s", self._percentage) @@ -220,16 +239,15 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) _LOGGER.debug("Fan current_oscillating : %s", self._oscillating) - if self.has_config(CONF_FAN_DIRECTION): value = self.dps_conf(CONF_FAN_DIRECTION) if value is not None: if value == self._config.get(CONF_FAN_DIRECTION_FWD): self._direction = DIRECTION_FORWARD - + if value == self._config.get(CONF_FAN_DIRECTION_REV): self._direction = DIRECTION_REVERSE _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) - + async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From 77f91590048b0e5b35aca36ce0628d9d092c24dc Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 11:54:26 +1030 Subject: [PATCH 22/65] workflow_dispatch --- .github/workflows/tox.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 40ca8d9..8fc6768 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -1,6 +1,6 @@ name: Tox PR CI -on: [pull_request] +on: [pull_request, workflow_dispatch] jobs: build: From eda45fc896f52bcb357977d316901fef9631d21f Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 12:14:49 +1030 Subject: [PATCH 23/65] fix tox errors --- custom_components/localtuya/fan.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 1a5b8d8..e02fd25 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -122,7 +122,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): async def async_set_percentage(self, percentage): """Set the speed of the fan.""" - _LOGGER.debug("Fan async_set_percentage: %s", percentage) if percentage is not None: @@ -199,7 +198,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): def status_updated(self): """Get state of Tuya fan.""" - self._is_on = self.dps(self._dp_id) current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) From f1753945ca5f6d26a6dc5fc281593e015a0106ce Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 13:08:32 +1030 Subject: [PATCH 24/65] checks updates and tox fixes --- requirements_test.txt | 6 +++--- tox.ini | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index df62e77..8a2a53d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,9 +1,9 @@ black==21.4b0 codespell==2.0.0 flake8==3.9.2 -mypy==0.901 +mypy==0.910 pydocstyle==6.1.1 cryptography==3.2 -pylint==2.8.2 +pylint==2.11.1 pylint-strict-informational==0.1 -homeassistant==2021.1.4 +homeassistant==2021.11.2 diff --git a/tox.ini b/tox.ini index 49ccccb..75c6e56 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] skipsdist = true -envlist = py{37,38}, lint, typing +envlist = py{38,39}, lint, typing skip_missing_interpreters = True cs_exclude_words = hass,unvalid [gh-actions] python = - 3.7: clean, py37, lint, typing 3.8: clean, py38, lint, typing + 3.9: clean, py38, lint, typing [testenv] passenv = TOXENV CI From c30c68dbf540a70691845b0754853bf83cb0ad96 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 13:09:01 +1030 Subject: [PATCH 25/65] tox --- .github/workflows/tox.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 8fc6768..67c0209 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -14,8 +14,8 @@ jobs: platform: - ubuntu-latest python-version: - - 3.7 - 3.8 + - 3.9 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 01b9b00050c5970654aab7a2d03959fba169a074 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 13:12:31 +1030 Subject: [PATCH 26/65] fix requirements --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8a2a53d..e65a9a5 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -3,7 +3,7 @@ codespell==2.0.0 flake8==3.9.2 mypy==0.910 pydocstyle==6.1.1 -cryptography==3.2 +cryptography==3.4.8 pylint==2.11.1 pylint-strict-informational==0.1 homeassistant==2021.11.2 From 6e6cd0861fa79d89cc66cecf1cdf1bfa5cb5b214 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 13:15:09 +1030 Subject: [PATCH 27/65] fix tox errors --- custom_components/localtuya/fan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 31076a3..6a1e219 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -245,4 +245,5 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._direction = DIRECTION_REVERSE _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) -async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) \ No newline at end of file + +async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From 95ef271dab36fcd7b204aec952e15796d895f7fc Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 13:50:18 +1030 Subject: [PATCH 28/65] more tox fixes --- custom_components/localtuya/fan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 6a1e219..51d9d5e 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -74,7 +74,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") self._ordered_list_mode = None - if type(self._ordered_list) is list and len(self._ordered_list) > 1: + if isinstance(self._ordered_list, list) and len(self._ordered_list) > 1: self._use_ordered_list = True _LOGGER.debug( "Fan _use_ordered_list: %s > %s", @@ -105,7 +105,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Return the current percentage.""" return self._percentage - async def async_turn_on(self, percentage: str = None, **kwargs) -> None: + async def async_turn_on(self, percentage: str = None, preset_mode: str = None, **kwargs: Any) -> None: """Turn on the entity.""" _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) From a171430cc877051582196a3456ecdaecdca864ec Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 13:53:51 +1030 Subject: [PATCH 29/65] fix error --- custom_components/localtuya/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 51d9d5e..17f0c32 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -105,7 +105,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Return the current percentage.""" return self._percentage - async def async_turn_on(self, percentage: str = None, preset_mode: str = None, **kwargs: Any) -> None: + async def async_turn_on(self, percentage: str = None, preset_mode: str = None, **kwargs) -> None: """Turn on the entity.""" _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) From e55cd2013faf9d37924cda0b51bf7c4d4283630d Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 13:57:18 +1030 Subject: [PATCH 30/65] line length issue --- custom_components/localtuya/fan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 17f0c32..553f577 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -105,7 +105,8 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Return the current percentage.""" return self._percentage - async def async_turn_on(self, percentage: str = None, preset_mode: str = None, **kwargs) -> None: + async def async_turn_on(self, percentage: str = None, + preset_mode: str = None, **kwargs) -> None: """Turn on the entity.""" _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) From 8d5da3d73f7d3cbbca197afbd3bc1b3d8e516be3 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 21:34:00 +1030 Subject: [PATCH 31/65] remove dev tool changes fro branch --- .github/workflows/tox.yaml | 4 ++-- .vscode/settings.json | 5 ----- requirements_test.txt | 8 ++++---- tox.ini | 6 +++--- 4 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 67c0209..40ca8d9 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -1,6 +1,6 @@ name: Tox PR CI -on: [pull_request, workflow_dispatch] +on: [pull_request] jobs: build: @@ -14,8 +14,8 @@ jobs: platform: - ubuntu-latest python-version: + - 3.7 - 3.8 - - 3.9 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a04b218..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "files.associations": { - "*.yaml": "home-assistant" - } -} \ No newline at end of file diff --git a/requirements_test.txt b/requirements_test.txt index e65a9a5..d388fd6 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,9 +1,9 @@ black==21.4b0 codespell==2.0.0 flake8==3.9.2 -mypy==0.910 +mypy==0.901 pydocstyle==6.1.1 -cryptography==3.4.8 -pylint==2.11.1 +cryptography==3.2 +pylint==2.8.2 pylint-strict-informational==0.1 -homeassistant==2021.11.2 +homeassistant==2021.1.4 \ No newline at end of file diff --git a/tox.ini b/tox.ini index 75c6e56..1c72213 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] skipsdist = true -envlist = py{38,39}, lint, typing +envlist = py{37,38}, lint, typing skip_missing_interpreters = True cs_exclude_words = hass,unvalid [gh-actions] python = + 3.7: clean, py37, lint, typing 3.8: clean, py38, lint, typing - 3.9: clean, py38, lint, typing [testenv] passenv = TOXENV CI @@ -35,4 +35,4 @@ commands = [testenv:typing] commands = - mypy --ignore-missing-imports --follow-imports=skip custom_components + mypy --ignore-missing-imports --follow-imports=skip custom_components \ No newline at end of file From c9c96ad6359c9a2c3fb15276e35a8ab260ac672f Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 21:37:57 +1030 Subject: [PATCH 32/65] revert dev tool config files for master branch --- requirements_test.txt | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index d388fd6..df62e77 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,4 +6,4 @@ pydocstyle==6.1.1 cryptography==3.2 pylint==2.8.2 pylint-strict-informational==0.1 -homeassistant==2021.1.4 \ No newline at end of file +homeassistant==2021.1.4 diff --git a/tox.ini b/tox.ini index 1c72213..49ccccb 100644 --- a/tox.ini +++ b/tox.ini @@ -35,4 +35,4 @@ commands = [testenv:typing] commands = - mypy --ignore-missing-imports --follow-imports=skip custom_components \ No newline at end of file + mypy --ignore-missing-imports --follow-imports=skip custom_components From e979a4b5cda15670b9f7127c3eb651f54f507d02 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 22:02:36 +1030 Subject: [PATCH 33/65] update dev checker tools --- .github/workflows/tox.yaml | 4 ++-- requirements_test.txt | 8 ++++---- tox.ini | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 40ca8d9..67c0209 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -1,6 +1,6 @@ name: Tox PR CI -on: [pull_request] +on: [pull_request, workflow_dispatch] jobs: build: @@ -14,8 +14,8 @@ jobs: platform: - ubuntu-latest python-version: - - 3.7 - 3.8 + - 3.9 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/requirements_test.txt b/requirements_test.txt index df62e77..e65a9a5 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,9 +1,9 @@ black==21.4b0 codespell==2.0.0 flake8==3.9.2 -mypy==0.901 +mypy==0.910 pydocstyle==6.1.1 -cryptography==3.2 -pylint==2.8.2 +cryptography==3.4.8 +pylint==2.11.1 pylint-strict-informational==0.1 -homeassistant==2021.1.4 +homeassistant==2021.11.2 diff --git a/tox.ini b/tox.ini index 49ccccb..75c6e56 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] skipsdist = true -envlist = py{37,38}, lint, typing +envlist = py{38,39}, lint, typing skip_missing_interpreters = True cs_exclude_words = hass,unvalid [gh-actions] python = - 3.7: clean, py37, lint, typing 3.8: clean, py38, lint, typing + 3.9: clean, py38, lint, typing [testenv] passenv = TOXENV CI From 445b62efd8a247bacf89ebc5a425426808add541 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 22:11:34 +1030 Subject: [PATCH 34/65] tox.yaml --- .github/workflows/tox.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 67c0209..afa1588 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -1,6 +1,6 @@ name: Tox PR CI -on: [pull_request, workflow_dispatch] +on: [push, pull_request, workflow_dispatch] jobs: build: From 1c2f4ba0843c76deafe2b05efd21ea10501ce95e Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 22:30:57 +1030 Subject: [PATCH 35/65] Delete settings.json --- .vscode/settings.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a04b218..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "files.associations": { - "*.yaml": "home-assistant" - } -} \ No newline at end of file From e8aa5ac9a8852a0e0f971ef247676eb80c6fe8c7 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 22:46:55 +1030 Subject: [PATCH 36/65] integration inputs --- custom_components/localtuya/const.py | 3 + custom_components/localtuya/fan.py | 197 ++++++++++-------- .../localtuya/translations/en.json | 10 +- 3 files changed, 123 insertions(+), 87 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index 9662b3b..36db866 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -40,6 +40,9 @@ CONF_FAN_ORDERED_LIST = "fan_speed_ordered_list" CONF_FAN_DIRECTION = "fan_direction" CONF_FAN_DIRECTION_FWD = "fan_direction_forward" CONF_FAN_DIRECTION_REV = "fan_direction_reverse" +CONF_FAN_SPEED_DPS_TYPE = "fan_speed_dps_type" +CONF_FAN_PRESET_CONTROL = "fan_preset_control" +CONF_FAN_PRESET_LIST = "fan_preset_list" # sensor CONF_SCALING = "scaling" diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 553f577..efd2058 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -1,56 +1,63 @@ """Platform to locally control Tuya-based fan devices.""" import logging -import math from functools import partial +import math import homeassistant.helpers.config_validation as cv import voluptuous as vol from homeassistant.components.fan import ( + DOMAIN, DIRECTION_FORWARD, DIRECTION_REVERSE, - DOMAIN, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, + SUPPORT_PRESET_MODE, FanEntity, ) -from homeassistant.util.percentage import ( - int_states_in_range, - ordered_list_item_to_percentage, - percentage_to_ordered_list_item, - percentage_to_ranged_value, - ranged_value_to_percentage, -) from .common import LocalTuyaEntity, async_setup_entry from .const import ( + CONF_FAN_OSCILLATING_CONTROL, + CONF_FAN_SPEED_CONTROL, + CONF_FAN_PRESET_CONTROL, CONF_FAN_DIRECTION, CONF_FAN_DIRECTION_FWD, CONF_FAN_DIRECTION_REV, - CONF_FAN_ORDERED_LIST, - CONF_FAN_OSCILLATING_CONTROL, - CONF_FAN_SPEED_CONTROL, - CONF_FAN_SPEED_MAX, CONF_FAN_SPEED_MIN, + CONF_FAN_SPEED_MAX, + CONF_FAN_ORDERED_LIST, + CONF_FAN_SPEED_DPS_TYPE, + CONF_FAN_PRESET_LIST +) + +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, + percentage_to_ranged_value, + ranged_value_to_percentage, + int_states_in_range ) _LOGGER = logging.getLogger(__name__) - def flow_schema(dps): """Return schema used in config flow.""" return { vol.Optional(CONF_FAN_SPEED_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_OSCILLATING_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION): vol.In(dps), + vol.Optional(CONF_FAN_PRESET_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION_FWD, default="forward"): cv.string, vol.Optional(CONF_FAN_DIRECTION_REV, default="reverse"): cv.string, vol.Optional(CONF_FAN_SPEED_MIN, default=1): cv.positive_int, vol.Optional(CONF_FAN_SPEED_MAX, default=9): cv.positive_int, - vol.Optional(CONF_FAN_ORDERED_LIST, default="disabled"): cv.string, + vol.Optional(CONF_FAN_ORDERED_LIST): cv.string, + vol.Optional(CONF_FAN_PRESET_LIST): cv.string, + vol.Optional(CONF_FAN_SPEED_DPS_TYPE, default="string"): vol.In( + ["string", "integer", "list"]), } - class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Representation of a Tuya fan.""" @@ -67,24 +74,22 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._oscillating = None self._direction = None self._percentage = None + self._preset = None self._speed_range = ( - self._config.get(CONF_FAN_SPEED_MIN), + self._config.get(CONF_FAN_SPEED_MIN), self._config.get(CONF_FAN_SPEED_MAX), ) - self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") - self._ordered_list_mode = None - - if isinstance(self._ordered_list, list) and len(self._ordered_list) > 1: - self._use_ordered_list = True - _LOGGER.debug( - "Fan _use_ordered_list: %s > %s", - self._use_ordered_list, - self._ordered_list, - ) + self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).replace(" ","").split(",") + self._preset_list = self._config.get(CONF_FAN_PRESET_LIST).replace(" ","").split(",") + self._ordered_speed_dps_type = self._config.get(CONF_FAN_SPEED_DPS_TYPE) + + if (self._ordered_speed_dps_type == "list" + and isinstance(self._ordered_list, list) + and len(self._ordered_list) > 1): + _LOGGER.debug("Fan _use_ordered_list: %s", self._ordered_list) else: - self._use_ordered_list = False - _LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list) - + _LOGGER.debug("Fan _use_ordered_list: Not a valid list") + @property def oscillating(self): """Return current oscillating status.""" @@ -105,13 +110,12 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Return the current percentage.""" return self._percentage - async def async_turn_on(self, percentage: str = None, - preset_mode: str = None, **kwargs) -> None: + async def async_turn_on(self, percentage: str = None, **kwargs) -> None: """Turn on the entity.""" _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) if percentage is not None: - await self.async_set_percentage(percentage) + await self.async_set_percentage(speed) else: self.schedule_update_ha_state() @@ -124,42 +128,44 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): async def async_set_percentage(self, percentage): """Set the speed of the fan.""" - _LOGGER.debug("Fan async_set_percentage: %s", percentage) + _LOGGER.debug("Fan async_set_percentage: %s", percentage) + if percentage is not None: if percentage == 0: return await self.async_turn_off() - elif not self.is_on: + + if not self.is_on: await self.async_turn_on() - if self._use_ordered_list: + + if self._ordered_speed_dps_type == "string": + send_speed = str(math.ceil( + percentage_to_ranged_value(self._speed_range, percentage))) await self._device.set_dp( - str( - percentage_to_ordered_list_item(self._ordered_list, percentage) - ), - self._config.get(CONF_FAN_SPEED_CONTROL), - ) - _LOGGER.debug( - "Fan async_set_percentage: %s > %s", - percentage, - percentage_to_ordered_list_item(self._ordered_list, percentage), - ) + send_speed, self._config.get(CONF_FAN_SPEED_CONTROL)) + _LOGGER.debug("Fan async_set_percentage: %s > %s", + percentage, send_speed) + + elif self._ordered_speed_dps_type == "integer": + send_speed = int(math.ceil( + percentage_to_ranged_value(self._speed_range, percentage))) + await self._device.set_dp( + send_speed, self._config.get(CONF_FAN_SPEED_CONTROL)) + _LOGGER.debug("Fan async_set_percentage: %s > %s", + percentage, send_speed) - else: - await self._device.set_dp( - str( - math.ceil( - percentage_to_ranged_value(self._speed_range, percentage) + elif self._ordered_speed_dps_type == "list": + send_speed = str( + percentage_to_ordered_list_item(self._ordered_list, percentage) ) - ), - self._config.get(CONF_FAN_SPEED_CONTROL), - ) - _LOGGER.debug( - "Fan async_set_percentage: %s > %s", - percentage, - percentage_to_ranged_value(self._speed_range, percentage), - ) + await self._device.set_dp( + send_speed, self._config.get(CONF_FAN_SPEED_CONTROL)) + _LOGGER.debug("Fan async_set_percentage: %s > %s", + percentage, send_speed) + self.schedule_update_ha_state() + async def async_oscillate(self, oscillating: bool) -> None: """Set oscillation.""" _LOGGER.debug("Fan async_oscillate: %s", oscillating) @@ -174,10 +180,21 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if direction == DIRECTION_FORWARD: value = self._config.get(CONF_FAN_DIRECTION_FWD) - + if direction == DIRECTION_REVERSE: value = self._config.get(CONF_FAN_DIRECTION_REV) - await self._device.set_dp(value, self._config.get(CONF_FAN_DIRECTION)) + + await self._device.set_dp( + value, self._config.get(CONF_FAN_DIRECTION) + ) + self.schedule_update_ha_state() + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode of the fan.""" + _LOGGER.debug("Fan set preset: %s", preset_mode) + await self._device.set_dp( + preset_mode, self._config.get(CONF_FAN_PRESET_CONTROL) + ) self.schedule_update_ha_state() @property @@ -194,8 +211,11 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if self.has_config(CONF_FAN_DIRECTION): features |= SUPPORT_DIRECTION - return features + if self.has_config(CONF_FAN_PRESET_CONTROL): + features |= SUPPORT_PRESET_MODE + return features + @property def speed_count(self) -> int: """Speed count for the fan.""" @@ -203,34 +223,41 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan speed_count: %s", speed_count) return speed_count + def status_updated(self): """Get state of Tuya fan.""" + self._is_on = self.dps(self._dp_id) + if self.has_config(CONF_FAN_PRESET_CONTROL): + current_preset = self.dps_conf(CONF_FAN_PRESET_CONTROL) + if current_preset is not None and current_preset in self._preset_list: + _LOGGER.debug("Fan current_preset in preset list: %s from %s", + current_preset, self._preset_list) + self._preset = current_preset + current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) - if self._use_ordered_list: - _LOGGER.debug( - "Fan current_speed ordered_list_item_to_percentage: %s from %s", - current_speed, - self._ordered_list, - ) - if current_speed is not None: - self._percentage = ordered_list_item_to_percentage( - self._ordered_list, current_speed - ) + if current_speed is not None: - else: - _LOGGER.debug( - "Fan current_speed ranged_value_to_percentage: %s from %s", - current_speed, - self._speed_range, - ) - if current_speed is not None: - self._percentage = ranged_value_to_percentage( - self._speed_range, int(current_speed) - ) + if (self.has_config(CONF_FAN_PRESET_CONTROL) + and (CONF_FAN_SPEED_CONTROL == CONF_FAN_PRESET_CONTROL) + and (current_speed in self._preset_list)): + _LOGGER.debug("Fan current_speed in preset list: %s from %s", + current_speed, self._preset_list) + self._preset = current_speed - _LOGGER.debug("Fan current_percentage: %s", self._percentage) + elif self._ordered_speed_dps_type == "list": + _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", + current_speed, self._ordered_list) + self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) + + elif self._ordered_speed_dps_type == "string" or self._ordered_speed_dps_type == "integer" : + _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", + current_speed, self._speed_range) + self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) + + _LOGGER.debug("Fan current_percentage: %s", self._percentage) + _LOGGER.debug("Fan current_preset: %s", self._preset) if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) @@ -241,10 +268,10 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if value is not None: if value == self._config.get(CONF_FAN_DIRECTION_FWD): self._direction = DIRECTION_FORWARD - + if value == self._config.get(CONF_FAN_DIRECTION_REV): self._direction = DIRECTION_REVERSE _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) - + async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 2cb903e..2014b0b 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -77,7 +77,10 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string" + "fan_direction_reverse": "reverse dps string", + "fan_speed_dps_type": "type of speed dps control. integer/string/list", + "fan_preset_control": "Fan preset dps. Can be the same as speed", + "fan_preset_list": "Fan preset list. Comma separated" } } } @@ -132,7 +135,10 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string" + "fan_direction_reverse": "reverse dps string", + "fan_speed_dps_type": "type of speed dps control. integer/string/list", + "fan_preset_control": "Fan preset dps. CAn be the same as speed", + "fan_preset_list": "Fan preset list. Comma separated" } }, "yaml_import": { From d600cba89cffec158c2d6d1049e5802ab76c7e63 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Nov 2021 22:56:34 +1030 Subject: [PATCH 37/65] fix tox errors --- custom_components/localtuya/fan.py | 187 +++++++++++++++++------------ 1 file changed, 109 insertions(+), 78 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index efd2058..6896831 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -1,46 +1,46 @@ """Platform to locally control Tuya-based fan devices.""" import logging -from functools import partial import math +from functools import partial import homeassistant.helpers.config_validation as cv import voluptuous as vol from homeassistant.components.fan import ( - DOMAIN, DIRECTION_FORWARD, DIRECTION_REVERSE, + DOMAIN, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, SUPPORT_PRESET_MODE, + SUPPORT_SET_SPEED, FanEntity, ) - -from .common import LocalTuyaEntity, async_setup_entry -from .const import ( - CONF_FAN_OSCILLATING_CONTROL, - CONF_FAN_SPEED_CONTROL, - CONF_FAN_PRESET_CONTROL, - CONF_FAN_DIRECTION, - CONF_FAN_DIRECTION_FWD, - CONF_FAN_DIRECTION_REV, - CONF_FAN_SPEED_MIN, - CONF_FAN_SPEED_MAX, - CONF_FAN_ORDERED_LIST, - CONF_FAN_SPEED_DPS_TYPE, - CONF_FAN_PRESET_LIST -) - from homeassistant.util.percentage import ( + int_states_in_range, ordered_list_item_to_percentage, percentage_to_ordered_list_item, percentage_to_ranged_value, ranged_value_to_percentage, - int_states_in_range +) + +from .common import LocalTuyaEntity, async_setup_entry +from .const import ( + CONF_FAN_DIRECTION, + CONF_FAN_DIRECTION_FWD, + CONF_FAN_DIRECTION_REV, + CONF_FAN_ORDERED_LIST, + CONF_FAN_OSCILLATING_CONTROL, + CONF_FAN_PRESET_CONTROL, + CONF_FAN_PRESET_LIST, + CONF_FAN_SPEED_CONTROL, + CONF_FAN_SPEED_DPS_TYPE, + CONF_FAN_SPEED_MAX, + CONF_FAN_SPEED_MIN, ) _LOGGER = logging.getLogger(__name__) + def flow_schema(dps): """Return schema used in config flow.""" return { @@ -55,9 +55,11 @@ def flow_schema(dps): vol.Optional(CONF_FAN_ORDERED_LIST): cv.string, vol.Optional(CONF_FAN_PRESET_LIST): cv.string, vol.Optional(CONF_FAN_SPEED_DPS_TYPE, default="string"): vol.In( - ["string", "integer", "list"]), + ["string", "integer", "list"] + ), } + class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Representation of a Tuya fan.""" @@ -76,20 +78,26 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._percentage = None self._preset = None self._speed_range = ( - self._config.get(CONF_FAN_SPEED_MIN), + self._config.get(CONF_FAN_SPEED_MIN), self._config.get(CONF_FAN_SPEED_MAX), ) - self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).replace(" ","").split(",") - self._preset_list = self._config.get(CONF_FAN_PRESET_LIST).replace(" ","").split(",") + self._ordered_list = ( + self._config.get(CONF_FAN_ORDERED_LIST).replace(" ", "").split(",") + ) + self._preset_list = ( + self._config.get(CONF_FAN_PRESET_LIST).replace(" ", "").split(",") + ) self._ordered_speed_dps_type = self._config.get(CONF_FAN_SPEED_DPS_TYPE) - - if (self._ordered_speed_dps_type == "list" - and isinstance(self._ordered_list, list) - and len(self._ordered_list) > 1): + + if ( + self._ordered_speed_dps_type == "list" + and isinstance(self._ordered_list, list) + and len(self._ordered_list) > 1 + ): _LOGGER.debug("Fan _use_ordered_list: %s", self._ordered_list) else: _LOGGER.debug("Fan _use_ordered_list: Not a valid list") - + @property def oscillating(self): """Return current oscillating status.""" @@ -115,7 +123,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) if percentage is not None: - await self.async_set_percentage(speed) + await self.async_set_percentage(percentage) else: self.schedule_update_ha_state() @@ -128,44 +136,50 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): async def async_set_percentage(self, percentage): """Set the speed of the fan.""" - _LOGGER.debug("Fan async_set_percentage: %s", percentage) - + if percentage is not None: if percentage == 0: return await self.async_turn_off() - + if not self.is_on: await self.async_turn_on() - + if self._ordered_speed_dps_type == "string": - send_speed = str(math.ceil( - percentage_to_ranged_value(self._speed_range, percentage))) + send_speed = str( + math.ceil(percentage_to_ranged_value(self._speed_range, percentage)) + ) await self._device.set_dp( - send_speed, self._config.get(CONF_FAN_SPEED_CONTROL)) - _LOGGER.debug("Fan async_set_percentage: %s > %s", - percentage, send_speed) - + send_speed, self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug( + "Fan async_set_percentage: %s > %s", percentage, send_speed + ) + elif self._ordered_speed_dps_type == "integer": - send_speed = int(math.ceil( - percentage_to_ranged_value(self._speed_range, percentage))) + send_speed = int( + math.ceil(percentage_to_ranged_value(self._speed_range, percentage)) + ) await self._device.set_dp( - send_speed, self._config.get(CONF_FAN_SPEED_CONTROL)) - _LOGGER.debug("Fan async_set_percentage: %s > %s", - percentage, send_speed) + send_speed, self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug( + "Fan async_set_percentage: %s > %s", percentage, send_speed + ) elif self._ordered_speed_dps_type == "list": send_speed = str( - percentage_to_ordered_list_item(self._ordered_list, percentage) - ) + percentage_to_ordered_list_item(self._ordered_list, percentage) + ) await self._device.set_dp( - send_speed, self._config.get(CONF_FAN_SPEED_CONTROL)) - _LOGGER.debug("Fan async_set_percentage: %s > %s", - percentage, send_speed) + send_speed, self._config.get(CONF_FAN_SPEED_CONTROL) + ) + _LOGGER.debug( + "Fan async_set_percentage: %s > %s", percentage, send_speed + ) self.schedule_update_ha_state() - async def async_oscillate(self, oscillating: bool) -> None: """Set oscillation.""" _LOGGER.debug("Fan async_oscillate: %s", oscillating) @@ -180,17 +194,15 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if direction == DIRECTION_FORWARD: value = self._config.get(CONF_FAN_DIRECTION_FWD) - + if direction == DIRECTION_REVERSE: value = self._config.get(CONF_FAN_DIRECTION_REV) - await self._device.set_dp( - value, self._config.get(CONF_FAN_DIRECTION) - ) - self.schedule_update_ha_state() + await self._device.set_dp(value, self._config.get(CONF_FAN_DIRECTION)) + self.schedule_update_ha_state() async def async_set_preset_mode(self, preset_mode: str) -> None: - """Set the preset mode of the fan.""" + """Set the preset mode of the fan.""" _LOGGER.debug("Fan set preset: %s", preset_mode) await self._device.set_dp( preset_mode, self._config.get(CONF_FAN_PRESET_CONTROL) @@ -214,8 +226,8 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if self.has_config(CONF_FAN_PRESET_CONTROL): features |= SUPPORT_PRESET_MODE - return features - + return features + @property def speed_count(self) -> int: """Speed count for the fan.""" @@ -223,38 +235,57 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): _LOGGER.debug("Fan speed_count: %s", speed_count) return speed_count - def status_updated(self): """Get state of Tuya fan.""" - self._is_on = self.dps(self._dp_id) if self.has_config(CONF_FAN_PRESET_CONTROL): current_preset = self.dps_conf(CONF_FAN_PRESET_CONTROL) if current_preset is not None and current_preset in self._preset_list: - _LOGGER.debug("Fan current_preset in preset list: %s from %s", - current_preset, self._preset_list) + _LOGGER.debug( + "Fan current_preset in preset list: %s from %s", + current_preset, + self._preset_list, + ) self._preset = current_preset current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) if current_speed is not None: - if (self.has_config(CONF_FAN_PRESET_CONTROL) - and (CONF_FAN_SPEED_CONTROL == CONF_FAN_PRESET_CONTROL) - and (current_speed in self._preset_list)): - _LOGGER.debug("Fan current_speed in preset list: %s from %s", - current_speed, self._preset_list) + if ( + self.has_config(CONF_FAN_PRESET_CONTROL) + and (CONF_FAN_SPEED_CONTROL == CONF_FAN_PRESET_CONTROL) + and (current_speed in self._preset_list) + ): + _LOGGER.debug( + "Fan current_speed in preset list: %s from %s", + current_speed, + self._preset_list, + ) self._preset = current_speed elif self._ordered_speed_dps_type == "list": - _LOGGER.debug("Fan current_speed ordered_list_item_to_percentage: %s from %s", - current_speed, self._ordered_list) - self._percentage = ordered_list_item_to_percentage(self._ordered_list, current_speed) - - elif self._ordered_speed_dps_type == "string" or self._ordered_speed_dps_type == "integer" : - _LOGGER.debug("Fan current_speed ranged_value_to_percentage: %s from %s", - current_speed, self._speed_range) - self._percentage = ranged_value_to_percentage(self._speed_range, int(current_speed)) + _LOGGER.debug( + "Fan current_speed ordered_list_item_to_percentage: %s from %s", + current_speed, + self._ordered_list, + ) + self._percentage = ordered_list_item_to_percentage( + self._ordered_list, current_speed + ) + + elif ( + self._ordered_speed_dps_type == "string" + or self._ordered_speed_dps_type == "integer" + ): + _LOGGER.debug( + "Fan current_speed ranged_value_to_percentage: %s from %s", + current_speed, + self._speed_range, + ) + self._percentage = ranged_value_to_percentage( + self._speed_range, int(current_speed) + ) _LOGGER.debug("Fan current_percentage: %s", self._percentage) _LOGGER.debug("Fan current_preset: %s", self._preset) @@ -268,10 +299,10 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if value is not None: if value == self._config.get(CONF_FAN_DIRECTION_FWD): self._direction = DIRECTION_FORWARD - + if value == self._config.get(CONF_FAN_DIRECTION_REV): self._direction = DIRECTION_REVERSE _LOGGER.debug("Fan current_direction : %s > %s", value, self._direction) - + async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaFan, flow_schema) From e13f02a88119ce8025e1561f95ca3b6aa063678e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Fri, 27 Aug 2021 02:04:48 +0100 Subject: [PATCH 38/65] send update dps command with heartbeat --- .../localtuya/pytuya/__init__.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 789091c..2c613b4 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -61,6 +61,7 @@ TuyaMessage = namedtuple("TuyaMessage", "seqno cmd retcode payload crc") SET = "set" STATUS = "status" HEARTBEAT = "heartbeat" +UPDATEDPS = "updatedps" # Request refresh of DPS PROTOCOL_VERSION_BYTES_31 = b"3.1" PROTOCOL_VERSION_BYTES_33 = b"3.3" @@ -90,11 +91,13 @@ PAYLOAD_DICT = { STATUS: {"hexByte": 0x0A, "command": {"gwId": "", "devId": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, HEARTBEAT: {"hexByte": 0x09, "command": {}}, + UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}}, }, "type_0d": { STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, HEARTBEAT: {"hexByte": 0x09, "command": {}}, + UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}}, }, } @@ -379,6 +382,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): while True: try: await self.heartbeat() + await self.updatedps() await asyncio.sleep(HEARTBEAT_INTERVAL) except asyncio.CancelledError: self.debug("Stopped heartbeat loop") @@ -478,6 +482,16 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): """Send a heartbeat message.""" return await self.exchange(HEARTBEAT) + async def updatedps(self): + """ + Request device to update index. + Args: + index(array): list of dps to update (ex. [4, 5, 6, 18, 19, 20]) + """ + self.debug('updatedps() entry (dev_type is %s)', self.dev_type) + payload = self._generate_payload(UPDATEDPS) + self.transport.write(payload) + async def set_dp(self, value, dp_index): """ Set value (may be any type: bool, int or string) of any dps index. @@ -582,7 +596,10 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): json_data["t"] = str(int(time.time())) if data is not None: - json_data["dps"] = data + if "dpId" in json_data: + json_data["dpId"] = data + else: + json_data["dps"] = data elif command_hb == 0x0D: json_data["dps"] = self.dps_to_request @@ -591,7 +608,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): if self.version == 3.3: payload = self.cipher.encrypt(payload, False) - if command_hb != 0x0A: + if command_hb != 0x0A and command_hb != 0x12: # add the 3.3 header payload = PROTOCOL_33_HEADER + payload elif command == SET: From 24152569e7393a70403052df82d1566244866d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Tue, 31 Aug 2021 13:49:39 +0100 Subject: [PATCH 39/65] update state only on changes --- custom_components/localtuya/common.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 88b434f..2573b1a 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -234,13 +234,13 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): def _update_handler(status): """Update entity state when status was updated.""" - if status is not None: - self._status = status - self.status_updated() - else: - self._status = {} - - self.schedule_update_ha_state() + if status is None: + status = {} + if self._status != status: + self._status = status.copy() + if status: + self.status_updated() + self.schedule_update_ha_state() signal = f"localtuya_{self._config_entry.data[CONF_DEVICE_ID]}" self.async_on_remove( From 47c4edc20d6e4ed6b2848b8df9becf83a6fc8b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Tue, 23 Nov 2021 21:20:43 +0000 Subject: [PATCH 40/65] fix lint issues --- custom_components/localtuya/pytuya/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 2c613b4..90ac8ed 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -61,7 +61,7 @@ TuyaMessage = namedtuple("TuyaMessage", "seqno cmd retcode payload crc") SET = "set" STATUS = "status" HEARTBEAT = "heartbeat" -UPDATEDPS = "updatedps" # Request refresh of DPS +UPDATEDPS = "updatedps" # Request refresh of DPS PROTOCOL_VERSION_BYTES_31 = b"3.1" PROTOCOL_VERSION_BYTES_33 = b"3.3" @@ -485,10 +485,11 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): async def updatedps(self): """ Request device to update index. + Args: index(array): list of dps to update (ex. [4, 5, 6, 18, 19, 20]) """ - self.debug('updatedps() entry (dev_type is %s)', self.dev_type) + self.debug("updatedps() entry (dev_type is %s)", self.dev_type) payload = self._generate_payload(UPDATEDPS) self.transport.write(payload) @@ -608,7 +609,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): if self.version == 3.3: payload = self.cipher.encrypt(payload, False) - if command_hb != 0x0A and command_hb != 0x12: + if command_hb not in [0x0A, 0x12]: # add the 3.3 header payload = PROTOCOL_33_HEADER + payload elif command == SET: From 515b3303e9042322fe185db11ae5cac85fe5dfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Tue, 23 Nov 2021 22:16:30 +0000 Subject: [PATCH 41/65] add polling option --- custom_components/localtuya/common.py | 21 ++++++++++++++++++- custom_components/localtuya/config_flow.py | 4 ++++ .../localtuya/pytuya/__init__.py | 1 - custom_components/localtuya/strings.json | 3 ++- .../localtuya/translations/en.json | 4 +++- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 2573b1a..30028af 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -1,6 +1,7 @@ """Code shared between all platforms.""" import asyncio import logging +from datetime import timedelta from homeassistant.const import ( CONF_DEVICE_ID, @@ -9,8 +10,10 @@ from homeassistant.const import ( CONF_HOST, CONF_ID, CONF_PLATFORM, + CONF_SCAN_INTERVAL, ) from homeassistant.core import callback +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -116,6 +119,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self.dps_to_request = {} self._is_closing = False self._connect_task = None + self._unsub_interval = None self.set_logger(_LOGGER, config_entry[CONF_DEVICE_ID]) # This has to be done in case the device type is type_0d @@ -151,6 +155,15 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): raise Exception("Failed to retrieve status") self.status_updated(status) + if ( + CONF_SCAN_INTERVAL in self._config_entry + and self._config_entry[CONF_SCAN_INTERVAL] > 0 + ): + self._unsub_interval = async_track_time_interval( + self._hass, + self._async_refresh, + timedelta(seconds=self._config_entry[CONF_SCAN_INTERVAL]), + ) except Exception: # pylint: disable=broad-except self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed") if self._interface is not None: @@ -158,6 +171,10 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._interface = None self._connect_task = None + async def _async_refresh(self, _now): + if self._interface is not None: + await self._interface.updatedps() + async def close(self): """Close connection and stop re-connect loop.""" self._is_closing = True @@ -204,7 +221,9 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): """Device disconnected.""" signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}" async_dispatcher_send(self._hass, signal, None) - + if self._unsub_interval is not None: + self._unsub_interval() + self._unsub_interval = None self._interface = None self.debug("Disconnected - waiting for discovery broadcast") diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 731b60a..03bf80d 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -14,6 +14,7 @@ from homeassistant.const import ( CONF_HOST, CONF_ID, CONF_PLATFORM, + CONF_SCAN_INTERVAL, ) from homeassistant.core import callback @@ -44,6 +45,7 @@ BASIC_INFO_SCHEMA = vol.Schema( vol.Required(CONF_HOST): str, vol.Required(CONF_DEVICE_ID): str, vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]), + vol.Optional(CONF_SCAN_INTERVAL): int, } ) @@ -55,6 +57,7 @@ DEVICE_SCHEMA = vol.Schema( vol.Required(CONF_LOCAL_KEY): cv.string, vol.Required(CONF_FRIENDLY_NAME): cv.string, vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]), + vol.Optional(CONF_SCAN_INTERVAL): int, } ) @@ -90,6 +93,7 @@ def options_schema(entities): vol.Required(CONF_HOST): str, vol.Required(CONF_LOCAL_KEY): str, vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]), + vol.Optional(CONF_SCAN_INTERVAL): int, vol.Required( CONF_ENTITIES, description={"suggested_value": entity_names} ): cv.multi_select(entity_names), diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 90ac8ed..f033757 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -382,7 +382,6 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): while True: try: await self.heartbeat() - await self.updatedps() await asyncio.sleep(HEARTBEAT_INTERVAL) except asyncio.CancelledError: self.debug("Stopped heartbeat loop") diff --git a/custom_components/localtuya/strings.json b/custom_components/localtuya/strings.json index 898b99d..8a2c03f 100644 --- a/custom_components/localtuya/strings.json +++ b/custom_components/localtuya/strings.json @@ -20,6 +20,7 @@ "device_id": "Device ID", "local_key": "Local key", "protocol_version": "Protocol Version", + "scan_interval": "Scan interval in seconds (fill only if the device is not updating automatically)", "device_type": "Device type" } }, @@ -39,4 +40,4 @@ } }, "title": "LocalTuya" -} \ No newline at end of file +} diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 6ce233a..34826c4 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -29,7 +29,8 @@ "host": "Host", "device_id": "Device ID", "local_key": "Local key", - "protocol_version": "Protocol Version" + "protocol_version": "Protocol Version", + "scan_interval": "Scan interval in seconds (fill only if the device is not updating automatically)" } }, "pick_entity_type": { @@ -89,6 +90,7 @@ "host": "Host", "local_key": "Local key", "protocol_version": "Protocol Version", + "scan_interval": "Scan interval in seconds (fill only if the device is not updating automatically)", "entities": "Entities (uncheck an entity to remove it)" } }, From 9c6de40731716baa2b51854b292095db5bf77887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Fri, 26 Nov 2021 23:47:00 +0000 Subject: [PATCH 42/65] handle updatedps response --- .../localtuya/pytuya/__init__.py | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index f033757..ccde438 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -91,13 +91,13 @@ PAYLOAD_DICT = { STATUS: {"hexByte": 0x0A, "command": {"gwId": "", "devId": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, HEARTBEAT: {"hexByte": 0x09, "command": {}}, - UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}}, + UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [4, 5, 6, 18, 19, 20]}}, }, "type_0d": { STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, HEARTBEAT: {"hexByte": 0x09, "command": {}}, - UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}}, + UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [4, 5, 6, 18, 19, 20]}}, }, } @@ -210,9 +210,11 @@ class AESCipher: class MessageDispatcher(ContextualLogger): """Buffer and dispatcher for Tuya messages.""" - # Heartbeats always respond with sequence number 0, so they can't be waited for like - # other messages. This is a hack to allow waiting for heartbeats. + # Heartbeats and updatedps always respond with sequence number 0, + # so they can't be waited for like other messages. + # This is a hack to allow waiting for them. HEARTBEAT_SEQNO = -100 + UPDATEDPS_SEQNO = -101 def __init__(self, dev_id, listener): """Initialize a new MessageBuffer.""" @@ -295,9 +297,28 @@ class MessageDispatcher(ContextualLogger): sem = self.listeners[self.HEARTBEAT_SEQNO] self.listeners[self.HEARTBEAT_SEQNO] = msg sem.release() + elif msg.cmd == 0x12: + self.debug("Got normal updatedps response") + if self.UPDATEDPS_SEQNO in self.listeners: + sem = self.listeners[self.UPDATEDPS_SEQNO] + self.listeners[self.UPDATEDPS_SEQNO] = msg + if isinstance(sem, asyncio.Semaphore): + sem.release() elif msg.cmd == 0x08: - self.debug("Got status update") - self.listener(msg) + # If we have an open updatedps call then this is for it. + # Some devices send 0x12 and 0x08 in response to a updatedps. + # Empty DPS responses here are always for updatedps + # but hey we haven't decoded yet to know + if self.UPDATEDPS_SEQNO in self.listeners and isinstance( + self.listeners[self.UPDATEDPS_SEQNO], asyncio.Semaphore + ): + self.debug("Got status type updatedps response") + sem = self.listeners[self.UPDATEDPS_SEQNO] + self.listeners[self.UPDATEDPS_SEQNO] = msg + sem.release() + else: + self.debug("Got status update") + self.listener(msg) else: self.debug( "Got message type %d for unknown listener %d: %s", @@ -443,12 +464,13 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): payload = self._generate_payload(command, dps) dev_type = self.dev_type - # Wait for special sequence number if heartbeat - seqno = ( - MessageDispatcher.HEARTBEAT_SEQNO - if command == HEARTBEAT - else (self.seqno - 1) - ) + # Wait for special sequence number if heartbeat or updatedps + if command == HEARTBEAT: + seqno = MessageDispatcher.HEARTBEAT_SEQNO + elif command == UPDATEDPS: + seqno = MessageDispatcher.UPDATEDPS_SEQNO + else: + seqno = self.seqno - 1 self.transport.write(payload) msg = await self.dispatcher.wait_for(seqno) @@ -482,15 +504,10 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): return await self.exchange(HEARTBEAT) async def updatedps(self): - """ - Request device to update index. - - Args: - index(array): list of dps to update (ex. [4, 5, 6, 18, 19, 20]) - """ - self.debug("updatedps() entry (dev_type is %s)", self.dev_type) - payload = self._generate_payload(UPDATEDPS) - self.transport.write(payload) + """Request device to update index.""" + if self.version == 3.3: + return await self.exchange(UPDATEDPS) + return True async def set_dp(self, value, dp_index): """ From e292524793c10bb3d5f78dfc1b60ca32aa303def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Sat, 27 Nov 2021 17:30:15 +0000 Subject: [PATCH 43/65] shorten label text --- custom_components/localtuya/strings.json | 2 +- custom_components/localtuya/translations/en.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/localtuya/strings.json b/custom_components/localtuya/strings.json index 8a2c03f..b8bedc8 100644 --- a/custom_components/localtuya/strings.json +++ b/custom_components/localtuya/strings.json @@ -20,7 +20,7 @@ "device_id": "Device ID", "local_key": "Local key", "protocol_version": "Protocol Version", - "scan_interval": "Scan interval in seconds (fill only if the device is not updating automatically)", + "scan_interval": "Scan interval (seconds, only when not updating automatically)", "device_type": "Device type" } }, diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 34826c4..8a03279 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -30,7 +30,7 @@ "device_id": "Device ID", "local_key": "Local key", "protocol_version": "Protocol Version", - "scan_interval": "Scan interval in seconds (fill only if the device is not updating automatically)" + "scan_interval": "Scan interval (seconds, only when not updating automatically)" } }, "pick_entity_type": { @@ -90,7 +90,7 @@ "host": "Host", "local_key": "Local key", "protocol_version": "Protocol Version", - "scan_interval": "Scan interval in seconds (fill only if the device is not updating automatically)", + "scan_interval": "Scan interval (seconds, only when not updating automatically)", "entities": "Entities (uncheck an entity to remove it)" } }, From d6e7c7dec433c1c89e33efe6cb100da8f399d35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Sun, 28 Nov 2021 01:46:12 +0000 Subject: [PATCH 44/65] send updatedps with all detected dps by default --- custom_components/localtuya/common.py | 2 +- custom_components/localtuya/pytuya/__init__.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 30028af..badfabc 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -173,7 +173,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): async def _async_refresh(self, _now): if self._interface is not None: - await self._interface.updatedps() + await self._interface.update_dps() async def close(self): """Close connection and stop re-connect loop.""" diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index ccde438..24b0268 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -21,6 +21,7 @@ Functions json = status() # returns json payload set_version(version) # 3.1 [default] or 3.3 detect_available_dps() # returns a list of available dps provided by the device + update_dps(dps) # sends update dps command add_dps_to_request(dp_index) # adds dp_index to the list of dps used by the # device (to be queried in the payload) set_dp(on, dp_index) # Set value of any dps index. @@ -503,10 +504,20 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): """Send a heartbeat message.""" return await self.exchange(HEARTBEAT) - async def updatedps(self): - """Request device to update index.""" + async def update_dps(self, dps=None): + """ + Request device to update index. + + Args: + dps([int]): list of dps to update, default=all detected + """ if self.version == 3.3: - return await self.exchange(UPDATEDPS) + if dps is None: + if not self.dps_cache: + await self.detect_available_dps() + if self.dps_cache: + dps = [int(dp) for dp in self.dps_cache][:255] + return await self.exchange(UPDATEDPS, dps) return True async def set_dp(self, value, dp_index): From 23e48b791a995f5757155c63953861074c0571e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Sun, 28 Nov 2021 10:46:28 +0000 Subject: [PATCH 45/65] revert defaulting to all dps --- custom_components/localtuya/pytuya/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 24b0268..ecd0b7d 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -509,14 +509,9 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): Request device to update index. Args: - dps([int]): list of dps to update, default=all detected + dps([int]): list of dps to update """ if self.version == 3.3: - if dps is None: - if not self.dps_cache: - await self.detect_available_dps() - if self.dps_cache: - dps = [int(dp) for dp in self.dps_cache][:255] return await self.exchange(UPDATEDPS, dps) return True From 3cbe6751d3ae9f26136c82811cb2b7a5e23478e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Mon, 29 Nov 2021 03:39:22 +0000 Subject: [PATCH 46/65] revert changes, dont wait for response but use all detected dps --- .../localtuya/pytuya/__init__.py | 55 +++++++------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index ecd0b7d..16ae271 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -92,13 +92,13 @@ PAYLOAD_DICT = { STATUS: {"hexByte": 0x0A, "command": {"gwId": "", "devId": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, HEARTBEAT: {"hexByte": 0x09, "command": {}}, - UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [4, 5, 6, 18, 19, 20]}}, + UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}}, }, "type_0d": { STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}}, SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}}, HEARTBEAT: {"hexByte": 0x09, "command": {}}, - UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [4, 5, 6, 18, 19, 20]}}, + UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}}, }, } @@ -211,11 +211,9 @@ class AESCipher: class MessageDispatcher(ContextualLogger): """Buffer and dispatcher for Tuya messages.""" - # Heartbeats and updatedps always respond with sequence number 0, - # so they can't be waited for like other messages. - # This is a hack to allow waiting for them. + # Heartbeats always respond with sequence number 0, so they can't be waited for like + # other messages. This is a hack to allow waiting for heartbeats. HEARTBEAT_SEQNO = -100 - UPDATEDPS_SEQNO = -101 def __init__(self, dev_id, listener): """Initialize a new MessageBuffer.""" @@ -300,26 +298,9 @@ class MessageDispatcher(ContextualLogger): sem.release() elif msg.cmd == 0x12: self.debug("Got normal updatedps response") - if self.UPDATEDPS_SEQNO in self.listeners: - sem = self.listeners[self.UPDATEDPS_SEQNO] - self.listeners[self.UPDATEDPS_SEQNO] = msg - if isinstance(sem, asyncio.Semaphore): - sem.release() elif msg.cmd == 0x08: - # If we have an open updatedps call then this is for it. - # Some devices send 0x12 and 0x08 in response to a updatedps. - # Empty DPS responses here are always for updatedps - # but hey we haven't decoded yet to know - if self.UPDATEDPS_SEQNO in self.listeners and isinstance( - self.listeners[self.UPDATEDPS_SEQNO], asyncio.Semaphore - ): - self.debug("Got status type updatedps response") - sem = self.listeners[self.UPDATEDPS_SEQNO] - self.listeners[self.UPDATEDPS_SEQNO] = msg - sem.release() - else: - self.debug("Got status update") - self.listener(msg) + self.debug("Got status update") + self.listener(msg) else: self.debug( "Got message type %d for unknown listener %d: %s", @@ -465,13 +446,12 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): payload = self._generate_payload(command, dps) dev_type = self.dev_type - # Wait for special sequence number if heartbeat or updatedps - if command == HEARTBEAT: - seqno = MessageDispatcher.HEARTBEAT_SEQNO - elif command == UPDATEDPS: - seqno = MessageDispatcher.UPDATEDPS_SEQNO - else: - seqno = self.seqno - 1 + # Wait for special sequence number if heartbeat + seqno = ( + MessageDispatcher.HEARTBEAT_SEQNO + if command == HEARTBEAT + else (self.seqno - 1) + ) self.transport.write(payload) msg = await self.dispatcher.wait_for(seqno) @@ -509,10 +489,17 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): Request device to update index. Args: - dps([int]): list of dps to update + dps([int]): list of dps to update, default=all detected """ if self.version == 3.3: - return await self.exchange(UPDATEDPS, dps) + if dps is None: + if not self.dps_cache: + await self.detect_available_dps() + if self.dps_cache: + dps = [int(dp) for dp in self.dps_cache][:255] + self.debug("updatedps() entry (dps %s)", dps) + payload = self._generate_payload(UPDATEDPS, dps) + self.transport.write(payload) return True async def set_dp(self, value, dp_index): From 943bfa532e1af6ac8c2b3d418fe944db772a77a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Tue, 30 Nov 2021 17:08:47 +0000 Subject: [PATCH 47/65] log dps_cache --- custom_components/localtuya/pytuya/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 16ae271..f9618e0 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -497,7 +497,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): await self.detect_available_dps() if self.dps_cache: dps = [int(dp) for dp in self.dps_cache][:255] - self.debug("updatedps() entry (dps %s)", dps) + self.debug("updatedps() entry (dps_cache %s)", self.dps_cache) payload = self._generate_payload(UPDATEDPS, dps) self.transport.write(payload) return True From 0db320ee36e30ea5cc8724c393bb19c68fe66968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Villagra?= Date: Wed, 1 Dec 2021 15:36:03 +0000 Subject: [PATCH 48/65] add whitelist with 18,19,20 --- custom_components/localtuya/pytuya/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index f9618e0..b7645ec 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -78,6 +78,9 @@ SUFFIX_VALUE = 0x0000AA55 HEARTBEAT_INTERVAL = 10 +# DPS that are known to be safe to use with update_dps (0x12) command +UPDATE_DPS_WHITELIST = [18, 19, 20] # Socket (Wi-Fi) + # This is intended to match requests.json payload at # https://github.com/codetheweb/tuyapi : # type_0a devices require the 0a command as the status request @@ -489,15 +492,17 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): Request device to update index. Args: - dps([int]): list of dps to update, default=all detected + dps([int]): list of dps to update, default=detected&whitelisted """ if self.version == 3.3: if dps is None: if not self.dps_cache: await self.detect_available_dps() if self.dps_cache: - dps = [int(dp) for dp in self.dps_cache][:255] - self.debug("updatedps() entry (dps_cache %s)", self.dps_cache) + dps = [int(dp) for dp in self.dps_cache] + # filter non whitelisted dps + dps = list(set(dps).intersection(set(UPDATE_DPS_WHITELIST))) + self.debug("updatedps() entry (dps %s, dps_cache %s)", dps, self.dps_cache) payload = self._generate_payload(UPDATEDPS, dps) self.transport.write(payload) return True From d271da1b503d22418fe84f4803f367ad4745e13f Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 11 Dec 2021 21:54:57 +1100 Subject: [PATCH 49/65] Implement optional light variable to reverse color temp --- custom_components/localtuya/const.py | 1 + custom_components/localtuya/light.py | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index bd8a5d3..f981582 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -16,6 +16,7 @@ CONF_COLOR = "color" CONF_COLOR_MODE = "color_mode" CONF_COLOR_TEMP_MIN_KELVIN = "color_temp_min_kelvin" CONF_COLOR_TEMP_MAX_KELVIN = "color_temp_max_kelvin" +CONF_COLOR_TEMP_REVERSE = "color_temp_reverse" CONF_MUSIC_MODE = "music_mode" # switch diff --git a/custom_components/localtuya/light.py b/custom_components/localtuya/light.py index 49fade8..a910997 100644 --- a/custom_components/localtuya/light.py +++ b/custom_components/localtuya/light.py @@ -27,6 +27,7 @@ from .const import ( CONF_COLOR_MODE, CONF_COLOR_TEMP_MAX_KELVIN, CONF_COLOR_TEMP_MIN_KELVIN, + CONF_COLOR_TEMP_REVERSE, CONF_MUSIC_MODE, ) @@ -35,6 +36,7 @@ _LOGGER = logging.getLogger(__name__) MIRED_TO_KELVIN_CONST = 1000000 DEFAULT_MIN_KELVIN = 2700 # MIRED 370 DEFAULT_MAX_KELVIN = 6500 # MIRED 153 +DEFAULT_COLOR_TEMP_REVERSE = False DEFAULT_LOWER_BRIGHTNESS = 29 DEFAULT_UPPER_BRIGHTNESS = 1000 @@ -117,6 +119,9 @@ def flow_schema(dps): vol.Optional(CONF_COLOR_TEMP_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All( vol.Coerce(int), vol.Range(min=1500, max=8000) ), + vol.Optional( + CONF_COLOR_TEMP_REVERSE, default=False, description={"suggested_value": False} + ): bool, vol.Optional(CONF_SCENE): vol.In(dps), vol.Optional( CONF_MUSIC_MODE, default=False, description={"suggested_value": False} @@ -154,6 +159,9 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): MIRED_TO_KELVIN_CONST / self._config.get(CONF_COLOR_TEMP_MAX_KELVIN, DEFAULT_MAX_KELVIN) ) + self._color_temp_reverse = self._config.get( + CONF_COLOR_TEMP_REVERSE, DEFAULT_COLOR_TEMP_REVERSE + ) self._hs = None self._effect = None self._effect_list = [] @@ -199,13 +207,15 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): def color_temp(self): """Return the color_temp of the light.""" if self.has_config(CONF_COLOR_TEMP) and self.is_white_mode: - return int( + color_temp_value = self._upper_color_temp - self._color_temp if self._color_temp_reverse else self._color_temp + color_temp_scaled = int( self._max_mired - ( ((self._max_mired - self._min_mired) / self._upper_color_temp) - * self._color_temp + * color_temp_value ) ) + return color_temp_scaled return None @property @@ -364,14 +374,16 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP): if brightness is None: brightness = self._brightness - color_temp = int( + + color_temp_value = (self._max_mired - self._min_mired) - (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) if self._color_temp_reverse else (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) + color_temp_scaled = int( self._upper_color_temp - (self._upper_color_temp / (self._max_mired - self._min_mired)) - * (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) + * color_temp_value ) states[self._config.get(CONF_COLOR_MODE)] = MODE_WHITE states[self._config.get(CONF_BRIGHTNESS)] = brightness - states[self._config.get(CONF_COLOR_TEMP)] = color_temp + states[self._config.get(CONF_COLOR_TEMP)] = color_temp_scaled await self._device.set_dps(states) async def async_turn_off(self, **kwargs): From b79c9d4841797496ea673a7ade5e29dbdc3fe5f2 Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 11 Dec 2021 22:07:42 +1100 Subject: [PATCH 50/65] update light config info --- info.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/info.md b/info.md index 3c69026..9e4c1d5 100644 --- a/info.md +++ b/info.md @@ -66,6 +66,13 @@ localtuya: - platform: light friendly_name: Device Light + brightness: 10 # Optional + brightness_lower: 0 # Optional + brightness_upper: 100 # Optional + color_temp: 11 # Optional + color_temp_reverse: false # Optional + color_temp_min_kelvin: 2700 # Optional + color_temp_max_kelvin: 6500 # Optional id: 4 - platform: sensor From 65758ba6e2de53db45ca7a7b83e3df97a690a3e4 Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 11 Dec 2021 22:19:52 +1100 Subject: [PATCH 51/65] Formatting and variables --- custom_components/localtuya/light.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/custom_components/localtuya/light.py b/custom_components/localtuya/light.py index a910997..8e76dca 100644 --- a/custom_components/localtuya/light.py +++ b/custom_components/localtuya/light.py @@ -36,6 +36,7 @@ _LOGGER = logging.getLogger(__name__) MIRED_TO_KELVIN_CONST = 1000000 DEFAULT_MIN_KELVIN = 2700 # MIRED 370 DEFAULT_MAX_KELVIN = 6500 # MIRED 153 + DEFAULT_COLOR_TEMP_REVERSE = False DEFAULT_LOWER_BRIGHTNESS = 29 @@ -120,7 +121,9 @@ def flow_schema(dps): vol.Coerce(int), vol.Range(min=1500, max=8000) ), vol.Optional( - CONF_COLOR_TEMP_REVERSE, default=False, description={"suggested_value": False} + CONF_COLOR_TEMP_REVERSE, + default=DEFAULT_COLOR_TEMP_REVERSE, + description={"suggested_value": DEFAULT_COLOR_TEMP_REVERSE}, ): bool, vol.Optional(CONF_SCENE): vol.In(dps), vol.Optional( @@ -207,7 +210,11 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): def color_temp(self): """Return the color_temp of the light.""" if self.has_config(CONF_COLOR_TEMP) and self.is_white_mode: - color_temp_value = self._upper_color_temp - self._color_temp if self._color_temp_reverse else self._color_temp + color_temp_value = ( + self._upper_color_temp - self._color_temp + if self._color_temp_reverse + else self._color_temp + ) color_temp_scaled = int( self._max_mired - ( @@ -374,8 +381,13 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP): if brightness is None: brightness = self._brightness - - color_temp_value = (self._max_mired - self._min_mired) - (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) if self._color_temp_reverse else (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) + + color_temp_value = ( + (self._max_mired - self._min_mired) + - (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) + if self._color_temp_reverse + else (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) + ) color_temp_scaled = int( self._upper_color_temp - (self._upper_color_temp / (self._max_mired - self._min_mired)) From f5ee4202233c5fdd53f625902b65862ae14eb11c Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 11 Dec 2021 22:22:45 +1100 Subject: [PATCH 52/65] update readme and info --- README.md | 2 +- info.md | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0030cf3..a5faa83 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ localtuya: color_mode: 21 # Optional, usually 2 or 21, default: "none" brightness: 22 # Optional, usually 3 or 22, default: "none" color_temp: 23 # Optional, usually 4 or 23, default: "none" + color_temp_reverse: false # Optional, default: false color: 24 # Optional, usually 5 (RGB_HSV) or 24 (HSV), default: "none" brightness_lower: 29 # Optional, usually 0 or 29, default: 29 brightness_upper: 1000 # Optional, usually 255 or 1000, default: 1000 @@ -79,7 +80,6 @@ localtuya: scene: 25 # Optional, usually 6 (RGB_HSV) or 25 (HSV), default: "none" music_mode: False # Optional, some use internal mic, others, phone mic. Only internal mic is supported, default: "False" - - platform: sensor friendly_name: Plug Voltage id: 20 diff --git a/info.md b/info.md index 9e4c1d5..1954f9d 100644 --- a/info.md +++ b/info.md @@ -66,14 +66,18 @@ localtuya: - platform: light friendly_name: Device Light - brightness: 10 # Optional - brightness_lower: 0 # Optional - brightness_upper: 100 # Optional - color_temp: 11 # Optional - color_temp_reverse: false # Optional - color_temp_min_kelvin: 2700 # Optional - color_temp_max_kelvin: 6500 # Optional - id: 4 + id: 4 # Usually 1 or 20 + color_mode: 21 # Optional, usually 2 or 21, default: "none" + brightness: 22 # Optional, usually 3 or 22, default: "none" + color_temp: 23 # Optional, usually 4 or 23, default: "none" + color_temp_reverse: false # Optional, default: false + color: 24 # Optional, usually 5 (RGB_HSV) or 24 (HSV), default: "none" + brightness_lower: 29 # Optional, usually 0 or 29, default: 29 + brightness_upper: 1000 # Optional, usually 255 or 1000, default: 1000 + color_temp_min_kelvin: 2700 # Optional, default: 2700 + color_temp_max_kelvin: 6500 # Optional, default: 6500 + scene: 25 # Optional, usually 6 (RGB_HSV) or 25 (HSV), default: "none" + music_mode: False # Optional, some use internal mic, others, phone mic. Only internal mic is supported, default: "False" - platform: sensor friendly_name: Plug Voltage From e45be52a9e7845c5ab4e3a460528ed829a9b07ea Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 13 Dec 2021 11:09:13 +1100 Subject: [PATCH 53/65] rename variables --- custom_components/localtuya/light.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/custom_components/localtuya/light.py b/custom_components/localtuya/light.py index 8e76dca..e99414e 100644 --- a/custom_components/localtuya/light.py +++ b/custom_components/localtuya/light.py @@ -215,14 +215,13 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): if self._color_temp_reverse else self._color_temp ) - color_temp_scaled = int( + return int( self._max_mired - ( ((self._max_mired - self._min_mired) / self._upper_color_temp) * color_temp_value ) ) - return color_temp_scaled return None @property @@ -381,21 +380,20 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP): if brightness is None: brightness = self._brightness - color_temp_value = ( (self._max_mired - self._min_mired) - (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) if self._color_temp_reverse else (int(kwargs[ATTR_COLOR_TEMP]) - self._min_mired) ) - color_temp_scaled = int( + color_temp = int( self._upper_color_temp - (self._upper_color_temp / (self._max_mired - self._min_mired)) * color_temp_value ) states[self._config.get(CONF_COLOR_MODE)] = MODE_WHITE states[self._config.get(CONF_BRIGHTNESS)] = brightness - states[self._config.get(CONF_COLOR_TEMP)] = color_temp_scaled + states[self._config.get(CONF_COLOR_TEMP)] = color_temp await self._device.set_dps(states) async def async_turn_off(self, **kwargs): From bfba2d7f722b2bacc221f9da3d4beba4b7e1417a Mon Sep 17 00:00:00 2001 From: Sefi Ninio Date: Thu, 16 Dec 2021 18:10:31 +0200 Subject: [PATCH 54/65] fix deprecated device_state_attributes Replace `device_state_attributes` with `extra_state_attributes` This fixes #662 --- custom_components/localtuya/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/switch.py b/custom_components/localtuya/switch.py index d3e08f5..f43d910 100644 --- a/custom_components/localtuya/switch.py +++ b/custom_components/localtuya/switch.py @@ -48,7 +48,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" attrs = {} if self.has_config(CONF_CURRENT): From 28554a1849746d9d210cff87b612acece2f47a7e Mon Sep 17 00:00:00 2001 From: jeremysherriff Date: Sun, 19 Dec 2021 17:38:30 +1300 Subject: [PATCH 55/65] Add scan interval information to README To accompany PR rospogrigio/localtuya#549 --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a4ccf04..20ff98f 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ localtuya: local_key: xxxxx friendly_name: Tuya Device protocol_version: "3.3" + scan_interval: # optional, only needed if energy monitoring values are not updating + seconds: 30 # Values less than 10 seconds may cause stability issues entities: - platform: binary_sensor friendly_name: Plug Status @@ -112,9 +114,13 @@ select one of these, or manually input all the parameters. ![discovery](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/1-discovery.png) If you have selected one entry, you only need to input the device's Friendly Name and the localKey. + +Setting the scan interval is optional, only needed if energy/power values are not updating frequently enough by default. Values less than 10 seconds may cause stability issues. + Once you press "Submit", the connection is tested to check that everything works. -![device](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/2-device.png) +![image](https://user-images.githubusercontent.com/1082213/146663895-41e1902b-4f09-4b21-b9d7-067d9cd67069.png) + Then, it's time to add the entities: this step will take place several times. First, select the entity type from the drop-down menu to set it up. After you have defined all the needed entities, leave the "Do not add more entities" checkbox checked: this will complete the procedure. @@ -140,6 +146,7 @@ You can obtain Energy monitoring (voltage, current) in two different ways: Note: Voltage and Consumption usually include the first decimal. You will need to scale the parament by 0.1 to get the correct values. 1) Access the voltage/current/current_consumption attributes of a switch, and define template sensors Note: these values are already divided by 10 for Voltage and Consumption +1) On some devices, you may find that the energy values are not updating frequently enough by default. If so, set the scan interval (see above) to an appropriate value. Settings below 10 seconds may cause stability issues, 30 seconds is recommended. ``` sensor: From 0d14df976d8cfa28f3b934bc5e0fbca6aa015486 Mon Sep 17 00:00:00 2001 From: jeremysherriff Date: Sun, 19 Dec 2021 17:41:02 +1300 Subject: [PATCH 56/65] Tweak image sizing --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20ff98f..7e4d690 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Setting the scan interval is optional, only needed if energy/power values are no Once you press "Submit", the connection is tested to check that everything works. -![image](https://user-images.githubusercontent.com/1082213/146663895-41e1902b-4f09-4b21-b9d7-067d9cd67069.png) +![image](https://user-images.githubusercontent.com/1082213/146664103-ac40319e-f934-4933-90cf-2beaff1e6bac.png) Then, it's time to add the entities: this step will take place several times. First, select the entity type from the drop-down menu to set it up. From 5542d83e31d1e13234264517843b02b60c685de5 Mon Sep 17 00:00:00 2001 From: jeremysherriff Date: Sun, 19 Dec 2021 17:46:34 +1300 Subject: [PATCH 57/65] Add scan interval information to info.md To accompany PR rospogrigio/localtuya#549 --- info.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/info.md b/info.md index 3c69026..b59ba35 100644 --- a/info.md +++ b/info.md @@ -43,6 +43,8 @@ localtuya: local_key: xxxxx friendly_name: Tuya Device protocol_version: "3.3" + scan_interval: # optional, only needed if energy monitoring values are not updating + seconds: 30 # Values less than 10 seconds may cause stability issues entities: - platform: binary_sensor friendly_name: Plug Status @@ -98,9 +100,12 @@ select one of these, or manually input all the parameters. ![discovery](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/1-discovery.png) If you have selected one entry, you just have to input the Friendly Name of the Device, and the localKey. -Once you press "Submit", the connection will be tested to check that everything works, in order to proceed. -![device](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/2-device.png) +Setting the scan interval is optional, only needed if energy/power values are not updating frequently enough by default. Values less than 10 seconds may cause stability issues. + +Once you press "Submit", the connection is tested to check that everything works. + +![image](https://user-images.githubusercontent.com/1082213/146663895-41e1902b-4f09-4b21-b9d7-067d9cd67069.png) Then, it's time to add the entities: this step will take place several times. Select the entity type from the drop-down menu to set it up. After you have defined all the needed entities leave the "Do not add more entities" checkbox checked: this will complete the procedure. @@ -122,7 +127,8 @@ After all the entities have been configured, the procedure is complete, and the Energy monitoring (voltage, current...) values can be obtained in two different ways: 1) creating individual sensors, each one with the desired name. Note: Voltage and Consumption usually include the first decimal, so 0.1 as "scaling" parameter shall be used in order to get the correct values. -2) accessing the voltage/current/current_consumption attributes of a switch, and then defining template sensors like this (please note that in this case the values are already divided by 10 for Voltage and Consumption): +2) accessing the voltage/current/current_consumption attributes of a switch, and then defining template sensors like this (please note that in this case the values are already divided by 10 for Voltage and Consumption) +3) On some devices, you may find that the energy values are not updating frequently enough by default. If so, set the scan interval (see above) to an appropriate value. Settings below 10 seconds may cause stability issues, 30 seconds is recommended. ``` sensor: From 38b9c675441fa4daaa1315307af7291b21310363 Mon Sep 17 00:00:00 2001 From: jeremysherriff Date: Sun, 19 Dec 2021 17:48:28 +1300 Subject: [PATCH 58/65] Tweak image sizing --- info.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/info.md b/info.md index b59ba35..ca0b487 100644 --- a/info.md +++ b/info.md @@ -105,7 +105,7 @@ Setting the scan interval is optional, only needed if energy/power values are no Once you press "Submit", the connection is tested to check that everything works. -![image](https://user-images.githubusercontent.com/1082213/146663895-41e1902b-4f09-4b21-b9d7-067d9cd67069.png) +![image](https://user-images.githubusercontent.com/1082213/146664103-ac40319e-f934-4933-90cf-2beaff1e6bac.png) Then, it's time to add the entities: this step will take place several times. Select the entity type from the drop-down menu to set it up. After you have defined all the needed entities leave the "Do not add more entities" checkbox checked: this will complete the procedure. From e1ac7deace562f4d1e200964b6a44bc3c852e9a4 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 22 Dec 2021 11:35:04 +1030 Subject: [PATCH 59/65] remove blank line --- custom_components/localtuya/fan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index ca501e8..0d9aeed 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -111,7 +111,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): preset_mode: str = None, **kwargs, ) -> None: - """Turn on the entity.""" _LOGGER.debug("Fan async_turn_on") await self._device.set_dp(True, self._dp_id) From ad0e1b186c381d7480d0d38f8b384aadabc7df8b Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 22 Dec 2021 17:34:32 +1100 Subject: [PATCH 60/65] fix missing color temp label --- custom_components/localtuya/translations/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index f12c4ab..5981c59 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -64,6 +64,7 @@ "brightness_lower": "Brightness Lower Value", "brightness_upper": "Brightness Upper Value", "color_temp": "Color Temperature", + "color_temp_reverse": "Color Temperature Reverse", "color": "Color", "color_mode": "Color Mode", "color_temp_min_kelvin": "Minimum Color Temperature in K", @@ -120,6 +121,7 @@ "brightness_lower": "Brightness Lower Value", "brightness_upper": "Brightness Upper Value", "color_temp": "Color Temperature", + "color_temp_reverse": "Color Temperature Reverse", "color": "Color", "color_mode": "Color Mode", "color_temp_min_kelvin": "Minimum Color Temperature in K", From a25bf75a4d13679961585357c9dfa62f524fe58c Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 22 Dec 2021 19:45:16 +1030 Subject: [PATCH 61/65] tox fixes --- custom_components/localtuya/fan.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 0d9aeed..e742481 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -107,6 +107,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): async def async_turn_on( self, + speed: str = None, percentage: int = None, preset_mode: str = None, **kwargs, @@ -116,9 +117,12 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): await self._device.set_dp(True, self._dp_id) if percentage is not None: await self.async_set_percentage(percentage) + elif preset_mode is not None: + _LOGGER.debug("Preset_mode not supported yet") else: self.schedule_update_ha_state() + async def async_turn_off(self, **kwargs) -> None: """Turn off the entity.""" _LOGGER.debug("Fan async_turn_off") @@ -133,7 +137,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if percentage is not None: if percentage == 0: return await self.async_turn_off() - elif not self.is_on: + if not self.is_on: await self.async_turn_on() if self._use_ordered_list: await self._device.set_dp( From 0e048af29b82ea547fdd92e52b1aecca2d7b9b17 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 22 Dec 2021 20:01:39 +1030 Subject: [PATCH 62/65] fix issue with last tox fix --- custom_components/localtuya/fan.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index e742481..39cd896 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -117,8 +117,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): await self._device.set_dp(True, self._dp_id) if percentage is not None: await self.async_set_percentage(percentage) - elif preset_mode is not None: - _LOGGER.debug("Preset_mode not supported yet") else: self.schedule_update_ha_state() From b748f0425cb4e7ac31bc60c0f21187dfba964eb3 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 22 Dec 2021 20:48:46 +1030 Subject: [PATCH 63/65] Remove blank line --- custom_components/localtuya/fan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 39cd896..d2b4583 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -120,7 +120,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): else: self.schedule_update_ha_state() - async def async_turn_off(self, **kwargs) -> None: """Turn off the entity.""" _LOGGER.debug("Fan async_turn_off") From d878391d5e27b04a94b67fe20b163076c3f83a0f Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 22 Dec 2021 23:02:08 +1030 Subject: [PATCH 64/65] readme updated --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d645eaa..705fc0b 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,16 @@ localtuya: - platform: fan friendly_name: Device Fan - id: 3 + id: 3 # dps for on/off state + fan_direction: 4 # Optional, dps for fan direction + fan_direction_fwd: forward # String for the forward direction + fan_direction_rev: reverse # String for the reverse direction + fan_ordered_list: low,medium,high,auto # Optional, If this is used it will not use the min and max integers. + fan_oscilating_control: 4 # Optional, dps for fan osciallation + fan_speed_control: 3 # Optional, if ordered list not used, dps for speed control + fan_speed_min: 1 # Optional, if ordered list not used, minimum integer for speed range + fan_speed_max: 10 # Optional, if ordered list not used, maximum integer for speed range + - platform: light friendly_name: Device Light From ddee11f74123f8627a26cf5b57ef460759d75837 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 22 Dec 2021 23:05:33 +1030 Subject: [PATCH 65/65] Revert "Merge branch 'add-string/integer-and-preset' into master" This reverts commit a6ef6a4b7f07c5277beb93801f1c1f69f69f7497, reversing changes made to b748f0425cb4e7ac31bc60c0f21187dfba964eb3. --- .github/workflows/tox.yaml | 2 +- custom_components/localtuya/const.py | 3 - custom_components/localtuya/fan.py | 149 ++++++------------ .../localtuya/translations/en.json | 10 +- 4 files changed, 48 insertions(+), 116 deletions(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 81d0350..40ca8d9 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -1,6 +1,6 @@ name: Tox PR CI -on: [push, pull_request, workflow_dispatch] +on: [pull_request] jobs: build: diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index a4da5ac..159bc12 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -41,9 +41,6 @@ CONF_FAN_ORDERED_LIST = "fan_speed_ordered_list" CONF_FAN_DIRECTION = "fan_direction" CONF_FAN_DIRECTION_FWD = "fan_direction_forward" CONF_FAN_DIRECTION_REV = "fan_direction_reverse" -CONF_FAN_SPEED_DPS_TYPE = "fan_speed_dps_type" -CONF_FAN_PRESET_CONTROL = "fan_preset_control" -CONF_FAN_PRESET_LIST = "fan_preset_list" # sensor CONF_SCALING = "scaling" diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 4ea98f4..d2b4583 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -11,7 +11,6 @@ from homeassistant.components.fan import ( DOMAIN, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, - SUPPORT_PRESET_MODE, SUPPORT_SET_SPEED, FanEntity, ) @@ -30,10 +29,7 @@ from .const import ( CONF_FAN_DIRECTION_REV, CONF_FAN_ORDERED_LIST, CONF_FAN_OSCILLATING_CONTROL, - CONF_FAN_PRESET_CONTROL, - CONF_FAN_PRESET_LIST, CONF_FAN_SPEED_CONTROL, - CONF_FAN_SPEED_DPS_TYPE, CONF_FAN_SPEED_MAX, CONF_FAN_SPEED_MIN, ) @@ -47,16 +43,11 @@ def flow_schema(dps): vol.Optional(CONF_FAN_SPEED_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_OSCILLATING_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION): vol.In(dps), - vol.Optional(CONF_FAN_PRESET_CONTROL): vol.In(dps), vol.Optional(CONF_FAN_DIRECTION_FWD, default="forward"): cv.string, vol.Optional(CONF_FAN_DIRECTION_REV, default="reverse"): cv.string, vol.Optional(CONF_FAN_SPEED_MIN, default=1): cv.positive_int, vol.Optional(CONF_FAN_SPEED_MAX, default=9): cv.positive_int, - vol.Optional(CONF_FAN_ORDERED_LIST): cv.string, - vol.Optional(CONF_FAN_PRESET_LIST): cv.string, - vol.Optional(CONF_FAN_SPEED_DPS_TYPE, default="string"): vol.In( - ["string", "integer", "list"] - ), + vol.Optional(CONF_FAN_ORDERED_LIST, default="disabled"): cv.string, } @@ -76,27 +67,23 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): self._oscillating = None self._direction = None self._percentage = None - self._preset = None self._speed_range = ( self._config.get(CONF_FAN_SPEED_MIN), self._config.get(CONF_FAN_SPEED_MAX), ) - self._ordered_list = ( - self._config.get(CONF_FAN_ORDERED_LIST).replace(" ", "").split(",") - ) - self._preset_list = ( - self._config.get(CONF_FAN_PRESET_LIST).replace(" ", "").split(",") - ) - self._ordered_speed_dps_type = self._config.get(CONF_FAN_SPEED_DPS_TYPE) + self._ordered_list = self._config.get(CONF_FAN_ORDERED_LIST).split(",") + self._ordered_list_mode = None - if ( - self._ordered_speed_dps_type == "list" - and isinstance(self._ordered_list, list) - and len(self._ordered_list) > 1 - ): - _LOGGER.debug("Fan _use_ordered_list: %s", self._ordered_list) + if isinstance(self._ordered_list, list) and len(self._ordered_list) > 1: + self._use_ordered_list = True + _LOGGER.debug( + "Fan _use_ordered_list: %s > %s", + self._use_ordered_list, + self._ordered_list, + ) else: - _LOGGER.debug("Fan _use_ordered_list: Not a valid list") + self._use_ordered_list = False + _LOGGER.debug("Fan _use_ordered_list: %s", self._use_ordered_list) @property def oscillating(self): @@ -147,43 +134,35 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if percentage is not None: if percentage == 0: return await self.async_turn_off() - if not self.is_on: await self.async_turn_on() - - if self._ordered_speed_dps_type == "string": - send_speed = str( - math.ceil(percentage_to_ranged_value(self._speed_range, percentage)) - ) + if self._use_ordered_list: await self._device.set_dp( - send_speed, self._config.get(CONF_FAN_SPEED_CONTROL) + str( + percentage_to_ordered_list_item(self._ordered_list, percentage) + ), + self._config.get(CONF_FAN_SPEED_CONTROL), ) _LOGGER.debug( - "Fan async_set_percentage: %s > %s", percentage, send_speed + "Fan async_set_percentage: %s > %s", + percentage, + percentage_to_ordered_list_item(self._ordered_list, percentage), ) - elif self._ordered_speed_dps_type == "integer": - send_speed = int( - math.ceil(percentage_to_ranged_value(self._speed_range, percentage)) - ) + else: await self._device.set_dp( - send_speed, self._config.get(CONF_FAN_SPEED_CONTROL) + str( + math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + ) + ), + self._config.get(CONF_FAN_SPEED_CONTROL), ) _LOGGER.debug( - "Fan async_set_percentage: %s > %s", percentage, send_speed + "Fan async_set_percentage: %s > %s", + percentage, + percentage_to_ranged_value(self._speed_range, percentage), ) - - elif self._ordered_speed_dps_type == "list": - send_speed = str( - percentage_to_ordered_list_item(self._ordered_list, percentage) - ) - await self._device.set_dp( - send_speed, self._config.get(CONF_FAN_SPEED_CONTROL) - ) - _LOGGER.debug( - "Fan async_set_percentage: %s > %s", percentage, send_speed - ) - self.schedule_update_ha_state() async def async_oscillate(self, oscillating: bool) -> None: @@ -203,18 +182,9 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if direction == DIRECTION_REVERSE: value = self._config.get(CONF_FAN_DIRECTION_REV) - await self._device.set_dp(value, self._config.get(CONF_FAN_DIRECTION)) self.schedule_update_ha_state() - async def async_set_preset_mode(self, preset_mode: str) -> None: - """Set the preset mode of the fan.""" - _LOGGER.debug("Fan set preset: %s", preset_mode) - await self._device.set_dp( - preset_mode, self._config.get(CONF_FAN_PRESET_CONTROL) - ) - self.schedule_update_ha_state() - @property def supported_features(self) -> int: """Flag supported features.""" @@ -229,9 +199,6 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): if self.has_config(CONF_FAN_DIRECTION): features |= SUPPORT_DIRECTION - if self.has_config(CONF_FAN_PRESET_CONTROL): - features |= SUPPORT_PRESET_MODE - return features @property @@ -245,56 +212,30 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """Get state of Tuya fan.""" self._is_on = self.dps(self._dp_id) - if self.has_config(CONF_FAN_PRESET_CONTROL): - current_preset = self.dps_conf(CONF_FAN_PRESET_CONTROL) - if current_preset is not None and current_preset in self._preset_list: - _LOGGER.debug( - "Fan current_preset in preset list: %s from %s", - current_preset, - self._preset_list, - ) - self._preset = current_preset - current_speed = self.dps_conf(CONF_FAN_SPEED_CONTROL) - if current_speed is not None: - - if ( - self.has_config(CONF_FAN_PRESET_CONTROL) - and (CONF_FAN_SPEED_CONTROL == CONF_FAN_PRESET_CONTROL) - and (current_speed in self._preset_list) - ): - _LOGGER.debug( - "Fan current_speed in preset list: %s from %s", - current_speed, - self._preset_list, - ) - self._preset = current_speed - - elif self._ordered_speed_dps_type == "list": - _LOGGER.debug( - "Fan current_speed ordered_list_item_to_percentage: %s from %s", - current_speed, - self._ordered_list, - ) + if self._use_ordered_list: + _LOGGER.debug( + "Fan current_speed ordered_list_item_to_percentage: %s from %s", + current_speed, + self._ordered_list, + ) + if current_speed is not None: self._percentage = ordered_list_item_to_percentage( self._ordered_list, current_speed ) - elif ( - self._ordered_speed_dps_type == "string" - or self._ordered_speed_dps_type == "integer" - ): - _LOGGER.debug( - "Fan current_speed ranged_value_to_percentage: %s from %s", - current_speed, - self._speed_range, - ) + else: + _LOGGER.debug( + "Fan current_speed ranged_value_to_percentage: %s from %s", + current_speed, + self._speed_range, + ) + if current_speed is not None: self._percentage = ranged_value_to_percentage( self._speed_range, int(current_speed) ) - _LOGGER.debug("Fan current_percentage: %s", self._percentage) - _LOGGER.debug("Fan current_preset: %s", self._preset) + _LOGGER.debug("Fan current_percentage: %s", self._percentage) if self.has_config(CONF_FAN_OSCILLATING_CONTROL): self._oscillating = self.dps_conf(CONF_FAN_OSCILLATING_CONTROL) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 2014b0b..2cb903e 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -77,10 +77,7 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string", - "fan_speed_dps_type": "type of speed dps control. integer/string/list", - "fan_preset_control": "Fan preset dps. Can be the same as speed", - "fan_preset_list": "Fan preset list. Comma separated" + "fan_direction_reverse": "reverse dps string" } } } @@ -135,10 +132,7 @@ "fan_speed_ordered_list": "Fan speed modes list (overrides speed min/max)", "fan_direction":"fan direction dps", "fan_direction_forward": "forward dps string", - "fan_direction_reverse": "reverse dps string", - "fan_speed_dps_type": "type of speed dps control. integer/string/list", - "fan_preset_control": "Fan preset dps. CAn be the same as speed", - "fan_preset_list": "Fan preset list. Comma separated" + "fan_direction_reverse": "reverse dps string" } }, "yaml_import": {