Merge remote-tracking branch 'upstream/master' into update_interval
This commit is contained in:
@@ -119,6 +119,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
|
||||
self.dps_to_request = {}
|
||||
self._is_closing = False
|
||||
self._connect_task = None
|
||||
self._disconnect_task = None
|
||||
self._unsub_interval = None
|
||||
self.set_logger(_LOGGER, config_entry[CONF_DEVICE_ID])
|
||||
|
||||
@@ -137,6 +138,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:
|
||||
@@ -155,6 +157,20 @@ 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()
|
||||
|
||||
signal = f"localtuya_entity_{self._config_entry[CONF_DEVICE_ID]}"
|
||||
self._disconnect_task = async_dispatcher_connect(
|
||||
self._hass, signal, _new_entity_handler
|
||||
)
|
||||
|
||||
if (
|
||||
CONF_SCAN_INTERVAL in self._config_entry
|
||||
and self._config_entry[CONF_SCAN_INTERVAL] > 0
|
||||
@@ -183,6 +199,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."""
|
||||
@@ -212,7 +230,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)
|
||||
|
||||
@@ -262,10 +282,14 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
signal = f"localtuya_{self._config_entry.data[CONF_DEVICE_ID]}"
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, signal, _update_handler)
|
||||
)
|
||||
|
||||
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):
|
||||
"""Return device information for the device registry."""
|
||||
|
@@ -46,6 +46,15 @@ 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",
|
||||
"number",
|
||||
"select",
|
||||
"sensor",
|
||||
"switch",
|
||||
]
|
||||
|
||||
TUYA_DEVICE = "tuya_device"
|
||||
|
@@ -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:
|
||||
|
87
custom_components/localtuya/number.py
Normal file
87
custom_components/localtuya/number.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""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._min_value = DEFAULT_MIN
|
||||
if CONF_MIN_VALUE in self._config:
|
||||
self._min_value = self._config.get(CONF_MIN_VALUE)
|
||||
|
||||
self._max_value = 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._min_value
|
||||
|
||||
@property
|
||||
def max_value(self) -> float:
|
||||
"""Return the maximum value."""
|
||||
return self._max_value
|
||||
|
||||
@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)
|
103
custom_components/localtuya/select.py
Normal file
103
custom_components/localtuya/select.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""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._state_friendly = ""
|
||||
self._valid_options = self._config.get(CONF_OPTIONS).split(";")
|
||||
|
||||
# Set Display options
|
||||
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 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._display_options = self._valid_options
|
||||
|
||||
_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._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._state_friendly
|
||||
|
||||
@property
|
||||
def options(self) -> list:
|
||||
"""Return the list of values."""
|
||||
return self._display_options
|
||||
|
||||
@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."""
|
||||
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._state_friendly = self._display_options[self._valid_options.index(state)]
|
||||
self._state = state
|
||||
|
||||
|
||||
async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaSelect, flow_schema)
|
@@ -75,7 +75,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 ;"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +132,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 ;"
|
||||
}
|
||||
},
|
||||
"yaml_import": {
|
||||
|
Reference in New Issue
Block a user