Update tuyaha to 0.0.8 and adapt code (#41375)
This commit is contained in:
parent
2858a90d5c
commit
c4b3cf0788
11 changed files with 776 additions and 51 deletions
|
@ -4,12 +4,17 @@ from datetime import timedelta
|
|||
import logging
|
||||
|
||||
from tuyaha import TuyaApi
|
||||
from tuyaha.tuyaapi import TuyaAPIException, TuyaNetException, TuyaServerException
|
||||
from tuyaha.tuyaapi import (
|
||||
TuyaAPIException,
|
||||
TuyaFrequentlyInvokeException,
|
||||
TuyaNetException,
|
||||
TuyaServerException,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
|
@ -21,24 +26,30 @@ from homeassistant.helpers.event import async_track_time_interval
|
|||
|
||||
from .const import (
|
||||
CONF_COUNTRYCODE,
|
||||
CONF_DISCOVERY_INTERVAL,
|
||||
CONF_QUERY_DEVICE,
|
||||
CONF_QUERY_INTERVAL,
|
||||
DEFAULT_DISCOVERY_INTERVAL,
|
||||
DEFAULT_QUERY_INTERVAL,
|
||||
DOMAIN,
|
||||
SIGNAL_CONFIG_ENTITY,
|
||||
SIGNAL_DELETE_ENTITY,
|
||||
SIGNAL_UPDATE_ENTITY,
|
||||
TUYA_DATA,
|
||||
TUYA_DEVICES_CONF,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
TUYA_PLATFORMS,
|
||||
TUYA_TYPE_NOT_QUERY,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_TUYA_DEV_ID = "tuya_device_id"
|
||||
ENTRY_IS_SETUP = "tuya_entry_is_setup"
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
SERVICE_FORCE_UPDATE = "force_update"
|
||||
SERVICE_PULL_DEVICES = "pull_devices"
|
||||
|
||||
SIGNAL_DELETE_ENTITY = "tuya_delete"
|
||||
SIGNAL_UPDATE_ENTITY = "tuya_update"
|
||||
|
||||
TUYA_TYPE_TO_HA = {
|
||||
"climate": "climate",
|
||||
"cover": "cover",
|
||||
|
@ -56,9 +67,9 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_COUNTRYCODE): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_PLATFORM, default="tuya"): cv.string,
|
||||
}
|
||||
)
|
||||
|
@ -68,6 +79,30 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
def _update_discovery_interval(hass, interval):
|
||||
tuya = hass.data[DOMAIN].get(TUYA_DATA)
|
||||
if not tuya:
|
||||
return
|
||||
|
||||
try:
|
||||
tuya.discovery_interval = interval
|
||||
_LOGGER.info("Tuya discovery device poll interval set to %s seconds", interval)
|
||||
except ValueError as ex:
|
||||
_LOGGER.warning(ex)
|
||||
|
||||
|
||||
def _update_query_interval(hass, interval):
|
||||
tuya = hass.data[DOMAIN].get(TUYA_DATA)
|
||||
if not tuya:
|
||||
return
|
||||
|
||||
try:
|
||||
tuya.query_interval = interval
|
||||
_LOGGER.info("Tuya query device poll interval set to %s seconds", interval)
|
||||
except ValueError as ex:
|
||||
_LOGGER.warning(ex)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Tuya integration."""
|
||||
|
||||
|
@ -82,7 +117,7 @@ async def async_setup(hass, config):
|
|||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry):
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up Tuya platform."""
|
||||
|
||||
tuya = TuyaApi()
|
||||
|
@ -95,7 +130,11 @@ async def async_setup_entry(hass, entry):
|
|||
await hass.async_add_executor_job(
|
||||
tuya.init, username, password, country_code, platform
|
||||
)
|
||||
except (TuyaNetException, TuyaServerException) as exc:
|
||||
except (
|
||||
TuyaNetException,
|
||||
TuyaServerException,
|
||||
TuyaFrequentlyInvokeException,
|
||||
) as exc:
|
||||
raise ConfigEntryNotReady() from exc
|
||||
|
||||
except TuyaAPIException as exc:
|
||||
|
@ -107,12 +146,22 @@ async def async_setup_entry(hass, entry):
|
|||
|
||||
hass.data[DOMAIN] = {
|
||||
TUYA_DATA: tuya,
|
||||
TUYA_DEVICES_CONF: entry.options.copy(),
|
||||
TUYA_TRACKER: None,
|
||||
ENTRY_IS_SETUP: set(),
|
||||
"entities": {},
|
||||
"pending": {},
|
||||
"listener": entry.add_update_listener(update_listener),
|
||||
}
|
||||
|
||||
_update_discovery_interval(
|
||||
hass, entry.options.get(CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL)
|
||||
)
|
||||
|
||||
_update_query_interval(
|
||||
hass, entry.options.get(CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL)
|
||||
)
|
||||
|
||||
async def async_load_devices(device_list):
|
||||
"""Load new devices by device_list."""
|
||||
device_type_list = {}
|
||||
|
@ -139,11 +188,13 @@ async def async_setup_entry(hass, entry):
|
|||
else:
|
||||
async_dispatcher_send(hass, TUYA_DISCOVERY_NEW.format(ha_type), dev_ids)
|
||||
|
||||
device_list = await hass.async_add_executor_job(tuya.get_all_devices)
|
||||
await async_load_devices(device_list)
|
||||
await async_load_devices(tuya.get_all_devices())
|
||||
|
||||
def _get_updated_devices():
|
||||
tuya.poll_devices_update()
|
||||
try:
|
||||
tuya.poll_devices_update()
|
||||
except TuyaFrequentlyInvokeException as exc:
|
||||
_LOGGER.error(exc)
|
||||
return tuya.get_all_devices()
|
||||
|
||||
async def async_poll_devices_update(event_time):
|
||||
|
@ -162,7 +213,7 @@ async def async_setup_entry(hass, entry):
|
|||
hass.data[DOMAIN]["entities"].pop(dev_id)
|
||||
|
||||
hass.data[DOMAIN][TUYA_TRACKER] = async_track_time_interval(
|
||||
hass, async_poll_devices_update, timedelta(minutes=5)
|
||||
hass, async_poll_devices_update, timedelta(minutes=2)
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
|
@ -178,7 +229,7 @@ async def async_setup_entry(hass, entry):
|
|||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, entry):
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unloading the Tuya platforms."""
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
|
@ -191,10 +242,8 @@ async def async_unload_entry(hass, entry):
|
|||
)
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN][ENTRY_IS_SETUP] = set()
|
||||
hass.data[DOMAIN]["listener"]()
|
||||
hass.data[DOMAIN][TUYA_TRACKER]()
|
||||
hass.data[DOMAIN][TUYA_TRACKER] = None
|
||||
hass.data[DOMAIN][TUYA_DATA] = None
|
||||
hass.services.async_remove(DOMAIN, SERVICE_FORCE_UPDATE)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_PULL_DEVICES)
|
||||
hass.data.pop(DOMAIN)
|
||||
|
@ -202,20 +251,86 @@ async def async_unload_entry(hass, entry):
|
|||
return unload_ok
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Update when config_entry options update."""
|
||||
hass.data[DOMAIN][TUYA_DEVICES_CONF] = entry.options.copy()
|
||||
_update_discovery_interval(
|
||||
hass, entry.options.get(CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL)
|
||||
)
|
||||
_update_query_interval(
|
||||
hass, entry.options.get(CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL)
|
||||
)
|
||||
async_dispatcher_send(hass, SIGNAL_CONFIG_ENTITY)
|
||||
|
||||
|
||||
async def cleanup_device_registry(hass: HomeAssistant, device_id):
|
||||
"""Remove device registry entry if there are no remaining entities."""
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
if device_id and not hass.helpers.entity_registry.async_entries_for_device(
|
||||
entity_registry, device_id
|
||||
):
|
||||
device_registry.async_remove_device(device_id)
|
||||
|
||||
|
||||
class TuyaDevice(Entity):
|
||||
"""Tuya base device."""
|
||||
|
||||
_dev_can_query_count = 0
|
||||
|
||||
def __init__(self, tuya, platform):
|
||||
"""Init Tuya devices."""
|
||||
self._tuya = tuya
|
||||
self._tuya_platform = platform
|
||||
|
||||
def _device_can_query(self):
|
||||
"""Check if device can also use query method."""
|
||||
dev_type = self._tuya.device_type()
|
||||
return dev_type not in TUYA_TYPE_NOT_QUERY
|
||||
|
||||
def _inc_device_count(self):
|
||||
"""Increment static variable device count."""
|
||||
if not self._device_can_query():
|
||||
return
|
||||
TuyaDevice._dev_can_query_count += 1
|
||||
|
||||
def _dec_device_count(self):
|
||||
"""Decrement static variable device count."""
|
||||
if not self._device_can_query():
|
||||
return
|
||||
TuyaDevice._dev_can_query_count -= 1
|
||||
|
||||
def _get_device_config(self):
|
||||
"""Get updated device options."""
|
||||
devices_config = self.hass.data[DOMAIN].get(TUYA_DEVICES_CONF)
|
||||
if not devices_config:
|
||||
return {}
|
||||
dev_conf = devices_config.get(self.object_id, {})
|
||||
if dev_conf:
|
||||
_LOGGER.debug(
|
||||
"Configuration for deviceID %s: %s", self.object_id, str(dev_conf)
|
||||
)
|
||||
return dev_conf
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
dev_id = self._tuya.object_id()
|
||||
self.hass.data[DOMAIN]["entities"][dev_id] = self.entity_id
|
||||
async_dispatcher_connect(self.hass, SIGNAL_DELETE_ENTITY, self._delete_callback)
|
||||
async_dispatcher_connect(self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback)
|
||||
self.hass.data[DOMAIN]["entities"][self.object_id] = self.entity_id
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_DELETE_ENTITY, self._delete_callback
|
||||
)
|
||||
)
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback
|
||||
)
|
||||
)
|
||||
self._inc_device_count()
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Call when entity is removed from hass."""
|
||||
self._dec_device_count()
|
||||
|
||||
@property
|
||||
def object_id(self):
|
||||
|
@ -252,7 +367,14 @@ class TuyaDevice(Entity):
|
|||
|
||||
def update(self):
|
||||
"""Refresh Tuya device data."""
|
||||
self._tuya.update()
|
||||
query_dev = self.hass.data[DOMAIN][TUYA_DEVICES_CONF].get(CONF_QUERY_DEVICE, "")
|
||||
use_discovery = (
|
||||
TuyaDevice._dev_can_query_count > 1 and self.object_id != query_dev
|
||||
)
|
||||
try:
|
||||
self._tuya.update(use_discovery=use_discovery)
|
||||
except TuyaFrequentlyInvokeException as exc:
|
||||
_LOGGER.error(exc)
|
||||
|
||||
async def _delete_callback(self, dev_id):
|
||||
"""Remove this entity."""
|
||||
|
@ -261,7 +383,9 @@ class TuyaDevice(Entity):
|
|||
await self.hass.helpers.entity_registry.async_get_registry()
|
||||
)
|
||||
if entity_registry.async_is_registered(self.entity_id):
|
||||
entity_entry = entity_registry.async_get(self.entity_id)
|
||||
entity_registry.async_remove(self.entity_id)
|
||||
await cleanup_device_registry(self.hass, entity_entry.device_id)
|
||||
else:
|
||||
await self.async_remove()
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
"""Support for the Tuya climate devices."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
|
@ -19,18 +22,32 @@ from homeassistant.components.climate.const import (
|
|||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_PLATFORM,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
ENTITY_MATCH_NONE,
|
||||
PRECISION_TENTHS,
|
||||
PRECISION_WHOLE,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import callback, valid_entity_id
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
from .const import (
|
||||
CONF_CURR_TEMP_DIVIDER,
|
||||
CONF_EXT_TEMP_SENSOR,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_TEMP_DIVIDER,
|
||||
DOMAIN,
|
||||
SIGNAL_CONFIG_ENTITY,
|
||||
TUYA_DATA,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
)
|
||||
|
||||
DEVICE_TYPE = "climate"
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
HA_STATE_TO_TUYA = {
|
||||
HVAC_MODE_AUTO: "auto",
|
||||
|
@ -43,6 +60,8 @@ TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()}
|
|||
|
||||
FAN_MODES = {FAN_LOW, FAN_MEDIUM, FAN_HIGH}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up tuya sensors dynamically through tuya discovery."""
|
||||
|
@ -89,21 +108,62 @@ class TuyaClimateEntity(TuyaDevice, ClimateEntity):
|
|||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
self.operations = [HVAC_MODE_OFF]
|
||||
self._has_operation = False
|
||||
self._def_hvac_mode = HVAC_MODE_AUTO
|
||||
self._min_temp = None
|
||||
self._max_temp = None
|
||||
self._temp_entity = None
|
||||
self._temp_entity_error = False
|
||||
|
||||
@callback
|
||||
def _process_config(self):
|
||||
"""Set device config parameter."""
|
||||
config = self._get_device_config()
|
||||
if not config:
|
||||
return
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
if unit:
|
||||
self._tuya.set_unit("FAHRENHEIT" if unit == TEMP_FAHRENHEIT else "CELSIUS")
|
||||
self._tuya.temp_divider = config.get(CONF_TEMP_DIVIDER, 0)
|
||||
self._tuya.curr_temp_divider = config.get(CONF_CURR_TEMP_DIVIDER, 0)
|
||||
min_temp = config.get(CONF_MIN_TEMP, 0)
|
||||
max_temp = config.get(CONF_MAX_TEMP, 0)
|
||||
if min_temp >= max_temp:
|
||||
self._min_temp = self._max_temp = None
|
||||
else:
|
||||
self._min_temp = min_temp
|
||||
self._max_temp = max_temp
|
||||
self._temp_entity = config.get(CONF_EXT_TEMP_SENSOR)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Create operation list when add to hass."""
|
||||
await super().async_added_to_hass()
|
||||
self._process_config()
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
|
||||
)
|
||||
)
|
||||
|
||||
modes = self._tuya.operation_list()
|
||||
if modes is None:
|
||||
if self._def_hvac_mode not in self.operations:
|
||||
self.operations.append(self._def_hvac_mode)
|
||||
return
|
||||
|
||||
for mode in modes:
|
||||
if mode in TUYA_STATE_TO_HA:
|
||||
self.operations.append(TUYA_STATE_TO_HA[mode])
|
||||
if mode not in TUYA_STATE_TO_HA:
|
||||
continue
|
||||
ha_mode = TUYA_STATE_TO_HA[mode]
|
||||
if ha_mode not in self.operations:
|
||||
self.operations.append(ha_mode)
|
||||
self._has_operation = True
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
"""Return the precision of the system."""
|
||||
if self._tuya.has_decimal():
|
||||
return PRECISION_TENTHS
|
||||
return PRECISION_WHOLE
|
||||
|
||||
@property
|
||||
|
@ -120,6 +180,9 @@ class TuyaClimateEntity(TuyaDevice, ClimateEntity):
|
|||
if not self._tuya.state():
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
if not self._has_operation:
|
||||
return self._def_hvac_mode
|
||||
|
||||
mode = self._tuya.current_operation()
|
||||
if mode is None:
|
||||
return None
|
||||
|
@ -133,7 +196,10 @@ class TuyaClimateEntity(TuyaDevice, ClimateEntity):
|
|||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._tuya.current_temperature()
|
||||
curr_temp = self._tuya.current_temperature()
|
||||
if curr_temp is None:
|
||||
return self._get_ext_temperature()
|
||||
return curr_temp
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
@ -168,11 +234,13 @@ class TuyaClimateEntity(TuyaDevice, ClimateEntity):
|
|||
"""Set new target operation mode."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self._tuya.turn_off()
|
||||
return
|
||||
|
||||
if not self._tuya.state():
|
||||
self._tuya.turn_on()
|
||||
|
||||
self._tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode))
|
||||
if self._has_operation:
|
||||
self._tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode))
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -187,9 +255,55 @@ class TuyaClimateEntity(TuyaDevice, ClimateEntity):
|
|||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
return self._tuya.min_temp()
|
||||
min_temp = (
|
||||
self._min_temp if self._min_temp is not None else self._tuya.min_temp()
|
||||
)
|
||||
if min_temp is not None:
|
||||
return min_temp
|
||||
return super().min_temp
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
return self._tuya.max_temp()
|
||||
max_temp = (
|
||||
self._max_temp if self._max_temp is not None else self._tuya.max_temp()
|
||||
)
|
||||
if max_temp is not None:
|
||||
return max_temp
|
||||
return super().max_temp
|
||||
|
||||
def _set_and_log_temp_error(self, error_msg):
|
||||
if not self._temp_entity_error:
|
||||
_LOGGER.warning(
|
||||
"Error on Tuya external temperature sensor %s: %s",
|
||||
self._temp_entity,
|
||||
error_msg,
|
||||
)
|
||||
self._temp_entity_error = True
|
||||
|
||||
def _get_ext_temperature(self):
|
||||
"""Get external temperature entity current state."""
|
||||
if not self._temp_entity or self._temp_entity == ENTITY_MATCH_NONE:
|
||||
return None
|
||||
|
||||
entity_name = self._temp_entity
|
||||
if not valid_entity_id(entity_name):
|
||||
self._set_and_log_temp_error("entity name is invalid")
|
||||
return None
|
||||
|
||||
state_obj = self.hass.states.get(entity_name)
|
||||
if state_obj:
|
||||
temperature = state_obj.state
|
||||
try:
|
||||
float(temperature)
|
||||
except (TypeError, ValueError):
|
||||
self._set_and_log_temp_error(
|
||||
"entity state is not available or is not a number"
|
||||
)
|
||||
return None
|
||||
|
||||
self._temp_entity_error = False
|
||||
return temperature
|
||||
|
||||
self._set_and_log_temp_error("entity not found")
|
||||
return None
|
||||
|
|
|
@ -6,13 +6,47 @@ from tuyaha.tuyaapi import TuyaAPIException, TuyaNetException, TuyaServerExcepti
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_USERNAME,
|
||||
ENTITY_MATCH_NONE,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
# pylint:disable=unused-import
|
||||
from .const import CONF_COUNTRYCODE, DOMAIN, TUYA_PLATFORMS
|
||||
from .const import (
|
||||
CONF_BRIGHTNESS_RANGE_MODE,
|
||||
CONF_COUNTRYCODE,
|
||||
CONF_CURR_TEMP_DIVIDER,
|
||||
CONF_DISCOVERY_INTERVAL,
|
||||
CONF_EXT_TEMP_SENSOR,
|
||||
CONF_MAX_KELVIN,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_KELVIN,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_QUERY_DEVICE,
|
||||
CONF_QUERY_INTERVAL,
|
||||
CONF_SUPPORT_COLOR,
|
||||
CONF_TEMP_DIVIDER,
|
||||
CONF_TUYA_MAX_COLTEMP,
|
||||
DEFAULT_DISCOVERY_INTERVAL,
|
||||
DEFAULT_QUERY_INTERVAL,
|
||||
DEFAULT_TUYA_MAX_COLTEMP,
|
||||
DOMAIN,
|
||||
TUYA_DATA,
|
||||
TUYA_PLATFORMS,
|
||||
TUYA_TYPE_NOT_QUERY,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_LIST_DEVICES = "list_devices"
|
||||
|
||||
DATA_SCHEMA_USER = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
|
@ -22,6 +56,10 @@ DATA_SCHEMA_USER = vol.Schema(
|
|||
}
|
||||
)
|
||||
|
||||
ERROR_DEV_MULTI_TYPE = "dev_multi_type"
|
||||
ERROR_DEV_NOT_CONFIG = "dev_not_config"
|
||||
ERROR_DEV_NOT_FOUND = "dev_not_found"
|
||||
|
||||
RESULT_AUTH_FAILED = "invalid_auth"
|
||||
RESULT_CONN_ERROR = "cannot_connect"
|
||||
RESULT_SUCCESS = "success"
|
||||
|
@ -31,6 +69,8 @@ RESULT_LOG_MESSAGE = {
|
|||
RESULT_CONN_ERROR: "Connection error",
|
||||
}
|
||||
|
||||
TUYA_TYPE_CONFIG = ["climate", "light"]
|
||||
|
||||
|
||||
class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a tuya config flow."""
|
||||
|
@ -46,7 +86,7 @@ class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self._username = None
|
||||
self._is_import = False
|
||||
|
||||
def _get_entry(self):
|
||||
def _save_entry(self):
|
||||
return self.async_create_entry(
|
||||
title=self._username,
|
||||
data={
|
||||
|
@ -93,7 +133,7 @@ class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
result = await self.hass.async_add_executor_job(self._try_connect)
|
||||
|
||||
if result == RESULT_SUCCESS:
|
||||
return self._get_entry()
|
||||
return self._save_entry()
|
||||
if result != RESULT_AUTH_FAILED or self._is_import:
|
||||
if self._is_import:
|
||||
_LOGGER.error(
|
||||
|
@ -106,3 +146,263 @@ class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Get the options flow for this handler."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle a option flow for Tuya."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry):
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
self._conf_devs_id = None
|
||||
self._conf_devs_option = {}
|
||||
self._form_error = None
|
||||
|
||||
def _get_form_error(self):
|
||||
"""Set the error to be shown in the options form."""
|
||||
errors = {}
|
||||
if self._form_error:
|
||||
errors["base"] = self._form_error
|
||||
self._form_error = None
|
||||
return errors
|
||||
|
||||
def _get_tuya_devices_filtered(self, types, exclude_mode=False, type_prefix=True):
|
||||
"""Get the list of Tuya device to filtered by types."""
|
||||
config_list = {}
|
||||
types_filter = set(types)
|
||||
tuya = self.hass.data[DOMAIN][TUYA_DATA]
|
||||
devices_list = tuya.get_all_devices()
|
||||
for device in devices_list:
|
||||
dev_type = device.device_type()
|
||||
exclude = (
|
||||
dev_type in types_filter
|
||||
if exclude_mode
|
||||
else dev_type not in types_filter
|
||||
)
|
||||
if exclude:
|
||||
continue
|
||||
dev_id = device.object_id()
|
||||
if type_prefix:
|
||||
dev_id = f"{dev_type}-{dev_id}"
|
||||
config_list[dev_id] = f"{device.name()} ({dev_type})"
|
||||
|
||||
return config_list
|
||||
|
||||
def _get_device(self, dev_id):
|
||||
"""Get specific device from tuya library."""
|
||||
tuya = self.hass.data[DOMAIN][TUYA_DATA]
|
||||
return tuya.get_device_by_id(dev_id)
|
||||
|
||||
def _save_config(self, data):
|
||||
"""Save the updated options."""
|
||||
curr_conf = self.config_entry.options.copy()
|
||||
curr_conf.update(data)
|
||||
curr_conf.update(self._conf_devs_option)
|
||||
|
||||
return self.async_create_entry(title="", data=curr_conf)
|
||||
|
||||
async def _async_device_form(self, devs_id):
|
||||
"""Return configuration form for devices."""
|
||||
conf_devs_id = []
|
||||
for count, dev_id in enumerate(devs_id):
|
||||
device_info = dev_id.split("-")
|
||||
if count == 0:
|
||||
device_type = device_info[0]
|
||||
device_id = device_info[1]
|
||||
elif device_type != device_info[0]:
|
||||
self._form_error = ERROR_DEV_MULTI_TYPE
|
||||
return await self.async_step_init()
|
||||
conf_devs_id.append(device_info[1])
|
||||
|
||||
device = self._get_device(device_id)
|
||||
if not device:
|
||||
self._form_error = ERROR_DEV_NOT_FOUND
|
||||
return await self.async_step_init()
|
||||
|
||||
curr_conf = self._conf_devs_option.get(
|
||||
device_id, self.config_entry.options.get(device_id, {})
|
||||
)
|
||||
|
||||
config_schema = await self._get_device_schema(device_type, curr_conf, device)
|
||||
if not config_schema:
|
||||
self._form_error = ERROR_DEV_NOT_CONFIG
|
||||
return await self.async_step_init()
|
||||
|
||||
self._conf_devs_id = conf_devs_id
|
||||
device_name = (
|
||||
"(multiple devices selected)" if len(conf_devs_id) > 1 else device.name()
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="device",
|
||||
data_schema=config_schema,
|
||||
description_placeholders={
|
||||
"device_type": device_type,
|
||||
"device_name": device_name,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Handle options flow."""
|
||||
if user_input is not None:
|
||||
dev_ids = user_input.get(CONF_LIST_DEVICES)
|
||||
if dev_ids:
|
||||
return await self.async_step_device(None, dev_ids)
|
||||
|
||||
user_input.pop(CONF_LIST_DEVICES, [])
|
||||
return self._save_config(data=user_input)
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_DISCOVERY_INTERVAL,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL
|
||||
),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=30, max=900)),
|
||||
}
|
||||
)
|
||||
|
||||
query_devices = self._get_tuya_devices_filtered(
|
||||
TUYA_TYPE_NOT_QUERY, True, False
|
||||
)
|
||||
if query_devices:
|
||||
devices = {ENTITY_MATCH_NONE: "Default"}
|
||||
devices.update(query_devices)
|
||||
def_val = self.config_entry.options.get(CONF_QUERY_DEVICE)
|
||||
if not def_val or not query_devices.get(def_val):
|
||||
def_val = ENTITY_MATCH_NONE
|
||||
data_schema = data_schema.extend(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_QUERY_INTERVAL,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL
|
||||
),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=30, max=240)),
|
||||
vol.Optional(CONF_QUERY_DEVICE, default=def_val): vol.In(devices),
|
||||
}
|
||||
)
|
||||
|
||||
config_devices = self._get_tuya_devices_filtered(TUYA_TYPE_CONFIG, False, True)
|
||||
if config_devices:
|
||||
data_schema = data_schema.extend(
|
||||
{vol.Optional(CONF_LIST_DEVICES): cv.multi_select(config_devices)}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=data_schema,
|
||||
errors=self._get_form_error(),
|
||||
)
|
||||
|
||||
async def async_step_device(self, user_input=None, dev_ids=None):
|
||||
"""Handle options flow for device."""
|
||||
if dev_ids is not None:
|
||||
return await self._async_device_form(dev_ids)
|
||||
if user_input is not None:
|
||||
for device_id in self._conf_devs_id:
|
||||
self._conf_devs_option[device_id] = user_input
|
||||
|
||||
return await self.async_step_init()
|
||||
|
||||
async def _get_device_schema(self, device_type, curr_conf, device):
|
||||
"""Return option schema for device."""
|
||||
if device_type == "light":
|
||||
return self._get_light_schema(curr_conf, device)
|
||||
if device_type == "climate":
|
||||
entities_list = await _get_entities_matching_domains(self.hass, ["sensor"])
|
||||
return self._get_climate_schema(curr_conf, device, entities_list)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_light_schema(curr_conf, device):
|
||||
"""Create option schema for light device."""
|
||||
min_kelvin = device.max_color_temp()
|
||||
max_kelvin = device.min_color_temp()
|
||||
|
||||
config_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_SUPPORT_COLOR,
|
||||
default=curr_conf.get(CONF_SUPPORT_COLOR, False),
|
||||
): bool,
|
||||
vol.Optional(
|
||||
CONF_BRIGHTNESS_RANGE_MODE,
|
||||
default=curr_conf.get(CONF_BRIGHTNESS_RANGE_MODE, 0),
|
||||
): vol.In({0: "Range 1-255", 1: "Range 10-1000"}),
|
||||
vol.Optional(
|
||||
CONF_MIN_KELVIN,
|
||||
default=curr_conf.get(CONF_MIN_KELVIN, min_kelvin),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=min_kelvin, max=max_kelvin)),
|
||||
vol.Optional(
|
||||
CONF_MAX_KELVIN,
|
||||
default=curr_conf.get(CONF_MAX_KELVIN, max_kelvin),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=min_kelvin, max=max_kelvin)),
|
||||
vol.Optional(
|
||||
CONF_TUYA_MAX_COLTEMP,
|
||||
default=curr_conf.get(
|
||||
CONF_TUYA_MAX_COLTEMP, DEFAULT_TUYA_MAX_COLTEMP
|
||||
),
|
||||
): vol.All(
|
||||
vol.Coerce(int),
|
||||
vol.Clamp(
|
||||
min=DEFAULT_TUYA_MAX_COLTEMP, max=DEFAULT_TUYA_MAX_COLTEMP * 10
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return config_schema
|
||||
|
||||
@staticmethod
|
||||
def _get_climate_schema(curr_conf, device, entities_list):
|
||||
"""Create option schema for climate device."""
|
||||
unit = device.temperature_unit()
|
||||
def_unit = TEMP_FAHRENHEIT if unit == "FAHRENHEIT" else TEMP_CELSIUS
|
||||
entities_list.insert(0, ENTITY_MATCH_NONE)
|
||||
|
||||
config_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
default=curr_conf.get(CONF_UNIT_OF_MEASUREMENT, def_unit),
|
||||
): vol.In({TEMP_CELSIUS: "Celsius", TEMP_FAHRENHEIT: "Fahrenheit"}),
|
||||
vol.Optional(
|
||||
CONF_TEMP_DIVIDER,
|
||||
default=curr_conf.get(CONF_TEMP_DIVIDER, 0),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=0)),
|
||||
vol.Optional(
|
||||
CONF_CURR_TEMP_DIVIDER,
|
||||
default=curr_conf.get(CONF_CURR_TEMP_DIVIDER, 0),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=0)),
|
||||
vol.Optional(
|
||||
CONF_MIN_TEMP,
|
||||
default=curr_conf.get(CONF_MIN_TEMP, 0),
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_MAX_TEMP,
|
||||
default=curr_conf.get(CONF_MAX_TEMP, 0),
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_EXT_TEMP_SENSOR,
|
||||
default=curr_conf.get(CONF_EXT_TEMP_SENSOR, ENTITY_MATCH_NONE),
|
||||
): vol.In(entities_list),
|
||||
}
|
||||
)
|
||||
|
||||
return config_schema
|
||||
|
||||
|
||||
async def _get_entities_matching_domains(hass, domains):
|
||||
"""List entities in the given domains."""
|
||||
included_domains = set(domains)
|
||||
entity_ids = hass.states.async_entity_ids(included_domains)
|
||||
entity_ids.sort()
|
||||
return entity_ids
|
||||
|
|
|
@ -1,10 +1,32 @@
|
|||
"""Constants for the Tuya integration."""
|
||||
|
||||
CONF_BRIGHTNESS_RANGE_MODE = "brightness_range_mode"
|
||||
CONF_COUNTRYCODE = "country_code"
|
||||
CONF_CURR_TEMP_DIVIDER = "curr_temp_divider"
|
||||
CONF_DISCOVERY_INTERVAL = "discovery_interval"
|
||||
CONF_EXT_TEMP_SENSOR = "ext_temp_sensor"
|
||||
CONF_MAX_KELVIN = "max_kelvin"
|
||||
CONF_MAX_TEMP = "max_temp"
|
||||
CONF_MIN_KELVIN = "min_kelvin"
|
||||
CONF_MIN_TEMP = "min_temp"
|
||||
CONF_QUERY_DEVICE = "query_device"
|
||||
CONF_QUERY_INTERVAL = "query_interval"
|
||||
CONF_SUPPORT_COLOR = "support_color"
|
||||
CONF_TEMP_DIVIDER = "temp_divider"
|
||||
CONF_TUYA_MAX_COLTEMP = "tuya_max_coltemp"
|
||||
|
||||
DEFAULT_DISCOVERY_INTERVAL = 605
|
||||
DEFAULT_QUERY_INTERVAL = 120
|
||||
DEFAULT_TUYA_MAX_COLTEMP = 10000
|
||||
|
||||
DOMAIN = "tuya"
|
||||
|
||||
SIGNAL_CONFIG_ENTITY = "tuya_config"
|
||||
SIGNAL_DELETE_ENTITY = "tuya_delete"
|
||||
SIGNAL_UPDATE_ENTITY = "tuya_update"
|
||||
|
||||
TUYA_DATA = "tuya_data"
|
||||
TUYA_DEVICES_CONF = "devices_config"
|
||||
TUYA_DISCOVERY_NEW = "tuya_discovery_new_{}"
|
||||
|
||||
TUYA_PLATFORMS = {
|
||||
|
@ -12,3 +34,5 @@ TUYA_PLATFORMS = {
|
|||
"smart_life": "Smart Life",
|
||||
"jinvoo_smart": "Jinvoo Smart",
|
||||
}
|
||||
|
||||
TUYA_TYPE_NOT_QUERY = ["scene", "switch"]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for Tuya covers."""
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
|
@ -13,7 +15,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
@ -60,6 +62,8 @@ class TuyaCover(TuyaDevice, CoverEntity):
|
|||
"""Init tuya cover device."""
|
||||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
self._was_closing = False
|
||||
self._was_opening = False
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -69,14 +73,34 @@ class TuyaCover(TuyaDevice, CoverEntity):
|
|||
supported_features |= SUPPORT_STOP
|
||||
return supported_features
|
||||
|
||||
@property
|
||||
def is_opening(self):
|
||||
"""Return if the cover is opening or not."""
|
||||
state = self._tuya.state()
|
||||
if state == 1:
|
||||
self._was_opening = True
|
||||
self._was_closing = False
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
"""Return if the cover is closing or not."""
|
||||
state = self._tuya.state()
|
||||
if state == 2:
|
||||
self._was_opening = False
|
||||
self._was_closing = True
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed or not."""
|
||||
state = self._tuya.state()
|
||||
if state == 1:
|
||||
return False
|
||||
if state == 2:
|
||||
if state != 2 and self._was_closing:
|
||||
return True
|
||||
if state != 1 and self._was_opening:
|
||||
return False
|
||||
return None
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
|
@ -89,4 +113,7 @@ class TuyaCover(TuyaDevice, CoverEntity):
|
|||
|
||||
def stop_cover(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
if self.is_closed is None:
|
||||
self._was_opening = False
|
||||
self._was_closing = False
|
||||
self._tuya.stop_cover()
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for Tuya fans."""
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
|
@ -12,7 +14,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for the Tuya lights."""
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
|
@ -11,13 +13,33 @@ from homeassistant.components.light import (
|
|||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util import color as colorutil
|
||||
|
||||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
from .const import (
|
||||
CONF_BRIGHTNESS_RANGE_MODE,
|
||||
CONF_MAX_KELVIN,
|
||||
CONF_MIN_KELVIN,
|
||||
CONF_SUPPORT_COLOR,
|
||||
CONF_TUYA_MAX_COLTEMP,
|
||||
DEFAULT_TUYA_MAX_COLTEMP,
|
||||
DOMAIN,
|
||||
SIGNAL_CONFIG_ENTITY,
|
||||
TUYA_DATA,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
TUYA_BRIGHTNESS_RANGE0 = (1, 255)
|
||||
TUYA_BRIGHTNESS_RANGE1 = (10, 1000)
|
||||
|
||||
BRIGHTNESS_MODES = {
|
||||
0: TUYA_BRIGHTNESS_RANGE0,
|
||||
1: TUYA_BRIGHTNESS_RANGE1,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
@ -64,6 +86,49 @@ class TuyaLight(TuyaDevice, LightEntity):
|
|||
"""Init Tuya light device."""
|
||||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
self._min_kelvin = tuya.max_color_temp()
|
||||
self._max_kelvin = tuya.min_color_temp()
|
||||
|
||||
@callback
|
||||
def _process_config(self):
|
||||
"""Set device config parameter."""
|
||||
config = self._get_device_config()
|
||||
if not config:
|
||||
return
|
||||
|
||||
# support color config
|
||||
supp_color = config.get(CONF_SUPPORT_COLOR, False)
|
||||
if supp_color:
|
||||
self._tuya.force_support_color()
|
||||
# brightness range config
|
||||
self._tuya.brightness_white_range = BRIGHTNESS_MODES.get(
|
||||
config.get(CONF_BRIGHTNESS_RANGE_MODE, 0),
|
||||
TUYA_BRIGHTNESS_RANGE0,
|
||||
)
|
||||
# color set temp range
|
||||
min_tuya = self._tuya.max_color_temp()
|
||||
min_kelvin = config.get(CONF_MIN_KELVIN, min_tuya)
|
||||
max_tuya = self._tuya.min_color_temp()
|
||||
max_kelvin = config.get(CONF_MAX_KELVIN, max_tuya)
|
||||
self._min_kelvin = min(max(min_kelvin, min_tuya), max_tuya)
|
||||
self._max_kelvin = min(max(max_kelvin, self._min_kelvin), max_tuya)
|
||||
# color shown temp range
|
||||
max_color_temp = max(
|
||||
config.get(CONF_TUYA_MAX_COLTEMP, DEFAULT_TUYA_MAX_COLTEMP),
|
||||
DEFAULT_TUYA_MAX_COLTEMP,
|
||||
)
|
||||
self._tuya.color_temp_range = (1000, max_color_temp)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Set config parameter when add to hass."""
|
||||
await super().async_added_to_hass()
|
||||
self._process_config()
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
|
@ -93,12 +158,12 @@ class TuyaLight(TuyaDevice, LightEntity):
|
|||
@property
|
||||
def min_mireds(self):
|
||||
"""Return color temperature min mireds."""
|
||||
return colorutil.color_temperature_kelvin_to_mired(self._tuya.min_color_temp())
|
||||
return colorutil.color_temperature_kelvin_to_mired(self._max_kelvin)
|
||||
|
||||
@property
|
||||
def max_mireds(self):
|
||||
"""Return color temperature max mireds."""
|
||||
return colorutil.color_temperature_kelvin_to_mired(self._tuya.max_color_temp())
|
||||
return colorutil.color_temperature_kelvin_to_mired(self._min_kelvin)
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn on or control the light."""
|
||||
|
|
|
@ -10,8 +10,6 @@ from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
|||
|
||||
ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up tuya sensors dynamically through tuya discovery."""
|
||||
|
|
|
@ -21,5 +21,41 @@
|
|||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Configure Tuya Options",
|
||||
"description": "Do not set pollings interval values too low or the calls will fail generating error message in the log",
|
||||
"data": {
|
||||
"discovery_interval": "Discovery device polling interval in seconds",
|
||||
"query_device": "Select device that will use query method for faster status update",
|
||||
"query_interval": "Query device polling interval in seconds",
|
||||
"list_devices": "Select the devices to configure or leave empty to save configuration"
|
||||
}
|
||||
},
|
||||
"device": {
|
||||
"title": "Configure Tuya Device",
|
||||
"description": "Configure options to adjust displayed information for {device_type} device `{device_name}`",
|
||||
"data": {
|
||||
"support_color": "Force color support",
|
||||
"brightness_range_mode": "Brightness range used by device",
|
||||
"min_kelvin": "Min color temperature supported in kelvin",
|
||||
"max_kelvin": "Max color temperature supported in kelvin",
|
||||
"tuya_max_coltemp": "Max color temperature reported by device",
|
||||
"unit_of_measurement": "Temperature unit used by device",
|
||||
"temp_divider": "Temperature values divider (0 = use default)",
|
||||
"curr_temp_divider": "Current Temperature value divider (0 = use default)",
|
||||
"min_temp": "Min target temperature (use min and max = 0 for default)",
|
||||
"max_temp": "Max target temperature (use min and max = 0 for default)",
|
||||
"ext_temp_sensor": "Sensor for current temperature"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"dev_multi_type": "Multiple selected devices to configure must be of the same type",
|
||||
"dev_not_config": "Device type not configurable",
|
||||
"dev_not_found": "Device not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for Tuya switches."""
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
|
@ -10,7 +12,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"auth_failed": "Invalid authentication",
|
||||
"cannot_connect": "Failed to connect",
|
||||
"conn_error": "Failed to connect",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||
},
|
||||
"error": {
|
||||
"auth_failed": "Invalid authentication",
|
||||
"invalid_auth": "Invalid authentication"
|
||||
},
|
||||
"flow_title": "Tuya configuration",
|
||||
|
@ -24,5 +21,41 @@
|
|||
"title": "Tuya"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"dev_multi_type": "Multiple selected devices to configure must be of the same type",
|
||||
"dev_not_config": "Device type not configurable",
|
||||
"dev_not_found": "Device not found"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Configure Tuya Options",
|
||||
"description": "Do not set pollings interval values too low or the calls will fail generating error message in the log",
|
||||
"data": {
|
||||
"discovery_interval": "Discovery device polling interval in seconds",
|
||||
"query_device": "Select device that will use query method for faster status update",
|
||||
"query_interval": "Query device polling interval in seconds",
|
||||
"list_devices": "Select the devices to configure or leave empty to save configuration"
|
||||
}
|
||||
},
|
||||
"device": {
|
||||
"title": "Configure Tuya Device",
|
||||
"description": "Configure options to adjust displayed information for {device_type} device `{device_name}`",
|
||||
"data": {
|
||||
"support_color": "Force color support",
|
||||
"brightness_range_mode": "Brightness range used by device",
|
||||
"min_kelvin": "Min color temperature supported in kelvin",
|
||||
"max_kelvin": "Max color temperature supported in kelvin",
|
||||
"tuya_max_coltemp": "Max color temperature reported by device",
|
||||
"unit_of_measurement": "Temperature unit used by device",
|
||||
"temp_divider": "Temperature values divider (0 = use default)",
|
||||
"curr_temp_divider": "Current Temperature value divider (0 = use default)",
|
||||
"min_temp": "Min target temperature (use min and max = 0 for default)",
|
||||
"max_temp": "Max target temperature (use min and max = 0 for default)",
|
||||
"ext_temp_sensor": "Sensor for current temperature"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue