From 58ba012ad70f834a7b4c70dc586679724fa809fd Mon Sep 17 00:00:00 2001 From: regevbr Date: Fri, 1 Oct 2021 22:41:35 +0300 Subject: [PATCH 01/12] fix sync bug --- custom_components/localtuya/common.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 88b434f..d2aa265 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -116,6 +116,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self.dps_to_request = {} self._is_closing = False self._connect_task = None + self._disconnect_task = 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 +152,14 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): raise Exception("Failed to retrieve status") self.status_updated(status) + + def _new_entity_handler(entity_id): + self.debug("New entity %s was added to %s", entity_id, self._config_entry[CONF_HOST]) + self._dispatch_status() + + """Subscribe localtuya entity events.""" + signal = f"localtuya_entity_{self._config_entry[CONF_DEVICE_ID]}" + self._disconnect_task = async_dispatcher_connect(self._hass, signal, _new_entity_handler) except Exception: # pylint: disable=broad-except self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed") if self._interface is not None: @@ -166,6 +175,8 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): await self._connect_task if self._interface is not None: await self._interface.close() + if self._disconnect_task is not None: + self._disconnect_task() async def set_dp(self, state, dp_index): """Change value of a DP of the Tuya device.""" @@ -195,7 +206,9 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): def status_updated(self, status): """Device updated status.""" self._status.update(status) + self._dispatch_status() + def _dispatch_status(self): signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}" async_dispatcher_send(self._hass, signal, self._status) @@ -223,7 +236,6 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): self.set_logger(logger, self._config_entry.data[CONF_DEVICE_ID]) async def async_added_to_hass(self): - """Subscribe localtuya events.""" await super().async_added_to_hass() self.debug("Adding %s with configuration: %s", self.entity_id, self._config) @@ -243,9 +255,14 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): self.schedule_update_ha_state() signal = f"localtuya_{self._config_entry.data[CONF_DEVICE_ID]}" + + """Subscribe localtuya events.""" self.async_on_remove( async_dispatcher_connect(self.hass, signal, _update_handler) ) + """Signal to device entity was added""" + signal = f"localtuya_entity_{self._config_entry.data[CONF_DEVICE_ID]}" + async_dispatcher_send(self.hass, signal, self.entity_id) @property def device_info(self): From 8e94b5f41174e437d743d3a0866ce4bc7db105bc Mon Sep 17 00:00:00 2001 From: regevbr Date: Fri, 1 Oct 2021 22:58:14 +0300 Subject: [PATCH 02/12] fix sync bug --- custom_components/localtuya/common.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index d2aa265..68b0d28 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -134,6 +134,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._connect_task = asyncio.create_task(self._make_connection()) async def _make_connection(self): + """Subscribe localtuya entity events.""" self.debug("Connecting to %s", self._config_entry[CONF_HOST]) try: @@ -154,12 +155,17 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self.status_updated(status) def _new_entity_handler(entity_id): - self.debug("New entity %s was added to %s", entity_id, self._config_entry[CONF_HOST]) + self.debug( + "New entity %s was added to %s", + entity_id, + self._config_entry[CONF_HOST], + ) self._dispatch_status() - """Subscribe localtuya entity events.""" signal = f"localtuya_entity_{self._config_entry[CONF_DEVICE_ID]}" - self._disconnect_task = async_dispatcher_connect(self._hass, signal, _new_entity_handler) + self._disconnect_task = async_dispatcher_connect( + self._hass, signal, _new_entity_handler + ) except Exception: # pylint: disable=broad-except self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed") if self._interface is not None: @@ -236,6 +242,7 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): self.set_logger(logger, self._config_entry.data[CONF_DEVICE_ID]) async def async_added_to_hass(self): + """Subscribe localtuya events.""" await super().async_added_to_hass() self.debug("Adding %s with configuration: %s", self.entity_id, self._config) @@ -256,11 +263,10 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): signal = f"localtuya_{self._config_entry.data[CONF_DEVICE_ID]}" - """Subscribe localtuya events.""" self.async_on_remove( async_dispatcher_connect(self.hass, signal, _update_handler) ) - """Signal to device entity was added""" + signal = f"localtuya_entity_{self._config_entry.data[CONF_DEVICE_ID]}" async_dispatcher_send(self.hass, signal, self.entity_id) From 5a28cec51762b0aa6b9c3d12443d07761ab3b91d Mon Sep 17 00:00:00 2001 From: Bashir <37988348+Brahmah@users.noreply.github.com> Date: Sun, 17 Oct 2021 21:55:38 +1100 Subject: [PATCH 03/12] Update README.md This addition suggests a user close the tuya app prior to proceeding with the config flow to avoid timeouts as experienced by #591 #570 #507 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0030cf3..a4ccf04 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ Start by going to Configuration - Integration and pressing the "+" button to cre Wait for 6 seconds for the scanning of the devices in your LAN. Then, a drop-down menu will appear containing the list of detected devices: you can select one of these, or manually input all the parameters. +> **Note: The tuya app on your device must be closed for the following steps to work reliably.** + ![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. From e1bb45f2a27e97d724a53579f881fb1e11c82bbf Mon Sep 17 00:00:00 2001 From: sibowler Date: Sat, 16 Oct 2021 07:41:31 +1100 Subject: [PATCH 04/12] Adding Number and Select as Tuya platforms. Provides support for a wider range of devices. --- custom_components/localtuya/const.py | 2 +- custom_components/localtuya/number.py | 86 +++++++++++++++++ custom_components/localtuya/select.py | 95 +++++++++++++++++++ .../localtuya/translations/en.json | 14 ++- 4 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 custom_components/localtuya/number.py create mode 100644 custom_components/localtuya/select.py diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index bd8a5d3..1bd74ca 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -46,6 +46,6 @@ DATA_DISCOVERY = "discovery" DOMAIN = "localtuya" # Platforms in this list must support config flows -PLATFORMS = ["binary_sensor", "cover", "fan", "light", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "cover", "fan", "light", "sensor", "switch", "number", "select"] TUYA_DEVICE = "tuya_device" diff --git a/custom_components/localtuya/number.py b/custom_components/localtuya/number.py new file mode 100644 index 0000000..50c1919 --- /dev/null +++ b/custom_components/localtuya/number.py @@ -0,0 +1,86 @@ +"""Platform to present any Tuya DP as a number.""" +import logging +from functools import partial + +import voluptuous as vol +from homeassistant.components.number import DOMAIN, NumberEntity +from homeassistant.const import ( + CONF_DEVICE_CLASS, + STATE_UNKNOWN, +) + +from .common import LocalTuyaEntity, async_setup_entry + +_LOGGER = logging.getLogger(__name__) + +CONF_MIN_VALUE = "min_value" +CONF_MAX_VALUE = "max_value" + +DEFAULT_MIN = 0 +DEFAULT_MAX = 100000 + +def flow_schema(dps): +# """Return schema used in config flow.""" + return { + vol.Optional(CONF_MIN_VALUE, default=DEFAULT_MIN): vol.All( + vol.Coerce(float), vol.Range(min=-1000000.0, max=1000000.0), + ), + vol.Required(CONF_MAX_VALUE, default=DEFAULT_MAX): vol.All( + vol.Coerce(float), vol.Range(min=-1000000.0, max=1000000.0), + ), + } + + +class LocaltuyaNumber(LocalTuyaEntity, NumberEntity): + """Representation of a Tuya Number.""" + + def __init__( + self, + device, + config_entry, + sensorid, + **kwargs, + ): + """Initialize the Tuya sensor.""" + super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs) + self._state = STATE_UNKNOWN + + self._minValue = DEFAULT_MIN + if (CONF_MIN_VALUE in self._config): + self._minValue = self._config.get(CONF_MIN_VALUE) + + self._maxValue = self._config.get(CONF_MAX_VALUE) + + @property + def value(self) -> float: + """Return sensor state.""" + return self._state + + @property + def min_value(self) -> float: + """Return the minimum value.""" + + return self._minValue + + @property + def max_value(self) -> float: + """Return the maximum value.""" + return self._maxValue + + @property + def device_class(self): + """Return the class of this device.""" + return self._config.get(CONF_DEVICE_CLASS) + + async def async_set_value(self, value: float) -> None: + """Update the current value.""" + await self._device.set_dp(value, self._dp_id) + + + def status_updated(self): + """Device status was updated.""" + state = self.dps(self._dp_id) + self._state = state + + +async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaNumber, flow_schema) diff --git a/custom_components/localtuya/select.py b/custom_components/localtuya/select.py new file mode 100644 index 0000000..2f70d99 --- /dev/null +++ b/custom_components/localtuya/select.py @@ -0,0 +1,95 @@ +"""Platform to present any Tuya DP as an enumeration.""" +import logging +from functools import partial + +import voluptuous as vol +from homeassistant.components.select import DOMAIN, SelectEntity +from homeassistant.const import ( + CONF_DEVICE_CLASS, + STATE_UNKNOWN, +) + +from .common import LocalTuyaEntity, async_setup_entry + +_LOGGER = logging.getLogger(__name__) + +CONF_OPTIONS = "select_options" +CONF_OPTIONS_FRIENDLY = "select_options_friendly" + + +def flow_schema(dps): +# """Return schema used in config flow.""" + return { + vol.Required(CONF_OPTIONS): str, + vol.Optional(CONF_OPTIONS_FRIENDLY): str, + } + + +class LocaltuyaSelect(LocalTuyaEntity, SelectEntity): + """Representation of a Tuya Enumeration.""" + + def __init__( + self, + device, + config_entry, + sensorid, + **kwargs, + ): + """Initialize the Tuya sensor.""" + super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs) + self._state = STATE_UNKNOWN + self._validOptions = self._config.get(CONF_OPTIONS).split(';') + + #Set Display options + self._displayOptions = [] + displayOptionsStr = "" + if (CONF_OPTIONS_FRIENDLY in self._config): + displayOptionsStr = self._config.get(CONF_OPTIONS_FRIENDLY).strip() + _LOGGER.debug("Display Options Configured: " + displayOptionsStr) + + if (displayOptionsStr.find(";") >= 0): + self._displayOptions = displayOptionsStr.split(';') + elif (len(displayOptionsStr.strip()) > 0): + self._displayOptions.append(displayOptionsStr) + else: + #Default display string to raw string + _LOGGER.debug("No Display options configured - defaulting to raw values") + self._displayOptions = self._validOptions + + _LOGGER.debug("Total Raw Options: " + str(len(self._validOptions)) + " - Total Display Options: " + str(len(self._displayOptions))) + if (len(self._validOptions) > len(self._displayOptions)): + #If list of display items smaller than list of valid items, then default remaining items to be the raw value + _LOGGER.debug("Valid options is larger than display options - filling up with raw values") + for i in range(len(self._displayOptions), len(self._validOptions)): + self._displayOptions.append(self._validOptions[i]) + + @property + def current_option(self) -> str: + """Return the current value.""" + return self._stateFriendly + + @property + def options(self) -> list: + """Return the list of values.""" + return self._displayOptions + + @property + def device_class(self): + """Return the class of this device.""" + return self._config.get(CONF_DEVICE_CLASS) + + async def async_select_option(self, option: str) -> None: + """Update the current value.""" + optionValue = self._validOptions[self._displayOptions.index(option)] + _LOGGER.debug("Sending Option: " + option + " -> " + optionValue) + await self._device.set_dp(optionValue, self._dp_id) + + + def status_updated(self): + """Device status was updated.""" + state = self.dps(self._dp_id) + self._stateFriendly = self._displayOptions[self._validOptions.index(state)] + self._state = state + + +async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSelect, flow_schema) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 6ce233a..a833f73 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -74,7 +74,11 @@ "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_high": "Fan High Speed Setting", + "max_value": "Maximum Value", + "min_value": "Minimum Value", + "select_options": "Valid entries, separate entries by a ;", + "select_options_friendly": "User Friendly options, separate entries by a ;" } } } @@ -124,9 +128,13 @@ "scene": "Scene", "fan_speed_control": "Fan Speed Control", "fan_oscillating_control": "Fan Oscillating Control", - "fan_speed_low": "Fan Low Speed Setting", + "fan_speed_low": "Fan Low Speed Settingaa", "fan_speed_medium": "Fan Medium Speed Setting", - "fan_speed_high": "Fan High Speed Setting" + "fan_speed_high": "Fan High Speed Setting", + "max_value": "Maximum Value", + "min_value": "Minimum Value", + "select_options": "Valid entries, separate entries by a ;", + "select_options_friendly": "User Friendly options, separate entries by a ;" } }, "yaml_import": { From 23a16babad08ba3b43f3e564ac2e673331d832c5 Mon Sep 17 00:00:00 2001 From: sibowler Date: Sat, 16 Oct 2021 07:43:33 +1100 Subject: [PATCH 05/12] Fix for typo introduced during debugging. --- custom_components/localtuya/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index a833f73..f12c4ab 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -128,7 +128,7 @@ "scene": "Scene", "fan_speed_control": "Fan Speed Control", "fan_oscillating_control": "Fan Oscillating Control", - "fan_speed_low": "Fan Low Speed Settingaa", + "fan_speed_low": "Fan Low Speed Setting", "fan_speed_medium": "Fan Medium Speed Setting", "fan_speed_high": "Fan High Speed Setting", "max_value": "Maximum Value", From fad7e0b1ac1e647f2f545a29745ecb5c671994c7 Mon Sep 17 00:00:00 2001 From: sibowler Date: Sat, 16 Oct 2021 07:46:56 +1100 Subject: [PATCH 06/12] Reordering platforms to be alphabetical --- custom_components/localtuya/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index 1bd74ca..af67876 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -46,6 +46,6 @@ DATA_DISCOVERY = "discovery" DOMAIN = "localtuya" # Platforms in this list must support config flows -PLATFORMS = ["binary_sensor", "cover", "fan", "light", "sensor", "switch", "number", "select"] +PLATFORMS = ["binary_sensor", "cover", "fan", "light", "number", "select", "sensor", "switch"] TUYA_DEVICE = "tuya_device" From 61896605b852a3e4f73b960edd7e2245be035b58 Mon Sep 17 00:00:00 2001 From: sibowler Date: Mon, 13 Dec 2021 06:08:53 +1100 Subject: [PATCH 07/12] Fixing up style issues --- custom_components/localtuya/const.py | 3 ++- custom_components/localtuya/number.py | 5 ++--- custom_components/localtuya/select.py | 16 +++++++++------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index af67876..b4b5817 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -46,6 +46,7 @@ DATA_DISCOVERY = "discovery" DOMAIN = "localtuya" # Platforms in this list must support config flows -PLATFORMS = ["binary_sensor", "cover", "fan", "light", "number", "select", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "cover", "fan", "light", "number", + "select", "sensor", "switch"] TUYA_DEVICE = "tuya_device" diff --git a/custom_components/localtuya/number.py b/custom_components/localtuya/number.py index 50c1919..1bd7ec4 100644 --- a/custom_components/localtuya/number.py +++ b/custom_components/localtuya/number.py @@ -19,8 +19,9 @@ CONF_MAX_VALUE = "max_value" DEFAULT_MIN = 0 DEFAULT_MAX = 100000 + def flow_schema(dps): -# """Return schema used in config flow.""" + """Return schema used in config flow.""" return { vol.Optional(CONF_MIN_VALUE, default=DEFAULT_MIN): vol.All( vol.Coerce(float), vol.Range(min=-1000000.0, max=1000000.0), @@ -59,7 +60,6 @@ class LocaltuyaNumber(LocalTuyaEntity, NumberEntity): @property def min_value(self) -> float: """Return the minimum value.""" - return self._minValue @property @@ -76,7 +76,6 @@ class LocaltuyaNumber(LocalTuyaEntity, NumberEntity): """Update the current value.""" await self._device.set_dp(value, self._dp_id) - def status_updated(self): """Device status was updated.""" state = self.dps(self._dp_id) diff --git a/custom_components/localtuya/select.py b/custom_components/localtuya/select.py index 2f70d99..90187b8 100644 --- a/custom_components/localtuya/select.py +++ b/custom_components/localtuya/select.py @@ -18,7 +18,7 @@ CONF_OPTIONS_FRIENDLY = "select_options_friendly" def flow_schema(dps): -# """Return schema used in config flow.""" + """Return schema used in config flow.""" return { vol.Required(CONF_OPTIONS): str, vol.Optional(CONF_OPTIONS_FRIENDLY): str, @@ -40,7 +40,7 @@ class LocaltuyaSelect(LocalTuyaEntity, SelectEntity): self._state = STATE_UNKNOWN self._validOptions = self._config.get(CONF_OPTIONS).split(';') - #Set Display options + # Set Display options self._displayOptions = [] displayOptionsStr = "" if (CONF_OPTIONS_FRIENDLY in self._config): @@ -52,14 +52,17 @@ class LocaltuyaSelect(LocalTuyaEntity, SelectEntity): elif (len(displayOptionsStr.strip()) > 0): self._displayOptions.append(displayOptionsStr) else: - #Default display string to raw string + # Default display string to raw string _LOGGER.debug("No Display options configured - defaulting to raw values") self._displayOptions = self._validOptions - _LOGGER.debug("Total Raw Options: " + str(len(self._validOptions)) + " - Total Display Options: " + str(len(self._displayOptions))) + _LOGGER.debug("Total Raw Options: " + str(len(self._validOptions)) + + " - Total Display Options: " + str(len(self._displayOptions))) if (len(self._validOptions) > len(self._displayOptions)): - #If list of display items smaller than list of valid items, then default remaining items to be the raw value - _LOGGER.debug("Valid options is larger than display options - filling up with raw values") + # If list of display items smaller than list of valid items, + # then default remaining items to be the raw value + _LOGGER.debug("Valid options is larger than display options - \ + filling up with raw values") for i in range(len(self._displayOptions), len(self._validOptions)): self._displayOptions.append(self._validOptions[i]) @@ -84,7 +87,6 @@ class LocaltuyaSelect(LocalTuyaEntity, SelectEntity): _LOGGER.debug("Sending Option: " + option + " -> " + optionValue) await self._device.set_dp(optionValue, self._dp_id) - def status_updated(self): """Device status was updated.""" state = self.dps(self._dp_id) From a0fff6ee2f9e0982bb14cb338b88765f4117f34d Mon Sep 17 00:00:00 2001 From: sibowler Date: Tue, 14 Dec 2021 06:16:31 +1100 Subject: [PATCH 08/12] Fixing up style errors in line with tox. --- custom_components/localtuya/const.py | 12 +++++- custom_components/localtuya/number.py | 18 +++++---- custom_components/localtuya/select.py | 56 +++++++++++++++------------ 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index b4b5817..12fa66c 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -46,7 +46,15 @@ DATA_DISCOVERY = "discovery" DOMAIN = "localtuya" # Platforms in this list must support config flows -PLATFORMS = ["binary_sensor", "cover", "fan", "light", "number", - "select", "sensor", "switch"] +PLATFORMS = [ + "binary_sensor", + "cover", + "fan", + "light", + "number", + "select", + "sensor", + "switch", +] TUYA_DEVICE = "tuya_device" diff --git a/custom_components/localtuya/number.py b/custom_components/localtuya/number.py index 1bd7ec4..328f6a2 100644 --- a/custom_components/localtuya/number.py +++ b/custom_components/localtuya/number.py @@ -24,10 +24,12 @@ def flow_schema(dps): """Return schema used in config flow.""" return { vol.Optional(CONF_MIN_VALUE, default=DEFAULT_MIN): vol.All( - vol.Coerce(float), vol.Range(min=-1000000.0, max=1000000.0), + vol.Coerce(float), + vol.Range(min=-1000000.0, max=1000000.0), ), vol.Required(CONF_MAX_VALUE, default=DEFAULT_MAX): vol.All( - vol.Coerce(float), vol.Range(min=-1000000.0, max=1000000.0), + vol.Coerce(float), + vol.Range(min=-1000000.0, max=1000000.0), ), } @@ -46,11 +48,11 @@ class LocaltuyaNumber(LocalTuyaEntity, NumberEntity): super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs) self._state = STATE_UNKNOWN - self._minValue = DEFAULT_MIN - if (CONF_MIN_VALUE in self._config): - self._minValue = self._config.get(CONF_MIN_VALUE) + self._min_value = DEFAULT_MIN + if CONF_MIN_VALUE in self._config: + self._min_value = self._config.get(CONF_MIN_VALUE) - self._maxValue = self._config.get(CONF_MAX_VALUE) + self._max_value = self._config.get(CONF_MAX_VALUE) @property def value(self) -> float: @@ -60,12 +62,12 @@ class LocaltuyaNumber(LocalTuyaEntity, NumberEntity): @property def min_value(self) -> float: """Return the minimum value.""" - return self._minValue + return self._min_value @property def max_value(self) -> float: """Return the maximum value.""" - return self._maxValue + return self._max_value @property def device_class(self): diff --git a/custom_components/localtuya/select.py b/custom_components/localtuya/select.py index 90187b8..4d9ce54 100644 --- a/custom_components/localtuya/select.py +++ b/custom_components/localtuya/select.py @@ -38,43 +38,49 @@ class LocaltuyaSelect(LocalTuyaEntity, SelectEntity): """Initialize the Tuya sensor.""" super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs) self._state = STATE_UNKNOWN - self._validOptions = self._config.get(CONF_OPTIONS).split(';') + self._state_friendly = "" + self._valid_options = self._config.get(CONF_OPTIONS).split(";") # Set Display options - self._displayOptions = [] - displayOptionsStr = "" - if (CONF_OPTIONS_FRIENDLY in self._config): - displayOptionsStr = self._config.get(CONF_OPTIONS_FRIENDLY).strip() - _LOGGER.debug("Display Options Configured: " + displayOptionsStr) + self._display_options = [] + display_options_str = "" + if CONF_OPTIONS_FRIENDLY in self._config: + display_options_str = self._config.get(CONF_OPTIONS_FRIENDLY).strip() + _LOGGER.debug("Display Options Configured: %s", display_options_str) - if (displayOptionsStr.find(";") >= 0): - self._displayOptions = displayOptionsStr.split(';') - elif (len(displayOptionsStr.strip()) > 0): - self._displayOptions.append(displayOptionsStr) + if display_options_str.find(";") >= 0: + self._display_options = display_options_str.split(";") + elif len(display_options_str.strip()) > 0: + self._display_options.append(display_options_str) else: # Default display string to raw string _LOGGER.debug("No Display options configured - defaulting to raw values") - self._displayOptions = self._validOptions + self._display_options = self._valid_options - _LOGGER.debug("Total Raw Options: " + str(len(self._validOptions)) + - " - Total Display Options: " + str(len(self._displayOptions))) - if (len(self._validOptions) > len(self._displayOptions)): - # If list of display items smaller than list of valid items, + _LOGGER.debug( + "Total Raw Options: %s - Total Display Options: %s", + str(len(self._valid_options)), + str(len(self._display_options)) + ) + if len(self._valid_options) > len(self._display_options): + # If list of display items smaller than list of valid items, # then default remaining items to be the raw value - _LOGGER.debug("Valid options is larger than display options - \ - filling up with raw values") - for i in range(len(self._displayOptions), len(self._validOptions)): - self._displayOptions.append(self._validOptions[i]) + _LOGGER.debug( + "Valid options is larger than display options - \ + filling up with raw values" + ) + for i in range(len(self._display_options), len(self._valid_options)): + self._display_options.append(self._valid_options[i]) @property def current_option(self) -> str: """Return the current value.""" - return self._stateFriendly + return self._state_friendly @property def options(self) -> list: """Return the list of values.""" - return self._displayOptions + return self._display_options @property def device_class(self): @@ -83,14 +89,14 @@ class LocaltuyaSelect(LocalTuyaEntity, SelectEntity): async def async_select_option(self, option: str) -> None: """Update the current value.""" - optionValue = self._validOptions[self._displayOptions.index(option)] - _LOGGER.debug("Sending Option: " + option + " -> " + optionValue) - await self._device.set_dp(optionValue, self._dp_id) + option_value = self._valid_options[self._display_options.index(option)] + _LOGGER.debug("Sending Option: " + option + " -> " + option_value) + await self._device.set_dp(option_value, self._dp_id) def status_updated(self): """Device status was updated.""" state = self.dps(self._dp_id) - self._stateFriendly = self._displayOptions[self._validOptions.index(state)] + self._state_friendly = self._display_options[self._valid_options.index(state)] self._state = state From 1b53c227918bde30107c0874426eaad6fcf1183c Mon Sep 17 00:00:00 2001 From: sibowler Date: Tue, 14 Dec 2021 08:51:53 +1100 Subject: [PATCH 09/12] black styling --- custom_components/localtuya/select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/select.py b/custom_components/localtuya/select.py index 4d9ce54..43b32c9 100644 --- a/custom_components/localtuya/select.py +++ b/custom_components/localtuya/select.py @@ -60,7 +60,7 @@ class LocaltuyaSelect(LocalTuyaEntity, SelectEntity): _LOGGER.debug( "Total Raw Options: %s - Total Display Options: %s", str(len(self._valid_options)), - str(len(self._display_options)) + str(len(self._display_options)), ) if len(self._valid_options) > len(self._display_options): # If list of display items smaller than list of valid items, From 5a4167fe5b18cb1e422f1f363bafd70a59b9b485 Mon Sep 17 00:00:00 2001 From: sibowler Date: Tue, 14 Dec 2021 19:49:10 +1100 Subject: [PATCH 10/12] Fix dependencies to account for Select type. Also fixup errors in Fan due to movement in dependencies. --- custom_components/localtuya/fan.py | 8 +++++++- requirements_test.txt | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 834e199..0c65dfa 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -79,7 +79,13 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity): """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: + async def async_turn_on( + self, + speed: str = None, + percentage: int = None, + preset_mode: str = None, + **kwargs, + ) -> None: """Turn on the entity.""" await self._device.set_dp(True, self._dp_id) if speed is not None: diff --git a/requirements_test.txt b/requirements_test.txt index df62e77..a4e209a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -3,7 +3,7 @@ codespell==2.0.0 flake8==3.9.2 mypy==0.901 pydocstyle==6.1.1 -cryptography==3.2 +cryptography==3.3.2 pylint==2.8.2 pylint-strict-informational==0.1 -homeassistant==2021.1.4 +homeassistant==2021.7.1 From a6738691202a6b59eecf7c4541440c10223bf47c Mon Sep 17 00:00:00 2001 From: sibowler Date: Tue, 14 Dec 2021 19:50:17 +1100 Subject: [PATCH 11/12] Adding Select and Number dependencies --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 2d94fd3..562dc77 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,7 @@ warn_incomplete_stub = true warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*] +[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,homeassistant.components.select.*,homeassistant.components.number.*] strict = true ignore_errors = false warn_unreachable = true From 76b86eb6e11452d6cffea541a63805b24a1ee718 Mon Sep 17 00:00:00 2001 From: sibowler Date: Tue, 14 Dec 2021 19:51:39 +1100 Subject: [PATCH 12/12] Adding number and select types to hacs definition. --- hacs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hacs.json b/hacs.json index 4b6ec05..3c5a41a 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { "name": "Local Tuya", - "domains": ["climate", "cover", "fan", "light", "sensor", "switch"], + "domains": ["climate", "cover", "fan", "light", "number", "select", "sensor", "switch"], "homeassistant": "0.116.0", "iot_class": ["Local Push"] }