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": {