diff --git a/.coveragerc b/.coveragerc index eb0fbdc1fcfe11..31c361417ac5ee 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1094,6 +1094,8 @@ omit = homeassistant/components/transmission/const.py homeassistant/components/transmission/errors.py homeassistant/components/travisci/sensor.py + homeassistant/components/tuya/base.py + homeassistant/components/tuya/aes_cbc.py homeassistant/components/tuya/__init__.py homeassistant/components/tuya/climate.py homeassistant/components/tuya/const.py diff --git a/CODEOWNERS b/CODEOWNERS index 70fbaf8ce1f9a8..d68c3f247a2f47 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -521,7 +521,7 @@ homeassistant/components/trafikverket_train/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/transmission/* @engrbm87 @JPHutchins homeassistant/components/tts/* @pvizeli -homeassistant/components/tuya/* @ollo69 +homeassistant/components/tuya/* @Tuya homeassistant/components/twentemilieu/* @frenck homeassistant/components/twinkly/* @dr1rrb homeassistant/components/ubus/* @noltari diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 595350324f93ad..eda7b466c6938c 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -1,376 +1,290 @@ +#!/usr/bin/env python3 """Support for Tuya Smart devices.""" -from datetime import timedelta -import logging -from tuyaha import TuyaApi -from tuyaha.tuyaapi import ( - TuyaAPIException, - TuyaAPIRateLimitException, - TuyaFrequentlyInvokeException, - TuyaNetException, - TuyaServerException, +import itertools +import json +import logging +from typing import Any + +from tuya_iot import ( + ProjectType, + TuyaDevice, + TuyaDeviceListener, + TuyaDeviceManager, + TuyaHomeManager, + TuyaOpenAPI, + TuyaOpenMQ, + tuya_logger, ) +import voluptuous as vol -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_PASSWORD, - CONF_PLATFORM, - CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP, -) -from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.dispatcher import async_dispatcher_send +from .aes_cbc import AES_ACCOUNT_KEY, KEY_KEY, XOR_KEY, AesCBC as Aes from .const import ( - CONF_COUNTRYCODE, - CONF_DISCOVERY_INTERVAL, - CONF_QUERY_DEVICE, - CONF_QUERY_INTERVAL, - DEFAULT_DISCOVERY_INTERVAL, - DEFAULT_QUERY_INTERVAL, + CONF_ACCESS_ID, + CONF_ACCESS_SECRET, + CONF_APP_TYPE, + CONF_COUNTRY_CODE, + CONF_ENDPOINT, + CONF_PASSWORD, + CONF_PROJECT_TYPE, + CONF_USERNAME, DOMAIN, - SIGNAL_CONFIG_ENTITY, - SIGNAL_DELETE_ENTITY, - SIGNAL_UPDATE_ENTITY, - TUYA_DATA, - TUYA_DEVICES_CONF, + TUYA_DEVICE_MANAGER, TUYA_DISCOVERY_NEW, - TUYA_PLATFORMS, - TUYA_TYPE_NOT_QUERY, + TUYA_HA_DEVICES, + TUYA_HA_TUYA_MAP, + TUYA_HOME_MANAGER, + TUYA_MQTT_LISTENER, + TUYA_SETUP_PLATFORM, + TUYA_SUPPORT_HA_TYPE, ) _LOGGER = logging.getLogger(__name__) -ATTR_TUYA_DEV_ID = "tuya_device_id" -ENTRY_IS_SETUP = "tuya_entry_is_setup" +CONFIG_SCHEMA = vol.Schema( + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_PROJECT_TYPE): int, + vol.Required(CONF_ENDPOINT): cv.string, + vol.Required(CONF_ACCESS_ID): cv.string, + vol.Required(CONF_ACCESS_SECRET): cv.string, + CONF_USERNAME: cv.string, + CONF_PASSWORD: cv.string, + CONF_COUNTRY_CODE: cv.string, + CONF_APP_TYPE: cv.string, + } + ) + }, + ), + extra=vol.ALLOW_EXTRA, +) -SERVICE_FORCE_UPDATE = "force_update" -SERVICE_PULL_DEVICES = "pull_devices" +# decrypt or encrypt entry info + + +def entry_decrypt(hass: HomeAssistant, entry: ConfigEntry, init_entry_data): + """Decrypt code from config entry.""" + aes = Aes() + # decrypt the new account info + if XOR_KEY in init_entry_data: + _LOGGER.info("tuya.__init__.exist_xor_cache-->True") + key_iv = aes.xor_decrypt(init_entry_data[XOR_KEY], init_entry_data[KEY_KEY]) + cbc_key = key_iv[0:16] + cbc_iv = key_iv[16:32] + decrpyt_str = aes.cbc_decrypt(cbc_key, cbc_iv, init_entry_data[AES_ACCOUNT_KEY]) + # _LOGGER.info(f"tuya.__init__.exist_xor_cache:::decrpyt_str-->{decrpyt_str}") + entry_data = aes.json_to_dict(decrpyt_str) + else: + # if not exist xor cache, use old account info + _LOGGER.info("tuya.__init__.exist_xor_cache-->False") + entry_data = init_entry_data + cbc_key = aes.random_16() + cbc_iv = aes.random_16() + access_id = init_entry_data[CONF_ACCESS_ID] + access_id_entry = aes.cbc_encrypt(cbc_key, cbc_iv, access_id) + c = cbc_key + cbc_iv + c_xor_entry = aes.xor_encrypt(c, access_id_entry) + # account info encrypted with AES-CBC + user_input_encrpt = aes.cbc_encrypt( + cbc_key, cbc_iv, json.dumps(init_entry_data) + ) + # update old account info + hass.config_entries.async_update_entry( + entry, + data={ + AES_ACCOUNT_KEY: user_input_encrpt, + XOR_KEY: c_xor_entry, + KEY_KEY: access_id_entry, + }, + ) + return entry_data + + +async def _init_tuya_sdk(hass: HomeAssistant, entry: ConfigEntry) -> bool: + init_entry_data = entry.data + # decrypt or encrypt entry info + entry_data = entry_decrypt(hass, entry, init_entry_data) + project_type = ProjectType(entry_data[CONF_PROJECT_TYPE]) + api = TuyaOpenAPI( + entry_data[CONF_ENDPOINT], + entry_data[CONF_ACCESS_ID], + entry_data[CONF_ACCESS_SECRET], + project_type, + ) -TUYA_TYPE_TO_HA = { - "climate": "climate", - "cover": "cover", - "fan": "fan", - "light": "light", - "scene": "scene", - "switch": "switch", -} + api.set_dev_channel("hass") -TUYA_TRACKER = "tuya_tracker" -STOP_CANCEL = "stop_event_cancel" + response = ( + await hass.async_add_executor_job( + api.login, entry_data[CONF_USERNAME], entry_data[CONF_PASSWORD] + ) + if project_type == ProjectType.INDUSTY_SOLUTIONS + else await hass.async_add_executor_job( + api.login, + entry_data[CONF_USERNAME], + entry_data[CONF_PASSWORD], + entry_data[CONF_COUNTRY_CODE], + entry_data[CONF_APP_TYPE], + ) + ) + if response.get("success", False) is False: + _LOGGER.error(f"Tuya login error response: {response}") + return False -CONFIG_SCHEMA = cv.deprecated(DOMAIN) + tuya_mq = TuyaOpenMQ(api) + tuya_mq.start() + device_manager = TuyaDeviceManager(api, tuya_mq) -def _update_discovery_interval(hass, interval): - tuya = hass.data[DOMAIN].get(TUYA_DATA) - if not tuya: - return + # Get device list + home_manager = TuyaHomeManager(api, tuya_mq, device_manager) + await hass.async_add_executor_job(home_manager.update_device_cache) + hass.data[DOMAIN][TUYA_HOME_MANAGER] = home_manager - try: - tuya.discovery_interval = interval - _LOGGER.info("Tuya discovery device poll interval set to %s seconds", interval) - except ValueError as ex: - _LOGGER.warning(ex) + class DeviceListener(TuyaDeviceListener): + """Device Update Listener.""" + def update_device(self, device: TuyaDevice): + for ha_device in hass.data[DOMAIN][TUYA_HA_DEVICES]: + if ha_device.tuya_device.id == device.id: + _LOGGER.debug(f"_update-->{self};->>{ha_device.tuya_device.status}") + ha_device.schedule_update_ha_state() -def _update_query_interval(hass, interval): - tuya = hass.data[DOMAIN].get(TUYA_DATA) - if not tuya: - return + def add_device(self, device: TuyaDevice): - try: - tuya.query_interval = interval - _LOGGER.info("Tuya query device poll interval set to %s seconds", interval) - except ValueError as ex: - _LOGGER.warning(ex) + device_add = False + _LOGGER.info( + f"""add device category->{device.category}; keys->, + {hass.data[DOMAIN][TUYA_HA_TUYA_MAP].keys()}""" + ) + if device.category in itertools.chain( + *hass.data[DOMAIN][TUYA_HA_TUYA_MAP].values() + ): + ha_tuya_map = hass.data[DOMAIN][TUYA_HA_TUYA_MAP] -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Tuya platform.""" + remove_hass_device(hass, device.id) - tuya = TuyaApi() - username = entry.data[CONF_USERNAME] - password = entry.data[CONF_PASSWORD] - country_code = entry.data[CONF_COUNTRYCODE] - platform = entry.data[CONF_PLATFORM] + for key, tuya_list in ha_tuya_map.items(): + if device.category in tuya_list: + device_add = True + async_dispatcher_send( + hass, TUYA_DISCOVERY_NEW.format(key), [device.id] + ) - try: - await hass.async_add_executor_job( - tuya.init, username, password, country_code, platform - ) - except ( - TuyaNetException, - TuyaServerException, - TuyaFrequentlyInvokeException, - ) as exc: - raise ConfigEntryNotReady() from exc - - except TuyaAPIRateLimitException as exc: - raise ConfigEntryNotReady("Tuya login rate limited") from exc - - except TuyaAPIException as exc: - _LOGGER.error( - "Connection error during integration setup. Error: %s", - exc, + if device_add: + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] + device_manager.mq.stop() + tuya_mq = TuyaOpenMQ(device_manager.api) + tuya_mq.start() + + device_manager.mq = tuya_mq + tuya_mq.add_message_listener(device_manager._on_message) + + def remove_device(self, device_id: str): + _LOGGER.info(f"tuya remove device:{device_id}") + remove_hass_device(hass, device_id) + + __listener = DeviceListener() + hass.data[DOMAIN][TUYA_MQTT_LISTENER] = __listener + device_manager.add_device_listener(__listener) + hass.data[DOMAIN][TUYA_DEVICE_MANAGER] = device_manager + + # Clean up device entities + await cleanup_device_registry(hass) + + _LOGGER.info(f"init support type->{TUYA_SUPPORT_HA_TYPE}") + + for platform in TUYA_SUPPORT_HA_TYPE: + _LOGGER.info(f"tuya async platform-->{platform}") + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) ) - return False + hass.data[DOMAIN][TUYA_SETUP_PLATFORM].add(platform) - domain_data = 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) - ) + return True - _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 = {} - for device in device_list: - dev_type = device.device_type() - if ( - dev_type in TUYA_TYPE_TO_HA - and device.object_id() not in domain_data["entities"] - ): - ha_type = TUYA_TYPE_TO_HA[dev_type] - if ha_type not in device_type_list: - device_type_list[ha_type] = [] - device_type_list[ha_type].append(device.object_id()) - domain_data["entities"][device.object_id()] = None - - for ha_type, dev_ids in device_type_list.items(): - config_entries_key = f"{ha_type}.tuya" - if config_entries_key not in domain_data[ENTRY_IS_SETUP]: - domain_data["pending"][ha_type] = dev_ids - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, ha_type) - ) - domain_data[ENTRY_IS_SETUP].add(config_entries_key) - else: - async_dispatcher_send(hass, TUYA_DISCOVERY_NEW.format(ha_type), dev_ids) - - await async_load_devices(tuya.get_all_devices()) - - def _get_updated_devices(): - 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): - """Check if accesstoken is expired and pull device list from server.""" - _LOGGER.debug("Pull devices from Tuya") - # Add new discover device. - device_list = await hass.async_add_executor_job(_get_updated_devices) - await async_load_devices(device_list) - # Delete not exist device. - newlist_ids = [] - for device in device_list: - newlist_ids.append(device.object_id()) - for dev_id in list(domain_data["entities"]): - if dev_id not in newlist_ids: - async_dispatcher_send(hass, SIGNAL_DELETE_ENTITY, dev_id) - domain_data["entities"].pop(dev_id) - - domain_data[TUYA_TRACKER] = async_track_time_interval( - hass, async_poll_devices_update, timedelta(minutes=2) - ) +async def cleanup_device_registry(hass: HomeAssistant): + """Remove deleted device registry entry if there are no remaining entities.""" - @callback - def _async_cancel_tuya_tracker(event): - domain_data[TUYA_TRACKER]() + device_registry = hass.helpers.device_registry.async_get(hass) + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] - domain_data[STOP_CANCEL] = hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, _async_cancel_tuya_tracker - ) + for dev_id, device_entity in list(device_registry.devices.items()): + for item in device_entity.identifiers: + if DOMAIN == item[0] and item[1] not in device_manager.device_map.keys(): + device_registry.async_remove_device(dev_id) + break - hass.services.async_register( - DOMAIN, SERVICE_PULL_DEVICES, async_poll_devices_update - ) - async def async_force_update(call): - """Force all devices to pull data.""" - async_dispatcher_send(hass, SIGNAL_UPDATE_ENTITY) +def remove_hass_device(hass: HomeAssistant, device_id: str): + """Remove device from hass cache.""" + device_registry = hass.helpers.device_registry.async_get(hass) + entity_registry = hass.helpers.entity_registry.async_get(hass) + for entity in list(entity_registry.entities.values()): + if entity.unique_id.startswith(f"ty{device_id}"): + entity_registry.async_remove(entity.entity_id) + if device_registry.async_get(entity.device_id): + device_registry.async_remove_device(entity.device_id) - hass.services.async_register(DOMAIN, SERVICE_FORCE_UPDATE, async_force_update) + +async def async_setup(hass, config): + """Set up the Tuya integration.""" + tuya_logger.setLevel(_LOGGER.level) + conf = config.get(DOMAIN) + + _LOGGER.info(f"Tuya async setup conf {conf}") + if conf is not None: + + async def flow_init() -> Any: + try: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=conf + ) + except Exception as inst: + _LOGGER.error(inst.args) + _LOGGER.info("Tuya async setup flow_init") + return result + + hass.async_create_task(flow_init()) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unloading the Tuya platforms.""" - domain_data = hass.data[DOMAIN] - platforms = [platform.split(".", 1)[0] for platform in domain_data[ENTRY_IS_SETUP]] - unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms) - if unload_ok: - domain_data["listener"]() - domain_data[STOP_CANCEL]() - domain_data[TUYA_TRACKER]() - hass.services.async_remove(DOMAIN, SERVICE_FORCE_UPDATE) - hass.services.async_remove(DOMAIN, SERVICE_PULL_DEVICES) + _LOGGER.info("integration unload") + unload = await hass.config_entries.async_unload_platforms( + entry, hass.data[DOMAIN]["setup_platform"] + ) + if unload: + __device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] + __device_manager.mq.stop() + __device_manager.remove_device_listener(hass.data[DOMAIN][TUYA_MQTT_LISTENER]) + hass.data.pop(DOMAIN) - return unload_ok + return unload -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, include_disabled_entities=True - ): - 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.""" - 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): - """Return Tuya device id.""" - return self._tuya.object_id() - - @property - def unique_id(self): - """Return a unique ID.""" - return f"tuya.{self._tuya.object_id()}" - - @property - def name(self): - """Return Tuya device name.""" - return self._tuya.name() - - @property - def available(self): - """Return if the device is available.""" - return self._tuya.available() - - @property - def device_info(self): - """Return a device description for device registry.""" - _device_info = { - "identifiers": {(DOMAIN, f"{self.unique_id}")}, - "manufacturer": TUYA_PLATFORMS.get( - self._tuya_platform, self._tuya_platform - ), - "name": self.name, - "model": self._tuya.object_type(), - } - return _device_info - - def update(self): - """Refresh Tuya device data.""" - 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.""" - if dev_id == self.object_id: - entity_registry = ( - 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(force_remove=True) - - @callback - def _update_callback(self): - """Call update method.""" - self.async_schedule_update_ha_state(True) +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Async setup hass config entry.""" + _LOGGER.info(f"tuya.__init__.async_setup_entry-->{entry.data}") + + hass.data[DOMAIN] = {TUYA_HA_TUYA_MAP: {}, TUYA_HA_DEVICES: []} + hass.data[DOMAIN][TUYA_SETUP_PLATFORM] = set() + + success = await _init_tuya_sdk(hass, entry) + if not success: + return False + + return True diff --git a/homeassistant/components/tuya/aes_cbc.py b/homeassistant/components/tuya/aes_cbc.py new file mode 100644 index 00000000000000..876ee78169d95c --- /dev/null +++ b/homeassistant/components/tuya/aes_cbc.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""AES-CBC encryption and decryption for account info.""" + +import base64 as b64 +from binascii import a2b_hex, b2a_hex +import json +import random + +from Crypto.Cipher import AES + +AES_ACCOUNT_KEY = "o0o0o0" +XOR_KEY = "00oo00" +KEY_KEY = "oo00oo" + + +class AesCBC: + """AES helper.""" + + def random_16(self): + """Return random 16.""" + str = "" + return str.join( + random.choice("abcdefghijklmnopqrstuvwxyz!@#$%^&*1234567890") + for i in range(16) + ) + + def add_to_16(self, text): + """Add to 16.""" + if len(text.encode("utf-8")) % 16: + add = 16 - (len(text.encode("utf-8")) % 16) + else: + add = 0 + text = text + ("\0" * add) + return text.encode("utf-8") + + def cbc_encrypt(self, key, iv, text): + """Cbc encrypt.""" + key = key.encode("utf-8") + mode = AES.MODE_CBC + iv = bytes(iv, encoding="utf8") + text = self.add_to_16(text) + cryptos = AES.new(key, mode, iv) + cipher_text = cryptos.encrypt(text) + return str(b2a_hex(cipher_text), encoding="utf-8") + + def cbc_decrypt(self, key, iv, text): + """Cbc decrypt.""" + key = key.encode("utf-8") + iv = bytes(iv, encoding="utf8") + mode = AES.MODE_CBC + cryptos = AES.new(key, mode, iv) + plain_text = cryptos.decrypt(a2b_hex(text)) + return bytes.decode(plain_text).rstrip("\0") + + def xor_encrypt(self, data, key): + """Xor encrypt.""" + lkey = len(key) + secret = [] + num = 0 + for each in data: + if num >= lkey: + num = num % lkey + secret.append(chr(ord(each) ^ ord(key[num]))) + num += 1 + return b64.b64encode("".join(secret).encode()).decode() + + def xor_decrypt(self, secret, key): + """Xor decrypt.""" + tips = b64.b64decode(secret.encode()).decode() + lkey = len(key) + secret = [] + num = 0 + for each in tips: + if num >= lkey: + num = num % lkey + secret.append(chr(ord(each) ^ ord(key[num]))) + num += 1 + return "".join(secret) + + def json_to_dict(self, json_str): + """Json to dict.""" + return json.loads(json_str) + + def b64_encrypt(self, text): + """Base64 encrypt.""" + return b64.b64encode(text.encode()).decode() + + def b64_decrypt(self, text): + """Base64 decrypt.""" + return b64.b64decode(text).decode() diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py new file mode 100644 index 00000000000000..2f94f3ccbfc4de --- /dev/null +++ b/homeassistant/components/tuya/base.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""Tuya Home Assistant Base Device Model.""" +from __future__ import annotations + +import asyncio + +from tuya_iot import TuyaDevice, TuyaDeviceManager + +from .const import DOMAIN + + +class TuyaHaDevice: + """Tuya base device.""" + + def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager): + """Init TuyaHaDevice.""" + super().__init__() + + self.tuya_device = device + self.tuya_device_manager = device_manager + + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + @staticmethod + def remap(old_value, old_min, old_max, new_min, new_max): + """Remap old_value to new_value.""" + new_value = ((old_value - old_min) / (old_max - old_min)) * ( + new_max - new_min + ) + new_min + return new_value + + @property + def should_poll(self) -> bool: + """Hass should not poll.""" + return False + + @property + def unique_id(self) -> str | None: + """Return a unique ID.""" + return f"tuya.{self.tuya_device.id}" + + @property + def name(self) -> str | None: + """Return Tuya device name.""" + return self.tuya_device.name + + @property + def device_info(self): + """Return a device description for device registry.""" + _device_info = { + "identifiers": {(DOMAIN, f"{self.unique_id}")}, + "manufacturer": "tuya", + "name": self.tuya_device.name, + "model": self.tuya_device.product_name, + } + return _device_info + + # @property + # def icon(self) -> Optional[str]: + # """Return Tuya device icon.""" + # cdn_url = 'https://images.tuyacn.com/' + # # customize cdn url + # return cdn_url + self.tuyaDevice.icon + + @property + def available(self) -> bool: + """Return if the device is available.""" + return self.tuya_device.online + + def _send_command(self, commands) -> None: + self.loop.run_in_executor( + None, self.tuya_device_manager.send_commands, self.tuya_device.id, commands + ) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 73ba69da79786b..00c74db07cc584 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -1,261 +1,434 @@ -"""Support for the Tuya climate devices.""" -from datetime import timedelta +#!/usr/bin/env python3 +"""Support for Tuya Climate.""" -from homeassistant.components.climate import ( - DOMAIN as SENSOR_DOMAIN, - ENTITY_ID_FORMAT, - ClimateEntity, -) +import json +import logging +from typing import List + +from tuya_iot import TuyaDevice, TuyaDeviceManager + +from homeassistant.components.climate import DOMAIN as DEVICE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( - FAN_HIGH, - FAN_LOW, - FAN_MEDIUM, + CURRENT_HVAC_COOL, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, + HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.const import ( - ATTR_TEMPERATURE, - CONF_PLATFORM, - CONF_UNIT_OF_MEASUREMENT, - TEMP_CELSIUS, - TEMP_FAHRENHEIT, -) -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect -from . import TuyaDevice +from .base import TuyaHaDevice from .const import ( - CONF_CURR_TEMP_DIVIDER, - CONF_MAX_TEMP, - CONF_MIN_TEMP, - CONF_SET_TEMP_DIVIDED, - CONF_TEMP_DIVIDER, - CONF_TEMP_STEP_OVERRIDE, DOMAIN, - SIGNAL_CONFIG_ENTITY, - TUYA_DATA, + TUYA_DEVICE_MANAGER, TUYA_DISCOVERY_NEW, + TUYA_HA_DEVICES, + TUYA_HA_TUYA_MAP, ) -DEVICE_TYPE = "climate" - -SCAN_INTERVAL = timedelta(seconds=15) - -HA_STATE_TO_TUYA = { - HVAC_MODE_AUTO: "auto", - HVAC_MODE_COOL: "cold", - HVAC_MODE_FAN_ONLY: "wind", - HVAC_MODE_HEAT: "hot", +_LOGGER = logging.getLogger(__name__) + + +# Air Conditioner +# https://developer.tuya.com/en/docs/iot/f?id=K9gf46qujdmwb +DPCODE_SWITCH = "switch" +DPCODE_TEMP_SET = "temp_set" +DPCODE_TEMP_SET_F = "temp_set_f" +DPCODE_MODE = "mode" +DPCODE_HUMIDITY_SET = "humidity_set" +DPCODE_FAN_SPEED_ENUM = "fan_speed_enum" + +# Temperature unit +DPCODE_TEMP_UNIT_CONVERT = "temp_unit_convert" +DPCODE_C_F = "c_f" + +# swing flap switch +DPCODE_SWITCH_HORIZONTAL = "switch_horizontal" +DPCODE_SWITCH_VERTICAL = "switch_vertical" + +# status +DPCODE_TEMP_CURRENT = "temp_current" +DPCODE_TEMP_CURRENT_F = "temp_current_f" +DPCODE_HUMIDITY_CURRENT = "humidity_current" + +SWING_OFF = "swing_off" +SWING_VERTICAL = "swing_vertical" +SWING_HORIZONTAL = "swing_horizontal" +SWING_BOTH = "swing_both" + +TUYA_HVAC_TO_HA = { + "hot": HVAC_MODE_HEAT, + "cold": HVAC_MODE_COOL, + "wet": HVAC_MODE_DRY, + "wind": HVAC_MODE_FAN_ONLY, + "auto": HVAC_MODE_AUTO, } -TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()} +TUYA_ACTION_TO_HA = { + "off": CURRENT_HVAC_OFF, + "heating": CURRENT_HVAC_HEAT, + "cooling": CURRENT_HVAC_COOL, + "wind": CURRENT_HVAC_FAN, + "auto": CURRENT_HVAC_IDLE, +} -FAN_MODES = {FAN_LOW, FAN_MEDIUM, FAN_HIGH} +TUYA_SUPPORT_TYPE = { + "kt", # Air conditioner + "qn", # Heater + "wk", # Thermostat +} -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up tuya sensors dynamically through tuya discovery.""" +async def async_setup_entry( + hass: HomeAssistant, _entry: ConfigEntry, async_add_entities +): + """Set up tuya climate dynamically through tuya discovery.""" + _LOGGER.info("climate init") - platform = config_entry.data[CONF_PLATFORM] + hass.data[DOMAIN][TUYA_HA_TUYA_MAP].update({DEVICE_DOMAIN: TUYA_SUPPORT_TYPE}) - async def async_discover_sensor(dev_ids): - """Discover and add a discovered tuya sensor.""" + async def async_discover_device(dev_ids): + """Discover and add a discovered tuya climate.""" + _LOGGER.info(f"climate add->{dev_ids}") if not dev_ids: return - entities = await hass.async_add_executor_job( - _setup_entities, - hass, - dev_ids, - platform, - ) + entities = await hass.async_add_executor_job(_setup_entities, hass, dev_ids) + hass.data[DOMAIN][TUYA_HA_DEVICES].extend(entities) async_add_entities(entities) async_dispatcher_connect( - hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor + hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device ) - devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) - await async_discover_sensor(devices_ids) + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] + device_ids = [] + for (device_id, device) in device_manager.device_map.items(): + if device.category in TUYA_SUPPORT_TYPE: + device_ids.append(device_id) + await async_discover_device(device_ids) -def _setup_entities(hass, dev_ids, platform): - """Set up Tuya Climate device.""" - tuya = hass.data[DOMAIN][TUYA_DATA] +def _setup_entities(hass, device_ids: List): + """Set up Tuya Climate.""" + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] entities = [] - for dev_id in dev_ids: - device = tuya.get_device_by_id(dev_id) + for device_id in device_ids: + device = device_manager.device_map[device_id] if device is None: continue - entities.append(TuyaClimateEntity(device, platform)) + entities.append(TuyaHaClimate(device, device_manager)) return entities -class TuyaClimateEntity(TuyaDevice, ClimateEntity): - """Tuya climate devices,include air conditioner,heater.""" - - def __init__(self, tuya, platform): - """Init climate device.""" - 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._set_temp_divided = True - self._temp_step_override = None - self._min_temp = None - self._max_temp = None - - @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) - self._set_temp_divided = config.get(CONF_SET_TEMP_DIVIDED, True) - self._temp_step_override = config.get(CONF_TEMP_STEP_OVERRIDE) - 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 +class TuyaHaClimate(TuyaHaDevice, ClimateEntity): + """Tuya Switch Device.""" + + def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager): + """Init Tuya Ha Climate.""" + super().__init__(device, device_manager) + if DPCODE_C_F in self.tuya_device.status: + self.dp_temp_unit = DPCODE_C_F else: - self._min_temp = min_temp - self._max_temp = max_temp - - 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 - ) + self.dp_temp_unit = DPCODE_TEMP_UNIT_CONVERT + + def get_temp_set_scale(self) -> int: + """Get temperature set scale.""" + __dp_temp_set = DPCODE_TEMP_SET if self.__is_celsius() else DPCODE_TEMP_SET_F + __temp_set_value_range = json.loads( + self.tuya_device.status_range.get(__dp_temp_set).values ) + return __temp_set_value_range.get("scale") - 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 + def get_temp_current_scale(self) -> int: + """Get temperature current scale.""" + __dp_temp_current = ( + DPCODE_TEMP_CURRENT if self.__is_celsius() else DPCODE_TEMP_CURRENT_F + ) + __temp_current_value_range = json.loads( + self.tuya_device.status_range.get(__dp_temp_current).values + ) + return __temp_current_value_range.get("scale") - for mode in modes: - 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 + # Functions - @property - def temperature_unit(self): - """Return the unit of measurement used by the platform.""" - unit = self._tuya.temperature_unit() - if unit == "FAHRENHEIT": - return TEMP_FAHRENHEIT - return TEMP_CELSIUS + def set_hvac_mode(self, hvac_mode): + """Set new target hvac mode.""" + commands = [] + if hvac_mode == HVAC_MODE_OFF: + commands.append({"code": DPCODE_SWITCH, "value": False}) + else: + commands.append({"code": DPCODE_SWITCH, "value": True}) - @property - def hvac_mode(self): - """Return current operation ie. heat, cool, idle.""" - if not self._tuya.state(): - return HVAC_MODE_OFF + for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items(): + if ha_mode == hvac_mode: + commands.append({"code": DPCODE_MODE, "value": tuya_mode}) - if not self._has_operation: - return self._def_hvac_mode + self._send_command(commands) - mode = self._tuya.current_operation() - if mode is None: - return None - return TUYA_STATE_TO_HA.get(mode) + def set_fan_mode(self, fan_mode): + """Set new target fan mode.""" + self._send_command([{"code": DPCODE_FAN_SPEED_ENUM, "value": fan_mode}]) + + def set_humidity(self, humidity): + """Set new target humidity.""" + self._send_command([{"code": DPCODE_HUMIDITY_SET, "value": int(humidity)}]) + + def set_swing_mode(self, swing_mode): + """Set new target swing operation.""" + commands = [] + if swing_mode == SWING_BOTH: + commands = [ + {"code": DPCODE_SWITCH_VERTICAL, "value": True}, + {"code": DPCODE_SWITCH_HORIZONTAL, "value": True}, + ] + elif swing_mode == SWING_HORIZONTAL: + commands = [ + {"code": DPCODE_SWITCH_VERTICAL, "value": False}, + {"code": DPCODE_SWITCH_HORIZONTAL, "value": True}, + ] + elif swing_mode == SWING_VERTICAL: + commands = [ + {"code": DPCODE_SWITCH_VERTICAL, "value": True}, + {"code": DPCODE_SWITCH_HORIZONTAL, "value": False}, + ] + else: + commands = [ + {"code": DPCODE_SWITCH_VERTICAL, "value": False}, + {"code": DPCODE_SWITCH_HORIZONTAL, "value": False}, + ] + + self._send_command(commands) + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + _LOGGER.debug(f"climate temp->{kwargs}") + code = DPCODE_TEMP_SET if self.__is_celsius() else DPCODE_TEMP_SET_F + self._send_command( + [ + { + "code": code, + "value": int( + kwargs["temperature"] * (10 ** self.get_temp_set_scale()) + ), + } + ] + ) + + def __is_celsius(self) -> bool: + return ( + self.dp_temp_unit in self.tuya_device.status + and self.tuya_device.status.get(self.dp_temp_unit).lower() == "c" + or DPCODE_TEMP_CURRENT in self.tuya_device.status + ) @property - def hvac_modes(self): - """Return the list of available operation modes.""" - return self.operations + def temperature_unit(self) -> str: + """Return true if fan is on.""" + if self.__is_celsius(): + return TEMP_CELSIUS + return TEMP_FAHRENHEIT @property - def current_temperature(self): + def current_temperature(self) -> float: """Return the current temperature.""" - return self._tuya.current_temperature() + if ( + DPCODE_TEMP_CURRENT not in self.tuya_device.status + and DPCODE_TEMP_CURRENT_F not in self.tuya_device.status + ): + return None + + if self.__is_celsius(): + return ( + self.tuya_device.status.get(DPCODE_TEMP_CURRENT, 0) + * 1.0 + / (10 ** self.get_temp_current_scale()) + ) + return ( + self.tuya_device.status.get(DPCODE_TEMP_CURRENT_F, 0) + * 1.0 + / (10 ** self.get_temp_current_scale()) + ) @property - def target_temperature(self): - """Return the temperature we try to reach.""" - return self._tuya.target_temperature() + def current_humidity(self) -> int: + """Return the current humidity.""" + return int(self.tuya_device.status.get(DPCODE_HUMIDITY_CURRENT, 0)) @property - def target_temperature_step(self): - """Return the supported step of target temperature.""" - if self._temp_step_override: - return self._temp_step_override - return self._tuya.target_temperature_step() + def target_temperature(self) -> float: + """Return the temperature currently set to be reached.""" + # return 1 + return ( + self.tuya_device.status.get(DPCODE_TEMP_SET, 0) + * 1.0 + / (10 ** self.get_temp_set_scale()) + ) @property - def fan_mode(self): - """Return the fan setting.""" - return self._tuya.current_fan_mode() + def max_temp(self) -> float: + """Return the maximum temperature.""" + return self.target_temperature_high @property - def fan_modes(self): - """Return the list of available fan modes.""" - return self._tuya.fan_list() + def min_temp(self) -> float: + """Return the minimum temperature.""" + return self.target_temperature_low - def set_temperature(self, **kwargs): - """Set new target temperature.""" - if ATTR_TEMPERATURE in kwargs: - self._tuya.set_temperature(kwargs[ATTR_TEMPERATURE], self._set_temp_divided) + @property + def target_temperature_high(self) -> float: + """Return the upper bound target temperature.""" + if self.__is_celsius(): + temp_value = json.loads( + self.tuya_device.function.get(DPCODE_TEMP_SET, {}).values + ) + return temp_value.get("max", 0) * 1.0 / (10 ** self.get_temp_set_scale()) - def set_fan_mode(self, fan_mode): - """Set new target fan mode.""" - self._tuya.set_fan_mode(fan_mode) + temp_value = json.loads( + self.tuya_device.function.get(DPCODE_TEMP_SET_F, {}).values + ) + return temp_value.get("max", 0) * 1.0 / (10 ** self.get_temp_set_scale()) - def set_hvac_mode(self, hvac_mode): - """Set new target operation mode.""" - if hvac_mode == HVAC_MODE_OFF: - self._tuya.turn_off() - return + @property + def target_temperature_low(self) -> float: + """Return the lower bound target temperature.""" + if self.__is_celsius(): + temp_value = json.loads( + self.tuya_device.function.get(DPCODE_TEMP_SET, {}).values + ) + low_value = ( + temp_value.get("min", 0) * 1.0 / (10 ** self.get_temp_set_scale()) + ) + return low_value - if not self._tuya.state(): - self._tuya.turn_on() + temp_value = json.loads( + self.tuya_device.function.get(DPCODE_TEMP_SET_F, {}).values + ) + return temp_value.get("min", 0) * 1.0 / (10 ** self.get_temp_set_scale()) - if self._has_operation: - self._tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode)) + @property + def target_temperature_step(self) -> float: + """Return target temperature setp.""" + __temp_set_value_range = json.loads( + self.tuya_device.status_range.get( + DPCODE_TEMP_SET if self.__is_celsius() else DPCODE_TEMP_SET_F + ).values + ) + return ( + __temp_set_value_range.get("step", 0) + * 1.0 + / (10 ** self.get_temp_set_scale()) + ) @property - def supported_features(self): - """Return the list of supported features.""" - supports = 0 - if self._tuya.support_target_temperature(): - supports = supports | SUPPORT_TARGET_TEMPERATURE - if self._tuya.support_wind_speed(): - supports = supports | SUPPORT_FAN_MODE - return supports + def target_humidity(self) -> int: + """Return target humidity.""" + return int(self.tuya_device.status.get(DPCODE_HUMIDITY_SET)) @property - def min_temp(self): - """Return the minimum temperature.""" - min_temp = ( - self._min_temp if self._min_temp is not None else self._tuya.min_temp() + def hvac_mode(self) -> str: + """Return hvac mode.""" + if not self.tuya_device.status.get(DPCODE_SWITCH): + return HVAC_MODE_OFF + + # if self.tuya_device.status.get(DPCODE_MODE) in TUYA_HVAC_TO_HA: + return TUYA_HVAC_TO_HA[self.tuya_device.status.get(DPCODE_MODE)] + # else: + # return + + @property + def hvac_modes(self) -> List: + """Return hvac modes for select.""" + modes = json.loads(self.tuya_device.function.get(DPCODE_MODE, {}).values).get( + "range" ) - if min_temp is not None: - return min_temp - return super().min_temp + + _LOGGER.debug(f"hvac_modes->{modes}") + hvac_modes = [HVAC_MODE_OFF] + for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items(): + if tuya_mode in modes: + hvac_modes.append(ha_mode) + + return hvac_modes @property - def max_temp(self): - """Return the maximum temperature.""" - max_temp = ( - self._max_temp if self._max_temp is not None else self._tuya.max_temp() + def preset_modes(self) -> list: + """Return available presets.""" + modes = json.loads(self.tuya_device.function.get(DPCODE_MODE, {}).values).get( + "range" ) - if max_temp is not None: - return max_temp - return super().max_temp + preset_modes = filter(lambda d: d not in TUYA_HVAC_TO_HA.keys(), modes) + return list(preset_modes) + + @property + def fan_mode(self) -> str: + """Return fan mode.""" + return self.tuya_device.status.get(DPCODE_FAN_SPEED_ENUM) + + @property + def fan_modes(self) -> List[str]: + """Return fan modes for select.""" + data = json.loads( + self.tuya_device.function.get(DPCODE_FAN_SPEED_ENUM, {}).values + ).get("range") + return data + + @property + def swing_mode(self) -> str: + """Return swing mode.""" + mode = 0 + if ( + DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status + and self.tuya_device.status.get(DPCODE_SWITCH_HORIZONTAL) + ): + mode += 1 + if ( + DPCODE_SWITCH_VERTICAL in self.tuya_device.status + and self.tuya_device.status.get(DPCODE_SWITCH_VERTICAL) + ): + mode += 2 + + if mode == 3: + return SWING_BOTH + if mode == 2: + return SWING_VERTICAL + if mode == 1: + return SWING_HORIZONTAL + return SWING_OFF + + @property + def swing_modes(self) -> List: + """Return swing mode for select.""" + return [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH] + + @property + def supported_features(self): + """Flag supported features.""" + supports = 0 + if ( + DPCODE_TEMP_SET in self.tuya_device.status + or DPCODE_TEMP_SET_F in self.tuya_device.status + ): + supports = supports | SUPPORT_TARGET_TEMPERATURE + if DPCODE_FAN_SPEED_ENUM in self.tuya_device.status: + supports = supports | SUPPORT_FAN_MODE + if DPCODE_HUMIDITY_SET in self.tuya_device.status: + supports = supports | SUPPORT_TARGET_HUMIDITY + if ( + DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status + or DPCODE_SWITCH_VERTICAL in self.tuya_device.status + ): + supports = supports | SUPPORT_SWING_MODE + return supports diff --git a/homeassistant/components/tuya/config_flow.py b/homeassistant/components/tuya/config_flow.py index 970a1c54f1ef8a..7ace8a6007c449 100644 --- a/homeassistant/components/tuya/config_flow.py +++ b/homeassistant/components/tuya/config_flow.py @@ -1,406 +1,162 @@ +#!/usr/bin/env python3 """Config flow for Tuya.""" + import logging -from tuyaha import TuyaApi -from tuyaha.tuyaapi import ( - TuyaAPIException, - TuyaAPIRateLimitException, - TuyaNetException, - TuyaServerException, -) +from tuya_iot import ProjectType, TuyaOpenAPI import voluptuous as vol from homeassistant import config_entries -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 from .const import ( - CONF_BRIGHTNESS_RANGE_MODE, - CONF_COUNTRYCODE, - CONF_CURR_TEMP_DIVIDER, - CONF_DISCOVERY_INTERVAL, - CONF_MAX_KELVIN, - CONF_MAX_TEMP, - CONF_MIN_KELVIN, - CONF_MIN_TEMP, - CONF_QUERY_DEVICE, - CONF_QUERY_INTERVAL, - CONF_SET_TEMP_DIVIDED, - CONF_SUPPORT_COLOR, - CONF_TEMP_DIVIDER, - CONF_TEMP_STEP_OVERRIDE, - CONF_TUYA_MAX_COLTEMP, - DEFAULT_DISCOVERY_INTERVAL, - DEFAULT_QUERY_INTERVAL, - DEFAULT_TUYA_MAX_COLTEMP, + CONF_ACCESS_ID, + CONF_ACCESS_SECRET, + CONF_APP_TYPE, + CONF_COUNTRY_CODE, + CONF_ENDPOINT, + CONF_PASSWORD, + CONF_PROJECT_TYPE, + CONF_USERNAME, DOMAIN, - TUYA_DATA, - TUYA_PLATFORMS, - TUYA_TYPE_NOT_QUERY, + TUYA_APP_TYPE, + TUYA_ENDPOINT, + TUYA_PROJECT_TYPE, ) +RESULT_SINGLE_INSTANCE = "single_instance_allowed" +RESULT_AUTH_FAILED = "invalid_auth" + _LOGGER = logging.getLogger(__name__) -CONF_LIST_DEVICES = "list_devices" +# Project Type +DATA_SCHEMA_PROJECT_TYPE = vol.Schema( + {vol.Required(CONF_PROJECT_TYPE, default=0): vol.In(TUYA_PROJECT_TYPE)} +) -DATA_SCHEMA_USER = vol.Schema( +# INDUSTRY_SOLUTIONS Schema +DATA_SCHEMA_INDUSTRY_SOLUTIONS = vol.Schema( { + vol.Required(CONF_ENDPOINT): vol.In(TUYA_ENDPOINT), + vol.Required(CONF_ACCESS_ID): str, + vol.Required(CONF_ACCESS_SECRET): str, vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_COUNTRYCODE): vol.Coerce(int), - vol.Required(CONF_PLATFORM): vol.In(TUYA_PLATFORMS), } ) -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_SINGLE_INSTANCE = "single_instance_allowed" -RESULT_SUCCESS = "success" - -RESULT_LOG_MESSAGE = { - RESULT_AUTH_FAILED: "Invalid credential", - RESULT_CONN_ERROR: "Connection error", -} - -TUYA_TYPE_CONFIG = ["climate", "light"] +# SMART_HOME Schema +DATA_SCHEMA_SMART_HOME = vol.Schema( + { + vol.Required(CONF_ACCESS_ID): str, + vol.Required(CONF_ACCESS_SECRET): str, + vol.Required(CONF_APP_TYPE): vol.In(TUYA_APP_TYPE), + vol.Required(CONF_COUNTRY_CODE): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } +) class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a tuya config flow.""" - - VERSION = 1 + """Tuya Config Flow.""" def __init__(self) -> None: - """Initialize flow.""" - self._country_code = None - self._password = None - self._platform = None - self._username = None - - def _save_entry(self): - return self.async_create_entry( - title=self._username, - data={ - CONF_COUNTRYCODE: self._country_code, - CONF_PASSWORD: self._password, - CONF_PLATFORM: self._platform, - CONF_USERNAME: self._username, - }, + """Init tuya config flow.""" + super().__init__() + self.conf_project_type = None + self.project_type = ProjectType.SMART_HOME + self.is_import = False + + @classmethod + def _try_login(cls, user_input): + _LOGGER.info(f"TuyaConfigFlow._try_login start, user_input: {user_input}") + project_type = ProjectType(user_input[CONF_PROJECT_TYPE]) + api = TuyaOpenAPI( + user_input[CONF_ENDPOINT] + if project_type == ProjectType.INDUSTY_SOLUTIONS + else "", + user_input[CONF_ACCESS_ID], + user_input[CONF_ACCESS_SECRET], + project_type, ) - - def _try_connect(self): - """Try to connect and check auth.""" - tuya = TuyaApi() - try: - tuya.init( - self._username, self._password, self._country_code, self._platform + api.set_dev_channel("hass") + + if project_type == ProjectType.INDUSTY_SOLUTIONS: + response = api.login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) + else: + api.endpoint = "https://openapi.tuyacn.com" + response = api.login( + user_input[CONF_USERNAME], + user_input[CONF_PASSWORD], + user_input[CONF_COUNTRY_CODE], + user_input[CONF_APP_TYPE], ) - except (TuyaAPIRateLimitException, TuyaNetException, TuyaServerException): - return RESULT_CONN_ERROR - except TuyaAPIException: - return RESULT_AUTH_FAILED - - return RESULT_SUCCESS - - async def async_step_user(self, user_input=None): - """Handle a flow initialized by the user.""" - if self._async_current_entries(): - return self.async_abort(reason=RESULT_SINGLE_INSTANCE) - - errors = {} - - if user_input is not None: - - self._country_code = str(user_input[CONF_COUNTRYCODE]) - self._password = user_input[CONF_PASSWORD] - self._platform = user_input[CONF_PLATFORM] - self._username = user_input[CONF_USERNAME] - - result = await self.hass.async_add_executor_job(self._try_connect) - - if result == RESULT_SUCCESS: - return self._save_entry() - if result != RESULT_AUTH_FAILED: - return self.async_abort(reason=result) - errors["base"] = result - - 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) -> None: - """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 response.get("success", False): + api.endpoint = api.token_info.platform_url + user_input[CONF_ENDPOINT] = api.token_info.platform_url + + _LOGGER.info(f"TuyaConfigFlow._try_login finish, response:, {response}") + return response + + async def async_step_import(self, user_input=None): + """Step import.""" + self.is_import = True + return await self.async_step_user(user_input) + + async def async_step_project_type(self, user_input=None): + """Step project type.""" + self.conf_project_type = user_input[CONF_PROJECT_TYPE] + self.project_type = ProjectType(self.conf_project_type) + return ( + self.async_show_form(step_id="user", data_schema=DATA_SCHEMA_SMART_HOME) + if self.project_type == ProjectType.SMART_HOME + else self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA_INDUSTRY_SOLUTIONS ) - 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 = 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_user(self, user_input=None): + """Step user.""" + _LOGGER.info( + f"TuyaConfigFlow.async_step_user start, is_import= {self.is_import}" ) + _LOGGER.info(f"TuyaConfigFlow.async_step_user start, user_input= {user_input}") - async def async_step_init(self, user_input=None): - """Handle options flow.""" - - if self.config_entry.state is not config_entries.ConfigEntryState.LOADED: - _LOGGER.error("Tuya integration not yet loaded") - return self.async_abort(reason=RESULT_CONN_ERROR) + if self._async_current_entries(): + return self.async_abort(reason=RESULT_SINGLE_INSTANCE) + errors = {} 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)), - } - ) + if self.conf_project_type is not None: + user_input[CONF_PROJECT_TYPE] = self.conf_project_type - 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), - } + response = await self.hass.async_add_executor_job( + self._try_login, user_input ) - 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)} + if response.get("success", False): + _LOGGER.info("TuyaConfigFlow.async_step_user login success") + return self.async_create_entry( + title=user_input[CONF_USERNAME], + data=user_input, + ) + + errors["base"] = RESULT_AUTH_FAILED + if self.is_import: + return self.async_abort(reason=errors["base"]) + + return ( + self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA_SMART_HOME, errors=errors + ) + if self.project_type == ProjectType.SMART_HOME + else self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA_INDUSTRY_SOLUTIONS, + errors=errors, + ) ) return self.async_show_form( - step_id="init", - data_schema=data_schema, - errors=self._get_form_error(), + step_id="project_type", data_schema=DATA_SCHEMA_PROJECT_TYPE, errors=errors ) - - 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() - - def _get_device_schema(self, device_type, curr_conf, device): - """Return option schema for device.""" - if device_type != device.device_type(): - return None - schema = None - if device_type == "light": - schema = self._get_light_schema(curr_conf, device) - elif device_type == "climate": - schema = self._get_climate_schema(curr_conf, device) - return schema - - @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): - """Create option schema for climate device.""" - unit = device.temperature_unit() - def_unit = TEMP_FAHRENHEIT if unit == "FAHRENHEIT" else TEMP_CELSIUS - supported_steps = device.supported_temperature_steps() - default_step = device.target_temperature_step() - - 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_SET_TEMP_DIVIDED, - default=curr_conf.get(CONF_SET_TEMP_DIVIDED, True), - ): bool, - vol.Optional( - CONF_TEMP_STEP_OVERRIDE, - default=curr_conf.get(CONF_TEMP_STEP_OVERRIDE, default_step), - ): vol.In(supported_steps), - 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, - } - ) - - return config_schema diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 646bcc077cfa42..00681f586ad8a0 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -1,39 +1,43 @@ +#!/usr/bin/env python3 """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_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_SET_TEMP_DIVIDED = "set_temp_divided" -CONF_SUPPORT_COLOR = "support_color" -CONF_TEMP_DIVIDER = "temp_divider" -CONF_TEMP_STEP_OVERRIDE = "temp_step_override" -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" +CONF_PROJECT_TYPE = "tuya_project_type" +CONF_ENDPOINT = "endpoint" +CONF_ACCESS_ID = "access_id" +CONF_ACCESS_SECRET = "access_secret" +CONF_USERNAME = "username" +CONF_PASSWORD = "password" +CONF_COUNTRY_CODE = "country_code" +CONF_APP_TYPE = "tuya_app_type" -TUYA_DATA = "tuya_data" -TUYA_DEVICES_CONF = "devices_config" TUYA_DISCOVERY_NEW = "tuya_discovery_new_{}" +TUYA_DEVICE_MANAGER = "tuya_device_manager" +TUYA_HOME_MANAGER = "tuya_home_manager" +TUYA_MQTT_LISTENER = "tuya_mqtt_listener" +TUYA_HA_TUYA_MAP = "tuya_ha_tuya_map" +TUYA_HA_DEVICES = "tuya_ha_devices" +TUYA_SETUP_PLATFORM = "setup_platform" -TUYA_PLATFORMS = { - "tuya": "Tuya", - "smart_life": "Smart Life", - "jinvoo_smart": "Jinvoo Smart", +TUYA_ENDPOINT = { + "https://openapi.tuyaus.com": "America", + "https://openapi.tuyacn.com": "China", + "https://openapi.tuyaeu.com": "Europe", + "https://openapi.tuyain.com": "India", + "https://openapi-ueaz.tuyaus.com": "EasternAmerica", + "https://openapi-weaz.tuyaeu.com": "WesternEurope", } -TUYA_TYPE_NOT_QUERY = ["scene", "switch"] +TUYA_PROJECT_TYPE = {1: "Custom Development", 0: "Smart Home PaaS"} + +TUYA_APP_TYPE = {"tuyaSmart": "TuyaSmart", "smartlife": "Smart Life"} + +TUYA_SUPPORT_HA_TYPE = [ + "climate", + "cover", + "fan", + "light", + "scene", + "switch", +] diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index 08f1d92aca5979..67f0d712006332 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -1,118 +1,131 @@ -"""Support for Tuya covers.""" -from datetime import timedelta +#!/usr/bin/env python3 +"""Support for Tuya Cover.""" +from __future__ import annotations + +import logging +from typing import Any from homeassistant.components.cover import ( - DOMAIN as SENSOR_DOMAIN, - ENTITY_ID_FORMAT, + DEVICE_CLASS_CURTAIN, + DOMAIN as DEVICE_DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, + SUPPORT_SET_POSITION, SUPPORT_STOP, CoverEntity, ) -from homeassistant.const import CONF_PLATFORM +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect -from . import TuyaDevice -from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW +from .base import TuyaHaDevice +from .const import ( + DOMAIN, + TUYA_DEVICE_MANAGER, + TUYA_DISCOVERY_NEW, + TUYA_HA_DEVICES, + TUYA_HA_TUYA_MAP, +) + +_LOGGER = logging.getLogger(__name__) + +TUYA_SUPPORT_TYPE = {"cl", "clkg"} # Curtain # Curtain Switch + +# Curtain +# https://developer.tuya.com/en/docs/iot/f?id=K9gf46o5mtfyc +DPCODE_CONTROL = "control" +DPCODE_PERCENT_CONTROL = "percent_control" +DPCODE_PERCENT_STATE = "percent_state" -SCAN_INTERVAL = timedelta(seconds=15) +ATTR_POSITION = "position" -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up tuya sensors dynamically through tuya discovery.""" +async def async_setup_entry( + hass: HomeAssistant, _entry: ConfigEntry, async_add_entities +): + """Set up tuya cover dynamically through tuya discovery.""" + _LOGGER.info("cover init") - platform = config_entry.data[CONF_PLATFORM] + hass.data[DOMAIN][TUYA_HA_TUYA_MAP].update({DEVICE_DOMAIN: TUYA_SUPPORT_TYPE}) - async def async_discover_sensor(dev_ids): - """Discover and add a discovered tuya sensor.""" + async def async_discover_device(dev_ids): + """Discover and add a discovered tuya cover.""" + _LOGGER.info(f"cover add-> {dev_ids}") if not dev_ids: return - entities = await hass.async_add_executor_job( - _setup_entities, - hass, - dev_ids, - platform, - ) + entities = await hass.async_add_executor_job(_setup_entities, hass, dev_ids) + hass.data[DOMAIN][TUYA_HA_DEVICES].extend(entities) async_add_entities(entities) async_dispatcher_connect( - hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor + hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device ) - devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) - await async_discover_sensor(devices_ids) + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] + device_ids = [] + for (device_id, device) in device_manager.device_map.items(): + if device.category in TUYA_SUPPORT_TYPE: + device_ids.append(device_id) + await async_discover_device(device_ids) -def _setup_entities(hass, dev_ids, platform): - """Set up Tuya Cover device.""" - tuya = hass.data[DOMAIN][TUYA_DATA] +def _setup_entities(hass, device_ids: list): + """Set up Tuya Cover.""" + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] entities = [] - for dev_id in dev_ids: - device = tuya.get_device_by_id(dev_id) + for device_id in device_ids: + device = device_manager.device_map[device_id] if device is None: continue - entities.append(TuyaCover(device, platform)) + entities.append(TuyaHaCover(device, device_manager)) return entities -class TuyaCover(TuyaDevice, CoverEntity): - """Tuya cover devices.""" - - def __init__(self, tuya, platform): - """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 +class TuyaHaCover(TuyaHaDevice, CoverEntity): + """Tuya Switch Device.""" + # property @property - def supported_features(self): - """Flag supported features.""" - if self._tuya.support_stop(): - return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP - return SUPPORT_OPEN | SUPPORT_CLOSE + def device_class(self) -> str: + """Return Entity Properties.""" + return DEVICE_CLASS_CURTAIN @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 + def is_closed(self) -> bool | None: + """Return is cover is closed.""" 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 + def current_cover_position(self) -> int: + """Return cover current position.""" + position = self.tuya_device.status.get(DPCODE_PERCENT_STATE, 0) + return 100 - position - @property - def is_closed(self): - """Return if the cover is closed or not.""" - state = self._tuya.state() - 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): + def open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - self._tuya.open_cover() + self._send_command([{"code": DPCODE_CONTROL, "value": "open"}]) - def close_cover(self, **kwargs): + def close_cover(self, **kwargs: Any) -> None: """Close cover.""" - self._tuya.close_cover() + self._send_command([{"code": DPCODE_CONTROL, "value": "close"}]) 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() + self._send_command([{"code": DPCODE_CONTROL, "value": "stop"}]) + + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + _LOGGER.debug(f"cover--> {kwargs}") + self._send_command( + [{"code": DPCODE_PERCENT_CONTROL, "value": kwargs[ATTR_POSITION]}] + ) + + @property + def supported_features(self): + """Flag supported features.""" + supports = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + + if DPCODE_PERCENT_CONTROL in self.tuya_device.status: + supports = supports | SUPPORT_SET_POSITION + + return supports diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index ab361c6ac31d5e..03e5f95e12af43 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -1,141 +1,263 @@ -"""Support for Tuya fans.""" +#!/usr/bin/env python3 +"""Support for Tuya Fan.""" from __future__ import annotations -from datetime import timedelta +import json +import logging +from typing import Any + +from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.components.fan import ( - DOMAIN as SENSOR_DOMAIN, - ENTITY_ID_FORMAT, + DIRECTION_FORWARD, + DIRECTION_REVERSE, + DOMAIN as DEVICE_DOMAIN, + SUPPORT_DIRECTION, SUPPORT_OSCILLATE, + SUPPORT_PRESET_MODE, SUPPORT_SET_SPEED, FanEntity, ) -from homeassistant.const import CONF_PLATFORM, STATE_OFF +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( ordered_list_item_to_percentage, percentage_to_ordered_list_item, ) -from . import TuyaDevice -from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW +from .base import TuyaHaDevice +from .const import ( + DOMAIN, + TUYA_DEVICE_MANAGER, + TUYA_DISCOVERY_NEW, + TUYA_HA_DEVICES, + TUYA_HA_TUYA_MAP, +) + +_LOGGER = logging.getLogger(__name__) + -SCAN_INTERVAL = timedelta(seconds=15) +# Fan +# https://developer.tuya.com/en/docs/iot/f?id=K9gf45vs7vkge +DPCODE_SWITCH = "switch" +DPCODE_FAN_SPEED = "fan_speed_percent" +DPCODE_MODE = "mode" +DPCODE_SWITCH_HORIZONTAL = "switch_horizontal" +DPCODE_FAN_DIRECTION = "fan_direction" +# Air Purifier +# https://developer.tuya.com/en/docs/iot/s?id=K9gf48r41mn81 +DPCODE_AP_FAN_SPEED = "speed" +DPCODE_AP_FAN_SPEED_ENUM = "fan_speed_enum" -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up tuya sensors dynamically through tuya discovery.""" +TUYA_SUPPORT_TYPE = { + "fs", # Fan + "kj", # Air Purifier +} - platform = config_entry.data[CONF_PLATFORM] - async def async_discover_sensor(dev_ids): - """Discover and add a discovered tuya sensor.""" +async def async_setup_entry( + hass: HomeAssistant, _entry: ConfigEntry, async_add_entities +): + """Set up tuya fan dynamically through tuya discovery.""" + _LOGGER.info("fan init") + + hass.data[DOMAIN][TUYA_HA_TUYA_MAP].update({DEVICE_DOMAIN: TUYA_SUPPORT_TYPE}) + + async def async_discover_device(dev_ids): + """Discover and add a discovered tuya fan.""" + _LOGGER.info(f"fan add-> {dev_ids}") if not dev_ids: return - entities = await hass.async_add_executor_job( - _setup_entities, - hass, - dev_ids, - platform, - ) + entities = await hass.async_add_executor_job(_setup_entities, hass, dev_ids) + hass.data[DOMAIN][TUYA_HA_DEVICES].extend(entities) async_add_entities(entities) async_dispatcher_connect( - hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor + hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device ) - devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) - await async_discover_sensor(devices_ids) + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] + device_ids = [] + for (device_id, device) in device_manager.device_map.items(): + if device.category in TUYA_SUPPORT_TYPE: + device_ids.append(device_id) + await async_discover_device(device_ids) -def _setup_entities(hass, dev_ids, platform): - """Set up Tuya Fan device.""" - tuya = hass.data[DOMAIN][TUYA_DATA] +def _setup_entities(hass, device_ids: list): + """Set up Tuya Fan.""" + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] entities = [] - for dev_id in dev_ids: - device = tuya.get_device_by_id(dev_id) + for device_id in device_ids: + device = device_manager.device_map[device_id] if device is None: continue - entities.append(TuyaFanDevice(device, platform)) + entities.append(TuyaHaFan(device, device_manager)) return entities -class TuyaFanDevice(TuyaDevice, FanEntity): - """Tuya fan devices.""" +class TuyaHaFan(TuyaHaDevice, FanEntity): + """Tuya Fan Device.""" - def __init__(self, tuya, platform): - """Init Tuya fan device.""" - super().__init__(tuya, platform) - self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) - self.speeds = [] + def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager): + """Init Tuya Fan Device.""" + super().__init__(device, device_manager) - async def async_added_to_hass(self): - """Create fan list when add to hass.""" - await super().async_added_to_hass() - self.speeds.extend(self._tuya.speed_list()) + # Air purifier fan can be controlled either via the ranged values or via the enum. + # We will always prefer the enumeration if available + # Enum is used for e.g. MEES SmartHIMOX-H06 + # Range is used for e.g. Concept CA3000 + self.air_purifier_speed_range_len = 0 + self.air_purifier_speed_range_enum = [] + if self.tuya_device.category == "kj": + try: + if ( + DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.status + or DPCODE_AP_FAN_SPEED in self.tuya_device.status + ): + + self.dp_code_speed_enum = ( + DPCODE_AP_FAN_SPEED_ENUM + if DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.status + else DPCODE_AP_FAN_SPEED + ) + data = json.loads( + self.tuya_device.function.get( + self.dp_code_speed_enum, {} + ).values + ).get("range") + if data: + self.air_purifier_speed_range_len = len(data) + self.air_purifier_speed_range_enum = data + except Exception: + _LOGGER.error("Cannot parse the air-purifier speed range") + + def set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode of the fan.""" + self._send_command([{"code": DPCODE_MODE, "value": preset_mode}]) + + def set_direction(self, direction: str) -> None: + """Set the direction of the fan.""" + self._send_command([{"code": DPCODE_FAN_DIRECTION, "value": direction}]) def set_percentage(self, percentage: int) -> None: - """Set the speed percentage of the fan.""" - if percentage == 0: - self.turn_off() + """Set the speed of the fan, as a percentage.""" + if self.tuya_device.category == "kj": + value_in_range = percentage_to_ordered_list_item( + self.air_purifier_speed_range_enum, percentage + ) + self._send_command( + [ + { + "code": self.dp_code_speed_enum, + "value": value_in_range, + } + ] + ) else: - tuya_speed = percentage_to_ordered_list_item(self.speeds, percentage) - self._tuya.set_speed(tuya_speed) + self._send_command([{"code": DPCODE_FAN_SPEED, "value": percentage}]) + + def turn_off(self, **kwargs: Any) -> None: + """Turn the fan off.""" + self._send_command([{"code": DPCODE_SWITCH, "value": False}]) def turn_on( self, speed: str = None, percentage: int = None, preset_mode: str = None, - **kwargs, + **kwargs: Any, ) -> None: """Turn on the fan.""" - if percentage is not None: - self.set_percentage(percentage) - else: - self._tuya.turn_on() - - def turn_off(self, **kwargs) -> None: - """Turn the entity off.""" - self._tuya.turn_off() + self._send_command([{"code": DPCODE_SWITCH, "value": True}]) - def oscillate(self, oscillating) -> None: + def oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" - self._tuya.oscillate(oscillating) + self._send_command([{"code": DPCODE_SWITCH_HORIZONTAL, "value": oscillating}]) + # property @property - def speed_count(self) -> int: - """Return the number of speeds the fan supports.""" - if self.speeds is None: - return super().speed_count - return len(self.speeds) + def is_on(self) -> bool: + """Return true if fan is on.""" + return self.tuya_device.status.get(DPCODE_SWITCH, False) + + @property + def current_direction(self) -> str: + """Return the current direction of the fan.""" + return ( + DIRECTION_FORWARD + if self.tuya_device.status.get(DPCODE_FAN_DIRECTION) + else DIRECTION_REVERSE + ) @property - def oscillating(self): - """Return current oscillating status.""" - if self.supported_features & SUPPORT_OSCILLATE == 0: - return None - if self.speed == STATE_OFF: - return False - return self._tuya.oscillating() + def oscillating(self) -> bool: + """Return true if the fan is oscillating.""" + return self.tuya_device.status.get(DPCODE_SWITCH_HORIZONTAL, False) + + @property + def preset_modes(self) -> list: + """Return the list of available preset_modes.""" + if DPCODE_MODE not in self.tuya_device.function: + return [] + + try: + data = json.loads( + self.tuya_device.function.get(DPCODE_MODE, {}).values + ).get("range") + return data + except Exception: + _LOGGER.error("Cannot parse the preset modes") + return [] @property - def is_on(self): - """Return true if the entity is on.""" - return self._tuya.state() + def preset_mode(self) -> str: + """Return the current preset_mode.""" + return self.tuya_device.status.get(DPCODE_MODE) @property - def percentage(self) -> int | None: + def percentage(self) -> int: """Return the current speed.""" if not self.is_on: return 0 - if self.speeds is None: - return None - return ordered_list_item_to_percentage(self.speeds, self._tuya.speed()) + + if self.tuya_device.category == "kj": + if self.air_purifier_speed_range_len > 1: + if not self.air_purifier_speed_range_enum: + # if air-purifier speed enumeration is supported we will prefer it. + return ordered_list_item_to_percentage( + self.air_purifier_speed_range_enum, + self.tuya_device.status.get(DPCODE_AP_FAN_SPEED_ENUM, 0), + ) + + return self.tuya_device.status.get(DPCODE_FAN_SPEED, 0) @property - def supported_features(self) -> int: + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + if self.tuya_device.category == "kj": + return self.air_purifier_speed_range_len + return super().speed_count + + @property + def supported_features(self): """Flag supported features.""" - if self._tuya.support_oscillate(): - return SUPPORT_SET_SPEED | SUPPORT_OSCILLATE - return SUPPORT_SET_SPEED + supports = 0 + if DPCODE_MODE in self.tuya_device.status: + supports = supports | SUPPORT_PRESET_MODE + if DPCODE_FAN_SPEED in self.tuya_device.status: + supports = supports | SUPPORT_SET_SPEED + if DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status: + supports = supports | SUPPORT_OSCILLATE + if DPCODE_FAN_DIRECTION in self.tuya_device.status: + supports = supports | SUPPORT_DIRECTION + + # Air Purifier specific + if ( + DPCODE_AP_FAN_SPEED in self.tuya_device.status + or DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.status + ): + supports = supports | SUPPORT_SET_SPEED + return supports diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 4602e65a4d5e74..646c65355f6633 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -1,198 +1,355 @@ +#!/usr/bin/env python3 """Support for the Tuya lights.""" -from datetime import timedelta + +import json +import logging +from typing import Any, Dict, List, Tuple + +from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - DOMAIN as SENSOR_DOMAIN, - ENTITY_ID_FORMAT, + DOMAIN as DEVICE_DOMAIN, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, LightEntity, ) -from homeassistant.const import CONF_PLATFORM -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.util import color as colorutil -from . import TuyaDevice +from .base import TuyaHaDevice 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_DEVICE_MANAGER, TUYA_DISCOVERY_NEW, + TUYA_HA_DEVICES, + TUYA_HA_TUYA_MAP, ) -SCAN_INTERVAL = timedelta(seconds=15) +_LOGGER = logging.getLogger(__name__) + + +# Light(dj) +# https://developer.tuya.com/en/docs/iot/f?id=K9i5ql3v98hn3 +DPCODE_SWITCH = "switch_led" +DPCODE_WORK_MODE = "work_mode" +DPCODE_BRIGHT_VALUE = "bright_value" +DPCODE_TEMP_VALUE = "temp_value" +DPCODE_COLOUR_DATA = "colour_data" +DPCODE_COLOUR_DATA_V2 = "colour_data_v2" + +MIREDS_MAX = 500 +MIREDS_MIN = 153 -TUYA_BRIGHTNESS_RANGE0 = (1, 255) -TUYA_BRIGHTNESS_RANGE1 = (10, 1000) +HSV_HA_HUE_MIN = 0 +HSV_HA_HUE_MAX = 360 +HSV_HA_SATURATION_MIN = 0 +HSV_HA_SATURATION_MAX = 100 -BRIGHTNESS_MODES = { - 0: TUYA_BRIGHTNESS_RANGE0, - 1: TUYA_BRIGHTNESS_RANGE1, +WORK_MODE_WHITE = "white" +WORK_MODE_COLOUR = "colour" + +TUYA_SUPPORT_TYPE = { + "dj", # Light + "dd", # Light strip + "fwl", # Ambient light + "dc", # Light string + "jsq", # Humidifier's light +} + +DEFAULT_HSV = { + "h": {"min": 1, "scale": 0, "unit": "", "max": 360, "step": 1}, + "s": {"min": 1, "scale": 0, "unit": "", "max": 255, "step": 1}, + "v": {"min": 1, "scale": 0, "unit": "", "max": 255, "step": 1}, } +DEFAULT_HSV_V2 = { + "h": {"min": 1, "scale": 0, "unit": "", "max": 360, "step": 1}, + "s": {"min": 1, "scale": 0, "unit": "", "max": 1000, "step": 1}, + "v": {"min": 1, "scale": 0, "unit": "", "max": 1000, "step": 1}, +} -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up tuya sensors dynamically through tuya discovery.""" - platform = config_entry.data[CONF_PLATFORM] +async def async_setup_entry( + hass: HomeAssistant, _entry: ConfigEntry, async_add_entities +): + """Set up tuya light dynamically through tuya discovery.""" + _LOGGER.info("light init") - async def async_discover_sensor(dev_ids): - """Discover and add a discovered tuya sensor.""" + hass.data[DOMAIN][TUYA_HA_TUYA_MAP].update({DEVICE_DOMAIN: TUYA_SUPPORT_TYPE}) + + async def async_discover_device(dev_ids): + """Discover and add a discovered tuya light.""" + _LOGGER.info(f"light add-> {dev_ids}") if not dev_ids: return - entities = await hass.async_add_executor_job( - _setup_entities, - hass, - dev_ids, - platform, - ) + entities = await hass.async_add_executor_job(_setup_entities, hass, dev_ids) + hass.data[DOMAIN][TUYA_HA_DEVICES].extend(entities) async_add_entities(entities) async_dispatcher_connect( - hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor + hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device ) - devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) - await async_discover_sensor(devices_ids) + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] + device_ids = [] + for (device_id, device) in device_manager.device_map.items(): + if device.category in TUYA_SUPPORT_TYPE: + device_ids.append(device_id) + await async_discover_device(device_ids) -def _setup_entities(hass, dev_ids, platform): +def _setup_entities(hass, device_ids: List): """Set up Tuya Light device.""" - tuya = hass.data[DOMAIN][TUYA_DATA] + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] entities = [] - for dev_id in dev_ids: - device = tuya.get_device_by_id(dev_id) + for device_id in device_ids: + device = device_manager.device_map[device_id] if device is None: continue - entities.append(TuyaLight(device, platform)) + entities.append(TuyaHaLight(device, device_manager)) return entities -class TuyaLight(TuyaDevice, LightEntity): +class TuyaHaLight(TuyaHaDevice, LightEntity): """Tuya light device.""" - def __init__(self, tuya, platform): - """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 + def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager): + """Init TuyaHaLight.""" + self.dp_code_bright = DPCODE_BRIGHT_VALUE + self.dp_code_temp = DPCODE_TEMP_VALUE + self.dp_code_colour = DPCODE_COLOUR_DATA - # 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 + for key in device.function: + if key.startswith(DPCODE_BRIGHT_VALUE): + self.dp_code_bright = key + elif key.startswith(DPCODE_TEMP_VALUE): + self.dp_code_temp = key + elif key.startswith(DPCODE_COLOUR_DATA): + self.dp_code_colour = key + + super().__init__(device, device_manager) + + @property + def is_on(self) -> bool: + """Return true if light is on.""" + return self.tuya_device.status.get(DPCODE_SWITCH, False) + + def turn_on(self, **kwargs: Any) -> None: + """Turn on or control the light.""" + commands = [] + _LOGGER.debug(f"light kwargs-> {kwargs}") + # if ( + # ATTR_BRIGHTNESS not in kwargs + # and ATTR_HS_COLOR not in kwargs + # and ATTR_COLOR_TEMP not in kwargs + # ): + commands += [{"code": DPCODE_SWITCH, "value": True}] + + if ATTR_BRIGHTNESS in kwargs: + if self._work_mode().startswith(WORK_MODE_COLOUR): + colour_data = self._get_hsv() + v_range = self._tuya_hsv_v_range() + # hsv_v = colour_data.get('v', 0) + colour_data["v"] = int( + self.remap(kwargs[ATTR_BRIGHTNESS], 0, 255, v_range[0], v_range[1]) + ) + commands += [ + {"code": self.dp_code_colour, "value": json.dumps(colour_data)} + ] + else: + new_range = self._tuya_brightness_range() + tuya_brightness = int( + self.remap( + kwargs[ATTR_BRIGHTNESS], 0, 255, new_range[0], new_range[1] + ) + ) + commands += [{"code": self.dp_code_bright, "value": tuya_brightness}] + # commands += [{'code': DPCODE_WORK_MODE, 'value': self._work_mode()}] + + if ATTR_HS_COLOR in kwargs: + colour_data = self._get_hsv() + # hsv h + colour_data["h"] = int(kwargs[ATTR_HS_COLOR][0]) + # hsv s + ha_s = kwargs[ATTR_HS_COLOR][1] + s_range = self._tuya_hsv_s_range() + colour_data["s"] = int( + self.remap( + ha_s, + HSV_HA_SATURATION_MIN, + HSV_HA_SATURATION_MAX, + s_range[0], + s_range[1], + ) ) - ) - return + # hsv v + ha_v = self.brightness + v_range = self._tuya_hsv_v_range() + colour_data["v"] = int(self.remap(ha_v, 0, 255, v_range[0], v_range[1])) + + commands += [ + {"code": self.dp_code_colour, "value": json.dumps(colour_data)} + ] + if self.tuya_device.status[DPCODE_WORK_MODE] != "colour": + commands += [{"code": DPCODE_WORK_MODE, "value": "colour"}] + + if ATTR_COLOR_TEMP in kwargs: + # temp color + new_range = self._tuya_temp_range() + color_temp = self.remap( + self.max_mireds - kwargs[ATTR_COLOR_TEMP] + self.min_mireds, + self.min_mireds, + self.max_mireds, + new_range[0], + new_range[1], + ) + commands += [{"code": self.dp_code_temp, "value": int(color_temp)}] + + # brightness + ha_brightness = self.brightness + new_range = self._tuya_brightness_range() + tuya_brightness = self.remap( + ha_brightness, 0, 255, new_range[0], new_range[1] + ) + commands += [{"code": self.dp_code_bright, "value": int(tuya_brightness)}] + + if self.tuya_device.status[DPCODE_WORK_MODE] != "white": + commands += [{"code": DPCODE_WORK_MODE, "value": "white"}] + + self._send_command(commands) + + def turn_off(self, **kwargs: Any) -> None: + """Instruct the light to turn off.""" + commands = [{"code": DPCODE_SWITCH, "value": False}] + self._send_command(commands) + + # LightEntity @property def brightness(self): """Return the brightness of the light.""" - if self._tuya.brightness() is None: - return None - return int(self._tuya.brightness()) + old_range = self._tuya_brightness_range() + brightness = self.tuya_device.status.get(self.dp_code_bright, 0) + + _LOGGER.debug( + f"""brightness id-> {self.tuya_device.id}, + work_mode-> {self._work_mode()}, + check true-> {self._work_mode().startswith(WORK_MODE_COLOUR)}""" + ) + + if self._work_mode().startswith(WORK_MODE_COLOUR): + colour_data = json.loads( + self.tuya_device.status.get(self.dp_code_colour, 0) + ) + v_range = self._tuya_hsv_v_range() + hsv_v = colour_data.get("v", 0) + return int(self.remap(hsv_v, v_range[0], v_range[1], 0, 255)) + + return int(self.remap(brightness, old_range[0], old_range[1], 0, 255)) + + def _tuya_brightness_range(self) -> Tuple[int, int]: + if self.dp_code_bright not in self.tuya_device.status: + return 0, 255 + + bright_value = json.loads( + self.tuya_device.function.get(self.dp_code_bright, {}).values + ) + return bright_value.get("min", 0), bright_value.get("max", 255) @property def hs_color(self): """Return the hs_color of the light.""" - return tuple(map(int, self._tuya.hs_color())) + colour_data = json.loads(self.tuya_device.status.get(self.dp_code_colour, 0)) + s_range = self._tuya_hsv_s_range() + return colour_data.get("h", 0), self.remap( + colour_data.get("s", 0), + s_range[0], + s_range[1], + HSV_HA_SATURATION_MIN, + HSV_HA_SATURATION_MAX, + ) @property def color_temp(self): """Return the color_temp of the light.""" - color_temp = int(self._tuya.color_temp()) - if color_temp is None: - return None - return colorutil.color_temperature_kelvin_to_mired(color_temp) - - @property - def is_on(self): - """Return true if light is on.""" - return self._tuya.state() + new_range = self._tuya_temp_range() + tuya_color_temp = self.tuya_device.status.get(self.dp_code_temp, 0) + ha_color_temp = ( + self.max_mireds + - self.remap( + tuya_color_temp, + new_range[0], + new_range[1], + self.min_mireds, + self.max_mireds, + ) + + self.min_mireds + ) + return ha_color_temp @property def min_mireds(self): """Return color temperature min mireds.""" - return colorutil.color_temperature_kelvin_to_mired(self._max_kelvin) + return MIREDS_MIN @property def max_mireds(self): """Return color temperature max mireds.""" - return colorutil.color_temperature_kelvin_to_mired(self._min_kelvin) + return MIREDS_MAX - def turn_on(self, **kwargs): - """Turn on or control the light.""" - if ( - ATTR_BRIGHTNESS not in kwargs - and ATTR_HS_COLOR not in kwargs - and ATTR_COLOR_TEMP not in kwargs - ): - self._tuya.turn_on() - if ATTR_BRIGHTNESS in kwargs: - self._tuya.set_brightness(kwargs[ATTR_BRIGHTNESS]) - if ATTR_HS_COLOR in kwargs: - self._tuya.set_color(kwargs[ATTR_HS_COLOR]) - if ATTR_COLOR_TEMP in kwargs: - color_temp = colorutil.color_temperature_mired_to_kelvin( - kwargs[ATTR_COLOR_TEMP] + def _tuya_temp_range(self) -> Tuple[int, int]: + temp_value = json.loads( + self.tuya_device.function.get(self.dp_code_temp, {}).values + ) + return temp_value.get("min", 0), temp_value.get("max", 255) + + def _tuya_hsv_s_range(self) -> Tuple[int, int]: + colour_data = self._tuya_hsv_function() + hsv_s = colour_data.get("s") + return hsv_s.get("min", 0), hsv_s.get("max", 255) + + def _tuya_hsv_v_range(self) -> Tuple[int, int]: + colour_data = self._tuya_hsv_function() + hsv_v = colour_data.get("v") + return hsv_v.get("min", 0), hsv_v.get("max", 255) + + def _tuya_hsv_function(self): + hsv_data = json.loads( + self.tuya_device.function.get(self.dp_code_colour, {}).values + ) + if hsv_data == {}: + return ( + DEFAULT_HSV_V2 + if self.dp_code_colour == DPCODE_COLOUR_DATA_V2 + else DEFAULT_HSV ) - self._tuya.set_color_temp(color_temp) - def turn_off(self, **kwargs): - """Instruct the light to turn off.""" - self._tuya.turn_off() + return hsv_data + + def _work_mode(self) -> str: + + return self.tuya_device.status.get(DPCODE_WORK_MODE, "") + + def _get_hsv(self) -> Dict[str, int]: + return json.loads(self.tuya_device.status[self.dp_code_colour]) @property def supported_features(self): """Flag supported features.""" - supports = SUPPORT_BRIGHTNESS - if self._tuya.support_color(): + supports = 0 + if self.dp_code_bright in self.tuya_device.status: + supports = supports | SUPPORT_BRIGHTNESS + + if ( + self.dp_code_colour in self.tuya_device.status + and len(self.tuya_device.status[self.dp_code_colour]) > 0 + ): supports = supports | SUPPORT_COLOR - if self._tuya.support_color_temp(): + if self.dp_code_temp in self.tuya_device.status: supports = supports | SUPPORT_COLOR_TEMP return supports diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index 5dae8e6a101969..8b97058025950a 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -1,17 +1,13 @@ { "domain": "tuya", "name": "Tuya", - "documentation": "https://www.home-assistant.io/integrations/tuya", - "requirements": ["tuyaha==0.0.10"], - "codeowners": ["@ollo69"], + "documentation": "https://github.com/tuya/tuya-home-assistant", + "requirements": [ + "tuya-iot-py-sdk==0.4.0" + ], + "codeowners": [ + "@Tuya" + ], "config_flow": true, - "iot_class": "cloud_polling", - "dhcp": [ - {"macaddress": "508A06*"}, - {"macaddress": "7CF666*"}, - {"macaddress": "10D561*"}, - {"macaddress": "D4A651*"}, - {"macaddress": "68572D*"}, - {"macaddress": "1869D8*"} - ] -} + "iot_class": "cloud_push" +} \ No newline at end of file diff --git a/homeassistant/components/tuya/scene.py b/homeassistant/components/tuya/scene.py index 430b2bc7e27729..85727405a9ca78 100644 --- a/homeassistant/components/tuya/scene.py +++ b/homeassistant/components/tuya/scene.py @@ -1,61 +1,84 @@ -"""Support for the Tuya scenes.""" +#!/usr/bin/env python3 +"""Support for Tuya switches.""" +from __future__ import annotations + +import logging from typing import Any -from homeassistant.components.scene import DOMAIN as SENSOR_DOMAIN, Scene -from homeassistant.const import CONF_PLATFORM -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from tuya_iot import TuyaHomeManager, TuyaScene + +from homeassistant.components.scene import Scene +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant -from . import TuyaDevice -from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW +from .const import DOMAIN, TUYA_HOME_MANAGER -ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}" +_LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up tuya sensors dynamically through tuya discovery.""" +async def async_setup_entry( + hass: HomeAssistant, _entry: ConfigEntry, async_add_entities +): + """Set up tuya scenes.""" + _LOGGER.info("scenes init") - platform = config_entry.data[CONF_PLATFORM] + entities = [] - async def async_discover_sensor(dev_ids): - """Discover and add a discovered tuya sensor.""" - if not dev_ids: - return - entities = await hass.async_add_executor_job( - _setup_entities, - hass, - dev_ids, - platform, - ) - async_add_entities(entities) + __home_manager = hass.data[DOMAIN][TUYA_HOME_MANAGER] + scenes = await hass.async_add_executor_job(__home_manager.query_scenes) + for scene in scenes: + entities.append(TuyaHAScene(__home_manager, scene)) - async_dispatcher_connect( - hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor - ) + async_add_entities(entities) + print("scene remote ok") - devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) - await async_discover_sensor(devices_ids) +class TuyaHAScene(Scene): + """Tuya Scene Remote.""" -def _setup_entities(hass, dev_ids, platform): - """Set up Tuya Scene.""" - tuya = hass.data[DOMAIN][TUYA_DATA] - entities = [] - for dev_id in dev_ids: - device = tuya.get_device_by_id(dev_id) - if device is None: - continue - entities.append(TuyaScene(device, platform)) - return entities + def __init__(self, home_manager: TuyaHomeManager, scene: TuyaScene) -> None: + """Init Tuya Scene.""" + super().__init__() + self.home_manager = home_manager + self.scene = scene + + @property + def should_poll(self) -> bool: + """Hass should not poll.""" + return False + + @property + def unique_id(self) -> str | None: + """Return a unique ID.""" + return f"tys{self.scene.scene_id}" + + @property + def name(self) -> str | None: + """Return Tuya scene name.""" + return self.scene.name + @property + def device_info(self): + """Return a device description for device registry.""" + _device_info = { + "identifiers": {(DOMAIN, f"{self.unique_id}")}, + "manufacturer": "tuya", + "name": self.scene.name, + "model": "Tuya Scene", + } + return _device_info -class TuyaScene(TuyaDevice, Scene): - """Tuya Scene.""" + @property + def available(self) -> bool: + """Return if the scene is enabled.""" + print(f"remove available->{self.scene.enabled}") + return self.scene.enabled - def __init__(self, tuya, platform): - """Init Tuya scene.""" - super().__init__(tuya, platform) - self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) + @property + def current_activity(self) -> str | None: + """Active activity.""" + return self.scene.name def activate(self, **kwargs: Any) -> None: """Activate the scene.""" - self._tuya.activate() + self.home_manager.trigger_scene(self.scene.home_id, self.scene.scene_id) diff --git a/homeassistant/components/tuya/services.yaml b/homeassistant/components/tuya/services.yaml deleted file mode 100644 index 42fba3ad37b0fb..00000000000000 --- a/homeassistant/components/tuya/services.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Describes the format for available Tuya services - -pull_devices: - name: Pull devices - description: Pull device list from Tuya server. - -force_update: - name: Force update - description: Force all Tuya devices to pull data. diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index 61ea46c6a9f29c..59996e950618cd 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -1,14 +1,24 @@ { "config": { + "flow_title": "Tuya configuration", "step": { + "project_type":{ + "title":"Tuya Integration", + "data":{ + "tuya_project_type": "Tuya cloud project type" + } + }, "user": { "title": "Tuya", - "description": "Enter your Tuya credentials.", + "description": "Enter your Tuya credential", "data": { - "country_code": "Your account country code (e.g., 1 for USA or 86 for China)", - "password": "[%key:common::config_flow::data::password%]", - "platform": "The app where your account is registered", - "username": "[%key:common::config_flow::data::username%]" + "endpoint": "Availability Zone", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "Mobile App", + "country_code": "Country Code", + "username": "Account", + "password": "Password" } } }, @@ -20,45 +30,5 @@ "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" } - }, - "options": { - "abort": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" - }, - "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)", - "set_temp_divided": "Use divided Temperature value for set temperature command", - "temp_step_override": "Target Temperature step", - "min_temp": "Min target temperature (use min and max = 0 for default)", - "max_temp": "Max target temperature (use min and max = 0 for default)" - } - } - }, - "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" - } } -} +} \ No newline at end of file diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 3f5ff6db163b1f..d893fbe69f1bda 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -1,74 +1,168 @@ +#!/usr/bin/env python3 """Support for Tuya switches.""" -from datetime import timedelta +from __future__ import annotations -from homeassistant.components.switch import ( - DOMAIN as SENSOR_DOMAIN, - ENTITY_ID_FORMAT, - SwitchEntity, -) -from homeassistant.const import CONF_PLATFORM -from homeassistant.helpers.dispatcher import async_dispatcher_connect +import logging +from typing import Any -from . import TuyaDevice -from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW +from tuya_iot import TuyaDevice, TuyaDeviceManager -SCAN_INTERVAL = timedelta(seconds=15) +from homeassistant.components.switch import DOMAIN as DEVICE_DOMAIN, SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .base import TuyaHaDevice +from .const import ( + DOMAIN, + TUYA_DEVICE_MANAGER, + TUYA_DISCOVERY_NEW, + TUYA_HA_DEVICES, + TUYA_HA_TUYA_MAP, +) -async def async_setup_entry(hass, config_entry, async_add_entities): +_LOGGER = logging.getLogger(__name__) + +TUYA_SUPPORT_TYPE = { + "kg", # Switch + "cz", # Socket + "pc", # Power Strip + "bh", # Smart Kettle + "dlq", # Breaker + "cwysj", # Pet Water Feeder + "kj", # Air Purifier + "xxj", # Diffuser +} + +# Switch(kg), Socket(cz), Power Strip(pc) +# https://developer.tuya.com/en/docs/iot/categorykgczpc?id=Kaiuz08zj1l4y +DPCODE_SWITCH = "switch" + +# Air Purifier +# https://developer.tuya.com/en/docs/iot/categorykj?id=Kaiuz1atqo5l7 +# Pet Water Feeder +# https://developer.tuya.com/en/docs/iot/f?id=K9gf46aewxem5 +DPCODE_ANION = "anion" # Air Purifier - Ionizer unit +# Air Purifier - Filter cartridge resetting; Pet Water Feeder - Filter cartridge resetting +DPCODE_FRESET = "filter_reset" +DPCODE_LIGHT = "light" # Air Purifier - Light +DPCODE_LOCK = "lock" # Air Purifier - Child lock +# Air Purifier - UV sterilization; Pet Water Feeder - UV sterilization +DPCODE_UV = "uv" +DPCODE_WET = "wet" # Air Purifier - Humidification unit +DPCODE_PRESET = "pump_reset" # Pet Water Feeder - Water pump resetting +DPCODE_WRESET = "water_reset" # Pet Water Feeder - Resetting of water usage days + + +DPCODE_START = "start" + + +async def async_setup_entry( + hass: HomeAssistant, _entry: ConfigEntry, async_add_entities +): """Set up tuya sensors dynamically through tuya discovery.""" + _LOGGER.info("switch init") - platform = config_entry.data[CONF_PLATFORM] + hass.data[DOMAIN][TUYA_HA_TUYA_MAP].update({DEVICE_DOMAIN: TUYA_SUPPORT_TYPE}) - async def async_discover_sensor(dev_ids): + async def async_discover_device(dev_ids): """Discover and add a discovered tuya sensor.""" + _LOGGER.info(f"switch add-> {dev_ids}") if not dev_ids: return - entities = await hass.async_add_executor_job( - _setup_entities, - hass, - dev_ids, - platform, - ) + entities = await hass.async_add_executor_job(_setup_entities, hass, dev_ids) + hass.data[DOMAIN][TUYA_HA_DEVICES].extend(entities) async_add_entities(entities) async_dispatcher_connect( - hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor + hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device ) - devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) - await async_discover_sensor(devices_ids) + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] + device_ids = [] + for (device_id, device) in device_manager.device_map.items(): + if device.category in TUYA_SUPPORT_TYPE: + device_ids.append(device_id) + await async_discover_device(device_ids) -def _setup_entities(hass, dev_ids, platform): +def _setup_entities(hass, device_ids: list): """Set up Tuya Switch device.""" - tuya = hass.data[DOMAIN][TUYA_DATA] + device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER] entities = [] - for dev_id in dev_ids: - device = tuya.get_device_by_id(dev_id) + for device_id in device_ids: + device = device_manager.device_map[device_id] if device is None: continue - entities.append(TuyaSwitch(device, platform)) + + for function in device.function: + if device.category == "kj": + if function in [ + DPCODE_ANION, + DPCODE_FRESET, + DPCODE_LIGHT, + DPCODE_LOCK, + DPCODE_UV, + DPCODE_WET, + ]: + entities.append(TuyaHaSwitch(device, device_manager, function)) + # Main device switch is handled by the Fan object + elif device.category == "cwysj": + if function in [DPCODE_FRESET, DPCODE_UV, DPCODE_PRESET, DPCODE_WRESET]: + entities.append(TuyaHaSwitch(device, device_manager, function)) + + if function.startswith(DPCODE_SWITCH): + # Main device switch + entities.append(TuyaHaSwitch(device, device_manager, function)) + continue + else: + if function.startswith(DPCODE_START): + entities.append(TuyaHaSwitch(device, device_manager, function)) + continue + if function.startswith(DPCODE_SWITCH): + entities.append(TuyaHaSwitch(device, device_manager, function)) + continue return entities -class TuyaSwitch(TuyaDevice, SwitchEntity): +class TuyaHaSwitch(TuyaHaDevice, SwitchEntity): """Tuya Switch Device.""" - def __init__(self, tuya, platform): - """Init Tuya switch device.""" - super().__init__(tuya, platform) - self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) + dp_code_switch = DPCODE_SWITCH + dp_code_start = DPCODE_START + + def __init__( + self, device: TuyaDevice, device_manager: TuyaDeviceManager, dp_code: str = "" + ) -> None: + """Init TuyaHaSwitch.""" + super().__init__(device, device_manager) + + self.dp_code = dp_code + self.channel = ( + dp_code.replace(DPCODE_SWITCH, "") + if dp_code.startswith(DPCODE_SWITCH) + else dp_code + ) + + @property + def unique_id(self) -> str | None: + """Return a unique ID.""" + return f"{super().unique_id}{self.channel}" + + @property + def name(self) -> str | None: + """Return Tuya device name.""" + return self.tuya_device.name + self.channel @property - def is_on(self): + def is_on(self) -> bool: """Return true if switch is on.""" - return self._tuya.state() + return self.tuya_device.status.get(self.dp_code, False) - def turn_on(self, **kwargs): + def turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" - self._tuya.turn_on() + self._send_command([{"code": self.dp_code, "value": True}]) - def turn_off(self, **kwargs): - """Turn the device off.""" - self._tuya.turn_off() + def turn_off(self, **kwargs: Any) -> None: + """Turn the switch off.""" + self._send_command([{"code": self.dp_code, "value": False}]) diff --git a/homeassistant/components/tuya/translations/af.json b/homeassistant/components/tuya/translations/af.json deleted file mode 100644 index 71ac741b6b885a..00000000000000 --- a/homeassistant/components/tuya/translations/af.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "options": { - "error": { - "dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar", - "dev_not_found": "Ger\u00e4t nicht gefunden" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ca.json b/homeassistant/components/tuya/translations/ca.json deleted file mode 100644 index 62fad2ad47ffe9..00000000000000 --- a/homeassistant/components/tuya/translations/ca.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", - "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." - }, - "error": { - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" - }, - "flow_title": "Configuraci\u00f3 de Tuya", - "step": { - "user": { - "data": { - "country_code": "El teu codi de pa\u00eds (per exemple, 1 per l'EUA o 86 per la Xina)", - "password": "Contrasenya", - "platform": "L'aplicaci\u00f3 on es registra el teu compte", - "username": "Nom d'usuari" - }, - "description": "Introdueix les teves credencial de Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Ha fallat la connexi\u00f3" - }, - "error": { - "dev_multi_type": "Per configurar una selecci\u00f3 de m\u00faltiples dispositius, aquests han de ser del mateix tipus", - "dev_not_config": "El tipus d'aquest dispositiu no \u00e9s configurable", - "dev_not_found": "No s'ha trobat el dispositiu." - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rang de brillantor utilitzat pel dispositiu", - "curr_temp_divider": "Divisor del valor de temperatura actual (0 = predeterminat)", - "max_kelvin": "Temperatura del color m\u00e0xima suportada, en Kelvin", - "max_temp": "Temperatura desitjada m\u00e0xima (utilitza min i max = 0 per defecte)", - "min_kelvin": "Temperatura del color m\u00ednima suportada, en Kelvin", - "min_temp": "Temperatura desitjada m\u00ednima (utilitza min i max = 0 per defecte)", - "set_temp_divided": "Utilitza el valor de temperatura dividit per a ordres de configuraci\u00f3 de temperatura", - "support_color": "For\u00e7a el suport de color", - "temp_divider": "Divisor del valor de temperatura (0 = predeterminat)", - "temp_step_override": "Pas de temperatura objectiu", - "tuya_max_coltemp": "Temperatura de color m\u00e0xima enviada pel dispositiu", - "unit_of_measurement": "Unitat de temperatura utilitzada pel dispositiu" - }, - "description": "Configura les opcions per ajustar la informaci\u00f3 mostrada pel dispositiu {device_type} `{device_name}`", - "title": "Configuraci\u00f3 de dispositiu Tuya" - }, - "init": { - "data": { - "discovery_interval": "Interval de sondeig del dispositiu de descoberta, en segons", - "list_devices": "Selecciona els dispositius a configurar o deixa-ho buit per desar la configuraci\u00f3", - "query_device": "Selecciona el dispositiu que utilitzar\u00e0 m\u00e8tode de consulta, per actualitzacions d'estat m\u00e9s freq\u00fcents", - "query_interval": "Interval de sondeig de consultes del dispositiu, en segons" - }, - "description": "No estableixis valors d'interval de sondeig massa baixos ja que les crides fallaran i generaran missatges d'error al registre", - "title": "Configuraci\u00f3 d'opcions de Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/cs.json b/homeassistant/components/tuya/translations/cs.json deleted file mode 100644 index 1dda4ea6df737f..00000000000000 --- a/homeassistant/components/tuya/translations/cs.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", - "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." - }, - "error": { - "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" - }, - "flow_title": "Konfigurace Tuya", - "step": { - "user": { - "data": { - "country_code": "K\u00f3d zem\u011b va\u0161eho \u00fa\u010dtu (nap\u0159. 1 pro USA nebo 86 pro \u010c\u00ednu)", - "password": "Heslo", - "platform": "Aplikace, ve kter\u00e9 m\u00e1te zaregistrovan\u00fd \u00fa\u010det", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - }, - "description": "Zadejte sv\u00e9 p\u0159ihla\u0161ovac\u00ed \u00fadaje k Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" - }, - "error": { - "dev_multi_type": "V\u00edce vybran\u00fdch za\u0159\u00edzen\u00ed k nastaven\u00ed mus\u00ed b\u00fdt stejn\u00e9ho typu", - "dev_not_config": "Typ za\u0159\u00edzen\u00ed nelze nastavit", - "dev_not_found": "Za\u0159\u00edzen\u00ed nenalezeno" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rozsah jasu pou\u017e\u00edvan\u00fd za\u0159\u00edzen\u00edm", - "max_kelvin": "Maxim\u00e1ln\u00ed podporovan\u00e1 teplota barev v kelvinech", - "max_temp": "Maxim\u00e1ln\u00ed c\u00edlov\u00e1 teplota (pou\u017eijte min a max = 0 jako v\u00fdchoz\u00ed)", - "min_kelvin": "Maxim\u00e1ln\u00ed podporovan\u00e1 teplota barev v kelvinech", - "min_temp": "Minim\u00e1ln\u00ed c\u00edlov\u00e1 teplota (pou\u017eijte min a max = 0 jako v\u00fdchoz\u00ed)", - "support_color": "Vynutit podporu barev", - "tuya_max_coltemp": "Maxim\u00e1ln\u00ed teplota barev nahl\u00e1\u0161en\u00e1 za\u0159\u00edzen\u00edm", - "unit_of_measurement": "Jednotka teploty pou\u017e\u00edvan\u00e1 za\u0159\u00edzen\u00edm" - }, - "title": "Nastavte za\u0159\u00edzen\u00ed Tuya" - }, - "init": { - "data": { - "discovery_interval": "Interval objevov\u00e1n\u00ed za\u0159\u00edzen\u00ed v sekund\u00e1ch", - "list_devices": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e1 chcete nastavit, nebo ponechte pr\u00e1zdn\u00e9, abyste konfiguraci ulo\u017eili", - "query_device": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e9 bude pou\u017e\u00edvat metodu dotaz\u016f pro rychlej\u0161\u00ed aktualizaci stavu", - "query_interval": "Interval dotazov\u00e1n\u00ed za\u0159\u00edzen\u00ed v sekund\u00e1ch" - }, - "description": "Nenastavujte intervalu dotazov\u00e1n\u00ed p\u0159\u00edli\u0161 n\u00edzk\u00e9 hodnoty, jinak se dotazov\u00e1n\u00ed nezda\u0159\u00ed a bude generovat chybov\u00e9 zpr\u00e1vy do logu", - "title": "Nastavte mo\u017enosti Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index 289d2661485ef8..fa591c2bcc9ce6 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -1,65 +1,34 @@ { "config": { "abort": { - "cannot_connect": "Verbindung fehlgeschlagen", - "invalid_auth": "Ung\u00fcltige Authentifizierung", - "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + "cannot_connect": "Verbinden fehlgeschlagen", + "invalid_auth": "Ungültige Authentifizierung", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration möglich." }, "error": { - "invalid_auth": "Ung\u00fcltige Authentifizierung" + "invalid_auth": "Ungültige Authentifizierung" }, "flow_title": "Tuya Konfiguration", "step": { + "project_type":{ + "title":"Tuya Integration", + "data":{ + "tuya_project_type": "Tuya Cloud Projekttyp" + } + }, "user": { "data": { - "country_code": "L\u00e4ndercode Ihres Kontos (z. B. 1 f\u00fcr USA oder 86 f\u00fcr China)", - "password": "Passwort", - "platform": "Die App, in der Ihr Konto registriert ist", - "username": "Benutzername" + "endpoint": "Verfügbarkeitszone", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "Mobile App", + "country_code": "Länder Code", + "username": "Benutzername", + "password": "Passwort" }, - "description": "Gib deine Tuya-Anmeldeinformationen ein.", + "description": "Geben Sie Ihre Tuya Anmeldedaten ein.", "title": "Tuya" } } - }, - "options": { - "abort": { - "cannot_connect": "Verbindung fehlgeschlagen" - }, - "error": { - "dev_multi_type": "Mehrere ausgew\u00e4hlte Ger\u00e4te zur Konfiguration m\u00fcssen vom gleichen Typ sein", - "dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar", - "dev_not_found": "Ger\u00e4t nicht gefunden" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Vom Ger\u00e4t genutzter Helligkeitsbereich", - "curr_temp_divider": "Aktueller Temperaturwert-Teiler (0 = Standard verwenden)", - "max_kelvin": "Maximal unterst\u00fctzte Farbtemperatur in Kelvin", - "max_temp": "Maximale Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", - "min_kelvin": "Minimale unterst\u00fctzte Farbtemperatur in Kelvin", - "min_temp": "Minimal Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", - "set_temp_divided": "Geteilten Temperaturwert f\u00fcr Solltemperaturbefehl verwenden", - "support_color": "Farbunterst\u00fctzung erzwingen", - "temp_divider": "Teiler f\u00fcr Temperaturwerte (0 = Standard verwenden)", - "temp_step_override": "Zieltemperaturschritt", - "tuya_max_coltemp": "Vom Ger\u00e4t gemeldete maximale Farbtemperatur", - "unit_of_measurement": "Vom Ger\u00e4t verwendete Temperatureinheit" - }, - "description": "Optionen zur Anpassung der angezeigten Informationen f\u00fcr das Ger\u00e4t `{device_name}` vom Typ: {device_type}konfigurieren", - "title": "Tuya-Ger\u00e4t konfigurieren" - }, - "init": { - "data": { - "discovery_interval": "Abfrageintervall f\u00fcr Ger\u00e4teabruf in Sekunden", - "list_devices": "W\u00e4hlen Sie die zu konfigurierenden Ger\u00e4te aus oder lassen Sie sie leer, um die Konfiguration zu speichern", - "query_device": "W\u00e4hlen Sie ein Ger\u00e4t aus, das die Abfragemethode f\u00fcr eine schnellere Statusaktualisierung verwendet.", - "query_interval": "Ger\u00e4teabrufintervall in Sekunden" - }, - "description": "Stellen Sie das Abfrageintervall nicht zu niedrig ein, sonst schlagen die Aufrufe fehl und erzeugen eine Fehlermeldung im Protokoll", - "title": "Tuya-Optionen konfigurieren" - } - } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/en.json b/homeassistant/components/tuya/translations/en.json index ee304ff30cd4e1..42c2572f36f723 100644 --- a/homeassistant/components/tuya/translations/en.json +++ b/homeassistant/components/tuya/translations/en.json @@ -10,56 +10,25 @@ }, "flow_title": "Tuya configuration", "step": { + "project_type":{ + "title":"Tuya Integration", + "data":{ + "tuya_project_type": "Tuya cloud project type" + } + }, "user": { "data": { - "country_code": "Your account country code (e.g., 1 for USA or 86 for China)", - "password": "Password", - "platform": "The app where your account is registered", - "username": "Username" + "endpoint": "Availability Zone", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "Mobile App", + "country_code": "Country Code", + "username": "Account", + "password": "Password" }, - "description": "Enter your Tuya credentials.", + "description": "Enter your Tuya credential.", "title": "Tuya" } } - }, - "options": { - "abort": { - "cannot_connect": "Failed to connect" - }, - "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": { - "device": { - "data": { - "brightness_range_mode": "Brightness range used by device", - "curr_temp_divider": "Current Temperature value divider (0 = use default)", - "max_kelvin": "Max color temperature supported in kelvin", - "max_temp": "Max target temperature (use min and max = 0 for default)", - "min_kelvin": "Min color temperature supported in kelvin", - "min_temp": "Min target temperature (use min and max = 0 for default)", - "set_temp_divided": "Use divided Temperature value for set temperature command", - "support_color": "Force color support", - "temp_divider": "Temperature values divider (0 = use default)", - "temp_step_override": "Target Temperature step", - "tuya_max_coltemp": "Max color temperature reported by device", - "unit_of_measurement": "Temperature unit used by device" - }, - "description": "Configure options to adjust displayed information for {device_type} device `{device_name}`", - "title": "Configure Tuya Device" - }, - "init": { - "data": { - "discovery_interval": "Discovery device polling interval in seconds", - "list_devices": "Select the devices to configure or leave empty to save configuration", - "query_device": "Select device that will use query method for faster status update", - "query_interval": "Query device polling interval in seconds" - }, - "description": "Do not set pollings interval values too low or the calls will fail generating error message in the log", - "title": "Configure Tuya Options" - } - } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/es.json b/homeassistant/components/tuya/translations/es.json index 9c57a2168880b2..e7342938ffe49f 100644 --- a/homeassistant/components/tuya/translations/es.json +++ b/homeassistant/components/tuya/translations/es.json @@ -2,64 +2,33 @@ "config": { "abort": { "cannot_connect": "No se pudo conectar", - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", - "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." + "invalid_auth": "Autenticación inválida", + "single_instance_allowed": "Ya esta configurado. Solo es posible una única configuración." }, "error": { - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + "invalid_auth": "Autenticación inválida" }, - "flow_title": "Configuraci\u00f3n Tuya", + "flow_title": "Configuración de Tuya", "step": { + "project_type":{ + "title":"Integración Tuya", + "data":{ + "tuya_project_type": "Tipo de cloud Tuya" + } + }, "user": { "data": { - "country_code": "C\u00f3digo de pais de tu cuenta (por ejemplo, 1 para USA o 86 para China)", - "password": "Contrase\u00f1a", - "platform": "La aplicaci\u00f3n en la cual registraste tu cuenta", - "username": "Usuario" + "endpoint": "Zona de disponibilidad", + "access_id": "ID de acceso", + "access_secret": "Secreto", + "tuya_app_type": "Aplicación movil", + "country_code": "Código de país", + "username": "Cuenta", + "password": "Contraseña" }, - "description": "Introduce tu credencial Tuya.", + "description": "Ingrese su credencial Tuya.", "title": "Tuya" } } - }, - "options": { - "abort": { - "cannot_connect": "No se pudo conectar" - }, - "error": { - "dev_multi_type": "Los m\u00faltiples dispositivos seleccionados para configurar deben ser del mismo tipo", - "dev_not_config": "Tipo de dispositivo no configurable", - "dev_not_found": "Dispositivo no encontrado" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rango de brillo utilizado por el dispositivo", - "curr_temp_divider": "Divisor del valor de la temperatura actual (0 = usar valor por defecto)", - "max_kelvin": "Temperatura de color m\u00e1xima admitida en kelvin", - "max_temp": "Temperatura objetivo m\u00e1xima (usa m\u00edn. y m\u00e1x. = 0 por defecto)", - "min_kelvin": "Temperatura de color m\u00ednima soportada en kelvin", - "min_temp": "Temperatura objetivo m\u00ednima (usa m\u00edn. y m\u00e1x. = 0 por defecto)", - "set_temp_divided": "Use el valor de temperatura dividido para el comando de temperatura establecida", - "support_color": "Forzar soporte de color", - "temp_divider": "Divisor de los valores de temperatura (0 = usar valor por defecto)", - "temp_step_override": "Temperatura deseada", - "tuya_max_coltemp": "Temperatura de color m\u00e1xima notificada por dispositivo", - "unit_of_measurement": "Unidad de temperatura utilizada por el dispositivo" - }, - "description": "Configura las opciones para ajustar la informaci\u00f3n mostrada para {device_type} dispositivo `{device_name}`", - "title": "Configurar dispositivo Tuya" - }, - "init": { - "data": { - "discovery_interval": "Intervalo de sondeo del descubrimiento al dispositivo en segundos", - "list_devices": "Selecciona los dispositivos a configurar o d\u00e9jalos en blanco para guardar la configuraci\u00f3n", - "query_device": "Selecciona el dispositivo que utilizar\u00e1 el m\u00e9todo de consulta para una actualizaci\u00f3n de estado m\u00e1s r\u00e1pida", - "query_interval": "Intervalo de sondeo de la consulta al dispositivo en segundos" - }, - "description": "No establezcas valores de intervalo de sondeo demasiado bajos o las llamadas fallar\u00e1n generando un mensaje de error en el registro", - "title": "Configurar opciones de Tuya" - } - } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/et.json b/homeassistant/components/tuya/translations/et.json deleted file mode 100644 index 48161f552b8284..00000000000000 --- a/homeassistant/components/tuya/translations/et.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "\u00dchendamine nurjus", - "invalid_auth": "Tuvastamise viga", - "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks sidumine." - }, - "error": { - "invalid_auth": "Tuvastamise viga" - }, - "flow_title": "Tuya seaded", - "step": { - "user": { - "data": { - "country_code": "Konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", - "password": "Salas\u00f5na", - "platform": "\u00c4pp kus konto registreeriti", - "username": "Kasutajanimi" - }, - "description": "Sisesta oma Tuya konto andmed.", - "title": "" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u00dchendamine nurjus" - }, - "error": { - "dev_multi_type": "Mitu h\u00e4\u00e4lestatavat seadet peavad olema sama t\u00fc\u00fcpi", - "dev_not_config": "Seda t\u00fc\u00fcpi seade pole seadistatav", - "dev_not_found": "Seadet ei leitud" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Seadme kasutatav heledusvahemik", - "curr_temp_divider": "Praeguse temperatuuri v\u00e4\u00e4rtuse eraldaja (0 = kasuta vaikev\u00e4\u00e4rtust)", - "max_kelvin": "Maksimaalne v\u00f5imalik v\u00e4rvitemperatuur (Kelvinites)", - "max_temp": "Maksimaalne sihttemperatuur (vaikimisi kasuta min ja max = 0)", - "min_kelvin": "Minimaalne v\u00f5imalik v\u00e4rvitemperatuur (Kelvinites)", - "min_temp": "Minimaalne sihttemperatuur (vaikimisi kasuta min ja max = 0)", - "set_temp_divided": "M\u00e4\u00e4ratud temperatuuri k\u00e4su jaoks kasuta jagatud temperatuuri v\u00e4\u00e4rtust", - "support_color": "Luba v\u00e4rvuse juhtimine", - "temp_divider": "Temperatuuri v\u00e4\u00e4rtuse eraldaja (0 = kasuta vaikev\u00e4\u00e4rtust)", - "temp_step_override": "Sihttemperatuuri samm", - "tuya_max_coltemp": "Seadme teatatud maksimaalne v\u00e4rvitemperatuur", - "unit_of_measurement": "Seadme temperatuuri\u00fchik" - }, - "description": "Suvandid \u00fcksuse {device_type} {device_name} kuvatava teabe muutmiseks", - "title": "H\u00e4\u00e4lesta Tuya seade" - }, - "init": { - "data": { - "discovery_interval": "Seadme leidmisp\u00e4ringute intervall (sekundites)", - "list_devices": "Vali seadistatavad seadmed v\u00f5i j\u00e4ta s\u00e4tete salvestamiseks t\u00fchjaks", - "query_device": "Vali seade, mis kasutab oleku kiiremaks v\u00e4rskendamiseks p\u00e4ringumeetodit", - "query_interval": "P\u00e4ringute intervall (sekundites)" - }, - "description": "\u00c4ra m\u00e4\u00e4ra k\u00fcsitlusintervalli v\u00e4\u00e4rtusi liiga madalaks, vastasel korral v\u00f5ivad p\u00e4ringud logis t\u00f5rketeate genereerida", - "title": "Tuya suvandite seadistamine" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/fi.json b/homeassistant/components/tuya/translations/fi.json deleted file mode 100644 index 3c74a9b8eebecd..00000000000000 --- a/homeassistant/components/tuya/translations/fi.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "flow_title": "Tuya-asetukset", - "step": { - "user": { - "data": { - "country_code": "Tilisi maakoodi (esim. 1 Yhdysvalloissa, 358 Suomessa)", - "password": "Salasana", - "platform": "Sovellus, johon tili rekister\u00f6id\u00e4\u00e4n", - "username": "K\u00e4ytt\u00e4j\u00e4tunnus" - }, - "description": "Anna Tuya-tunnistetietosi.", - "title": "Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/fr.json b/homeassistant/components/tuya/translations/fr.json index 1681343f3b756c..0eae137b69a9aa 100644 --- a/homeassistant/components/tuya/translations/fr.json +++ b/homeassistant/components/tuya/translations/fr.json @@ -1,65 +1,34 @@ { "config": { "abort": { - "cannot_connect": "\u00c9chec de connexion", + "cannot_connect": "Impossible de se connecter", "invalid_auth": "Authentification invalide", - "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + "single_instance_allowed": "Configuration déjà effectuée. Une seule configuration est possible." }, "error": { "invalid_auth": "Authentification invalide" }, - "flow_title": "Configuration Tuya", + "flow_title": "Configuration de Tuya", "step": { + "project_type":{ + "title":"Intégration Tuya", + "data":{ + "tuya_project_type": "Type de projet cloud Tuya" + } + }, "user": { "data": { - "country_code": "Le code de pays de votre compte (par exemple, 1 pour les \u00c9tats-Unis ou 86 pour la Chine)", - "password": "Mot de passe", - "platform": "L'application dans laquelle votre compte est enregistr\u00e9", - "username": "Nom d'utilisateur" + "endpoint": "Région", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "App Mobile", + "country_code": "Code Pays", + "username": "Compte", + "password": "Mot de passe" }, - "description": "Saisissez vos informations d'identification Tuya.", + "description": "Saisissez vos identifiants Tuya.", "title": "Tuya" } } - }, - "options": { - "abort": { - "cannot_connect": "Impossible de se connecter" - }, - "error": { - "dev_multi_type": "Plusieurs p\u00e9riph\u00e9riques s\u00e9lectionn\u00e9s \u00e0 configurer doivent \u00eatre du m\u00eame type", - "dev_not_config": "Type d'appareil non configurable", - "dev_not_found": "Appareil non trouv\u00e9" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Plage de luminosit\u00e9 utilis\u00e9e par l'appareil", - "curr_temp_divider": "Diviseur de valeur de temp\u00e9rature actuelle (0 = utiliser la valeur par d\u00e9faut)", - "max_kelvin": "Temp\u00e9rature de couleur maximale prise en charge en Kelvin", - "max_temp": "Temp\u00e9rature cible maximale (utilisez min et max = 0 par d\u00e9faut)", - "min_kelvin": "Temp\u00e9rature de couleur minimale prise en charge en kelvin", - "min_temp": "Temp\u00e9rature cible minimale (utilisez min et max = 0 par d\u00e9faut)", - "set_temp_divided": "Utilisez la valeur de temp\u00e9rature divis\u00e9e pour la commande de temp\u00e9rature d\u00e9finie", - "support_color": "Forcer la prise en charge des couleurs", - "temp_divider": "Diviseur de valeurs de temp\u00e9rature (0 = utiliser la valeur par d\u00e9faut)", - "temp_step_override": "Pas de temp\u00e9rature cible", - "tuya_max_coltemp": "Temp\u00e9rature de couleur maximale rapport\u00e9e par l'appareil", - "unit_of_measurement": "Unit\u00e9 de temp\u00e9rature utilis\u00e9e par l'appareil" - }, - "description": "Configurer les options pour ajuster les informations affich\u00e9es pour l'appareil {device_type} ` {device_name} `", - "title": "Configurer l'appareil Tuya" - }, - "init": { - "data": { - "discovery_interval": "Intervalle de d\u00e9couverte de l'appareil en secondes", - "list_devices": "S\u00e9lectionnez les appareils \u00e0 configurer ou laissez vide pour enregistrer la configuration", - "query_device": "S\u00e9lectionnez l'appareil qui utilisera la m\u00e9thode de requ\u00eate pour une mise \u00e0 jour plus rapide de l'\u00e9tat", - "query_interval": "Intervalle d'interrogation de l'appareil en secondes" - }, - "description": "Ne d\u00e9finissez pas des valeurs d'intervalle d'interrogation trop faibles ou les appels \u00e9choueront \u00e0 g\u00e9n\u00e9rer un message d'erreur dans le journal", - "title": "Configurer les options de Tuya" - } - } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/he.json b/homeassistant/components/tuya/translations/he.json deleted file mode 100644 index 0a05bec6b21a4e..00000000000000 --- a/homeassistant/components/tuya/translations/he.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", - "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", - "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." - }, - "error": { - "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" - }, - "flow_title": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d8\u05d5\u05d9\u05d4", - "step": { - "user": { - "data": { - "country_code": "\u05e7\u05d5\u05d3 \u05de\u05d3\u05d9\u05e0\u05d4 \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05e9\u05dc\u05da (\u05dc\u05de\u05e9\u05dc, 1 \u05dc\u05d0\u05e8\u05d4\"\u05d1 \u05d0\u05d5 972 \u05dc\u05d9\u05e9\u05e8\u05d0\u05dc)", - "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "platform": "\u05d4\u05d0\u05e4\u05dc\u05d9\u05e7\u05e6\u05d9\u05d4 \u05e9\u05d1\u05d4 \u05e8\u05e9\u05d5\u05dd \u05d7\u05e9\u05d1\u05d5\u05e0\u05da", - "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" - }, - "description": "\u05d4\u05d6\u05df \u05d0\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8\u05d9 \u05d8\u05d5\u05d9\u05d4 \u05e9\u05dc\u05da.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/hu.json b/homeassistant/components/tuya/translations/hu.json deleted file mode 100644 index b45148f80b8d3d..00000000000000 --- a/homeassistant/components/tuya/translations/hu.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." - }, - "error": { - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" - }, - "flow_title": "Tuya konfigur\u00e1ci\u00f3", - "step": { - "user": { - "data": { - "country_code": "A fi\u00f3k orsz\u00e1gk\u00f3dja (pl. 1 USA, 36 Magyarorsz\u00e1g, vagy 86 K\u00edna)", - "password": "Jelsz\u00f3", - "platform": "Az alkalmaz\u00e1s, ahol a fi\u00f3k regisztr\u00e1lt", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - }, - "description": "Adja meg Tuya hiteles\u00edt\u0151 adatait.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "A kapcsol\u00f3d\u00e1s nem siker\u00fclt" - }, - "error": { - "dev_multi_type": "T\u00f6bb kiv\u00e1lasztott konfigur\u00e1land\u00f3 eszk\u00f6znek azonos t\u00edpus\u00fanak kell lennie", - "dev_not_config": "Ez az eszk\u00f6zt\u00edpus nem konfigur\u00e1lhat\u00f3", - "dev_not_found": "Eszk\u00f6z nem tal\u00e1lhat\u00f3" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Az eszk\u00f6z \u00e1ltal haszn\u00e1lt f\u00e9nyer\u0151 tartom\u00e1ny", - "curr_temp_divider": "Aktu\u00e1lis h\u0151m\u00e9rs\u00e9klet-\u00e9rt\u00e9k oszt\u00f3 (0 = alap\u00e9rtelmezetten)", - "max_kelvin": "Maxim\u00e1lis t\u00e1mogatott sz\u00ednh\u0151m\u00e9rs\u00e9klet kelvinben", - "max_temp": "Maxim\u00e1lis c\u00e9l-sz\u00ednh\u0151m\u00e9rs\u00e9klet (haszn\u00e1lja a min-t \u00e9s a max-ot = 0-t az alap\u00e9rtelmezett be\u00e1ll\u00edt\u00e1shoz)", - "min_kelvin": "Minimum t\u00e1mogatott sz\u00ednh\u0151m\u00e9rs\u00e9klet kelvinben", - "min_temp": "Min. C\u00e9l-sz\u00ednh\u0151m\u00e9rs\u00e9klet (alap\u00e9rtelmez\u00e9s szerint haszn\u00e1ljon min-t \u00e9s max-ot = 0-t az alap\u00e9rtelmezett be\u00e1ll\u00edt\u00e1shoz)", - "support_color": "Sz\u00ednt\u00e1mogat\u00e1s k\u00e9nyszer\u00edt\u00e9se", - "temp_divider": "Sz\u00ednh\u0151m\u00e9rs\u00e9klet-\u00e9rt\u00e9kek oszt\u00f3ja (0 = alap\u00e9rtelmezett)", - "tuya_max_coltemp": "Az eszk\u00f6z \u00e1ltal megadott maxim\u00e1lis sz\u00ednh\u0151m\u00e9rs\u00e9klet", - "unit_of_measurement": "Az eszk\u00f6z \u00e1ltal haszn\u00e1lt h\u0151m\u00e9rs\u00e9kleti egys\u00e9g" - }, - "description": "Konfigur\u00e1l\u00e1si lehet\u0151s\u00e9gek a(z) {device_type} t\u00edpus\u00fa `{device_name}` eszk\u00f6z megjelen\u00edtett inform\u00e1ci\u00f3inak be\u00e1ll\u00edt\u00e1s\u00e1hoz", - "title": "Tuya eszk\u00f6z konfigur\u00e1l\u00e1sa" - }, - "init": { - "data": { - "discovery_interval": "Felfedez\u0151 eszk\u00f6z lek\u00e9rdez\u00e9si intervalluma m\u00e1sodpercben", - "list_devices": "V\u00e1laszd ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6z\u00f6ket, vagy hagyd \u00fcresen a konfigur\u00e1ci\u00f3 ment\u00e9s\u00e9hez", - "query_device": "V\u00e1laszd ki azt az eszk\u00f6zt, amely a lek\u00e9rdez\u00e9si m\u00f3dszert haszn\u00e1lja a gyorsabb \u00e1llapotfriss\u00edt\u00e9shez", - "query_interval": "Eszk\u00f6z lek\u00e9rdez\u00e9si id\u0151k\u00f6ze m\u00e1sodpercben" - }, - "description": "Ne \u00e1ll\u00edtsd t\u00fal alacsonyra a lek\u00e9rdez\u00e9si intervallum \u00e9rt\u00e9keit, k\u00fcl\u00f6nben a h\u00edv\u00e1sok nem fognak hiba\u00fczenetet gener\u00e1lni a napl\u00f3ban", - "title": "Tuya be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/id.json b/homeassistant/components/tuya/translations/id.json index bb338e12752879..193a865e1cd36f 100644 --- a/homeassistant/components/tuya/translations/id.json +++ b/homeassistant/components/tuya/translations/id.json @@ -1,65 +1,34 @@ { "config": { "abort": { - "cannot_connect": "Gagal terhubung", - "invalid_auth": "Autentikasi tidak valid", - "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + "cannot_connect": "Koneksi Gagal", + "invalid_auth": "Otentikasi Salah", + "single_instance_allowed": "Sudah terkonfigurasi. Anda hanya bisa melakukan satu konfigurasi saja." }, "error": { - "invalid_auth": "Autentikasi tidak valid" + "invalid_auth": "Otentikasi Salah" }, "flow_title": "Konfigurasi Tuya", "step": { + "project_type":{ + "title":"Integrasi Tuya", + "data":{ + "tuya_project_type": "Tipe dari Projek Tuya cloud" + } + }, "user": { "data": { - "country_code": "Kode negara akun Anda (mis., 1 untuk AS atau 86 untuk China)", - "password": "Kata Sandi", - "platform": "Aplikasi tempat akun Anda mendaftar", - "username": "Nama Pengguna" + "endpoint": "Zona yang Tersedia", + "access_id": "Akses ID", + "access_secret": "Akses Rahasia", + "tuya_app_type": "Aplikasi Mobil", + "country_code": "Kode Negara", + "username": "Akun", + "password": "Password" }, - "description": "Masukkan kredensial Tuya Anda.", + "description": "Masukkan kredensial dari Tuya Anda.", "title": "Tuya" } } - }, - "options": { - "abort": { - "cannot_connect": "Gagal terhubung" - }, - "error": { - "dev_multi_type": "Untuk konfigurasi sekaligus, beberapa perangkat yang dipilih harus berjenis sama", - "dev_not_config": "Jenis perangkat tidak dapat dikonfigurasi", - "dev_not_found": "Perangkat tidak ditemukan" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rentang kecerahan yang digunakan oleh perangkat", - "curr_temp_divider": "Pembagi nilai suhu saat ini (0 = gunakan bawaan)", - "max_kelvin": "Suhu warna maksimal yang didukung dalam Kelvin", - "max_temp": "Suhu target maksimal (gunakan min dan maks = 0 untuk bawaan)", - "min_kelvin": "Suhu warna minimal yang didukung dalam Kelvin", - "min_temp": "Suhu target minimal (gunakan min dan maks = 0 untuk bawaan)", - "set_temp_divided": "Gunakan nilai suhu terbagi untuk mengirimkan perintah mengatur suhu", - "support_color": "Paksa dukungan warna", - "temp_divider": "Pembagi nilai suhu (0 = gunakan bawaan)", - "temp_step_override": "Langkah Suhu Target", - "tuya_max_coltemp": "Suhu warna maksimal yang dilaporkan oleh perangkat", - "unit_of_measurement": "Satuan suhu yang digunakan oleh perangkat" - }, - "description": "Konfigurasikan opsi untuk menyesuaikan informasi yang ditampilkan untuk perangkat {device_type} `{device_name}`", - "title": "Konfigurasi Perangkat Tuya" - }, - "init": { - "data": { - "discovery_interval": "Interval polling penemuan perangkat dalam detik", - "list_devices": "Pilih perangkat yang akan dikonfigurasi atau biarkan kosong untuk menyimpan konfigurasi", - "query_device": "Pilih perangkat yang akan menggunakan metode kueri untuk pembaruan status lebih cepat", - "query_interval": "Interval polling perangkat kueri dalam detik" - }, - "description": "Jangan atur nilai interval polling terlalu rendah karena panggilan akan gagal menghasilkan pesan kesalahan dalam log", - "title": "Konfigurasikan Opsi Tuya" - } - } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/it.json b/homeassistant/components/tuya/translations/it.json deleted file mode 100644 index a2a8dc874732ef..00000000000000 --- a/homeassistant/components/tuya/translations/it.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Impossibile connettersi", - "invalid_auth": "Autenticazione non valida", - "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." - }, - "error": { - "invalid_auth": "Autenticazione non valida" - }, - "flow_title": "Configurazione di Tuya", - "step": { - "user": { - "data": { - "country_code": "Prefisso internazionale del tuo account (ad es. 1 per gli Stati Uniti o 86 per la Cina)", - "password": "Password", - "platform": "L'app in cui \u00e8 registrato il tuo account", - "username": "Nome utente" - }, - "description": "Inserisci le tue credenziali Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Impossibile connettersi" - }, - "error": { - "dev_multi_type": "Pi\u00f9 dispositivi selezionati da configurare devono essere dello stesso tipo", - "dev_not_config": "Tipo di dispositivo non configurabile", - "dev_not_found": "Dispositivo non trovato" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Intervallo di luminosit\u00e0 utilizzato dal dispositivo", - "curr_temp_divider": "Divisore del valore della temperatura corrente (0 = usa il valore predefinito)", - "max_kelvin": "Temperatura colore massima supportata in kelvin", - "max_temp": "Temperatura di destinazione massima (utilizzare min e max = 0 per impostazione predefinita)", - "min_kelvin": "Temperatura colore minima supportata in kelvin", - "min_temp": "Temperatura di destinazione minima (utilizzare min e max = 0 per impostazione predefinita)", - "set_temp_divided": "Utilizzare il valore temperatura diviso per impostare il comando temperatura", - "support_color": "Forza il supporto del colore", - "temp_divider": "Divisore dei valori di temperatura (0 = utilizzare il valore predefinito)", - "temp_step_override": "Passo della temperatura da raggiungere", - "tuya_max_coltemp": "Temperatura di colore massima riportata dal dispositivo", - "unit_of_measurement": "Unit\u00e0 di temperatura utilizzata dal dispositivo" - }, - "description": "Configura le opzioni per regolare le informazioni visualizzate per il dispositivo {device_type} `{device_name}`", - "title": "Configura il dispositivo Tuya" - }, - "init": { - "data": { - "discovery_interval": "Intervallo di scansione di rilevamento dispositivo in secondi", - "list_devices": "Selezionare i dispositivi da configurare o lasciare vuoto per salvare la configurazione", - "query_device": "Selezionare il dispositivo che utilizzer\u00e0 il metodo di interrogazione per un pi\u00f9 rapido aggiornamento dello stato", - "query_interval": "Intervallo di scansione di interrogazione dispositivo in secondi" - }, - "description": "Non impostare valori dell'intervallo di scansione troppo bassi o le chiamate non riusciranno a generare un messaggio di errore nel registro", - "title": "Configura le opzioni Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ka.json b/homeassistant/components/tuya/translations/ka.json deleted file mode 100644 index 7c80ef1ffbac08..00000000000000 --- a/homeassistant/components/tuya/translations/ka.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "options": { - "error": { - "dev_multi_type": "\u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d0\u10ea\u10d8\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1 \u10e8\u10d4\u10e0\u10e9\u10d4\u10e3\u10da\u10d8 \u10db\u10e0\u10d0\u10d5\u10da\u10dd\u10d1\u10d8\u10d7\u10d8 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0 \u10e3\u10dc\u10d3\u10d0 \u10d8\u10e7\u10dd\u10e1 \u10d4\u10e0\u10d7\u10dc\u10d0\u10d8\u10e0\u10d8 \u10e2\u10d8\u10de\u10d8\u10e1", - "dev_not_config": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10e2\u10d8\u10de\u10d8 \u10d0\u10e0 \u10d0\u10e0\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0\u10d3\u10d8", - "dev_not_found": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0 \u10d5\u10d4\u10e0 \u10db\u10dd\u10d8\u10eb\u10d4\u10d1\u10dc\u10d0" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10db\u10d8\u10d4\u10e0 \u10d2\u10d0\u10db\u10dd\u10e7\u10d4\u10dc\u10d4\u10d1\u10e3\u10da\u10d8 \u10e1\u10d8\u10d9\u10d0\u10e8\u10d9\u10d0\u10e8\u10d8\u10e1 \u10d3\u10d8\u10d0\u10de\u10d0\u10d6\u10dd\u10dc\u10d8", - "curr_temp_divider": "\u10db\u10d8\u10db\u10d3\u10d8\u10dc\u10d0\u10e0\u10d4 \u10e2\u10d4\u10db\u10d4\u10de\u10e0\u10d0\u10e2\u10e3\u10e0\u10d8\u10e1 \u10db\u10dc\u10d8\u10e8\u10d5\u10dc\u10d4\u10da\u10d8\u10e1 \u10d2\u10d0\u10db\u10e7\u10dd\u10e4\u10d8 (0 - \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8)", - "max_kelvin": "\u10db\u10ee\u10d0\u10e0\u10d3\u10d0\u10ed\u10d4\u10e0\u10d8\u10da\u10d8 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d8\u10e1 \u10e4\u10d4\u10e0\u10d8 \u10d9\u10d4\u10da\u10d5\u10d8\u10dc\u10d4\u10d1\u10e8\u10d8", - "max_temp": "\u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10db\u10d8\u10d6\u10dc\u10dd\u10d1\u10e0\u10d8\u10d5\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d0 (\u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10d3\u10d0 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8\u10e1\u10d0\u10d7\u10d5\u10d8\u10e1 \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8\u10d0 0)", - "min_kelvin": "\u10db\u10ee\u10d0\u10e0\u10d3\u10d0\u10ed\u10d4\u10e0\u10d8\u10da\u10d8 \u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d8\u10e1 \u10e4\u10d4\u10e0\u10d8 \u10d9\u10d4\u10da\u10d5\u10d8\u10dc\u10d4\u10d1\u10e8\u10d8", - "min_temp": "\u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10db\u10d8\u10d6\u10dc\u10dd\u10d1\u10e0\u10d8\u10d5\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d0 (\u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10d3\u10d0 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8\u10e1\u10d0\u10d7\u10d5\u10d8\u10e1 \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8\u10d0 0)", - "support_color": "\u10e4\u10d4\u10e0\u10d8\u10e1 \u10db\u10ee\u10d0\u10e0\u10d3\u10d0\u10ed\u10d4\u10e0\u10d0 \u10d8\u10eb\u10e3\u10da\u10d4\u10d1\u10d8\u10d7", - "temp_divider": "\u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10e3\u10da\u10d8 \u10db\u10dc\u10d8\u10e8\u10d5\u10dc\u10d4\u10da\u10d8\u10e1 \u10d2\u10d0\u10db\u10e7\u10dd\u10e4\u10d8 (0 = \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8)", - "tuya_max_coltemp": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10db\u10d8\u10d4\u10e0 \u10db\u10dd\u10ec\u10dd\u10d3\u10d4\u10d1\u10e3\u10da\u10d8 \u10e4\u10d4\u10e0\u10d8\u10e1 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d0", - "unit_of_measurement": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10db\u10d8\u10d4\u10e0 \u10d2\u10d0\u10db\u10dd\u10e7\u10d4\u10dc\u10d4\u10d1\u10e3\u10da\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10e3\u10da\u10d8 \u10d4\u10e0\u10d7\u10d4\u10e3\u10da\u10d8" - }, - "description": "\u10d3\u10d0\u10d0\u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d3 {device_type} `{device_name}` \u10de\u10d0\u10e0\u10d0\u10db\u10d4\u10e2\u10d4\u10e0\u10d1\u10d8 \u10d8\u10dc\u10e4\u10dd\u10e0\u10db\u10d0\u10ea\u10d8\u10d8\u10e1 \u10e9\u10d5\u10d4\u10dc\u10d4\u10d1\u10d8\u10e1 \u10db\u10dd\u10e1\u10d0\u10e0\u10d2\u10d4\u10d1\u10d0\u10d3", - "title": "Tuya-\u10e1 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0" - }, - "init": { - "data": { - "discovery_interval": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10d0\u10e6\u10db\u10dd\u10e9\u10d4\u10dc\u10d8\u10e1 \u10d2\u10d0\u10db\u10dd\u10d9\u10d8\u10d7\u10ee\u10d5\u10d8\u10e1 \u10d8\u10dc\u10e2\u10d4\u10e0\u10d5\u10d0\u10da\u10d8 \u10ec\u10d0\u10db\u10d4\u10d1\u10e8\u10d8", - "list_devices": "\u10d0\u10d8\u10e0\u10e9\u10d8\u10d4\u10d7 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d0\u10ea\u10d8\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1 \u10d0\u10dc \u10d3\u10d0\u10e2\u10dd\u10d5\u10d4\u10d7 \u10ea\u10d0\u10e0\u10d8\u10d4\u10da\u10d8 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d0\u10ea\u10d8\u10d8\u10e1 \u10e8\u10d4\u10e1\u10d0\u10dc\u10d0\u10ee\u10d0\u10d3", - "query_device": "\u10d0\u10d8\u10e0\u10e9\u10d8\u10d4\u10d7 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0, \u10e0\u10dd\u10db\u10d4\u10da\u10d8\u10ea \u10d2\u10d0\u10db\u10dd\u10d8\u10e7\u10d4\u10dc\u10d4\u10d1\u10e1 \u10db\u10dd\u10d7\u10ee\u10dd\u10d5\u10dc\u10d8\u10e1 \u10db\u10d4\u10d7\u10dd\u10d3\u10e1 \u10e1\u10e2\u10d0\u10e2\u10e3\u10e1\u10d8\u10e1 \u10e1\u10ec\u10e0\u10d0\u10e4\u10d8 \u10d2\u10d0\u10dc\u10d0\u10ee\u10da\u10d4\u10d1\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1", - "query_interval": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10d2\u10d0\u10db\u10dd\u10d9\u10d8\u10d7\u10ee\u10d5\u10d8\u10e1 \u10db\u10dd\u10d7\u10ee\u10dd\u10d5\u10dc\u10d8\u10e1 \u10d8\u10dc\u10e2\u10d4\u10e0\u10d5\u10d0\u10da\u10d8 \u10ec\u10d0\u10db\u10d4\u10d1\u10e8\u10d8" - }, - "description": "\u10d0\u10e0 \u10d3\u10d0\u10d0\u10e7\u10d4\u10dc\u10dd\u10d7 \u10d2\u10d0\u10db\u10dd\u10d9\u10d8\u10d7\u10ee\u10d5\u10d8\u10e1 \u10d8\u10dc\u10e2\u10d4\u10e0\u10d5\u10d0\u10da\u10d8\u10e1 \u10db\u10dc\u10d8\u10e8\u10d5\u10dc\u10d4\u10da\u10dd\u10d1\u10d4\u10d1\u10d8 \u10eb\u10d0\u10da\u10d8\u10d0\u10dc \u10db\u10ea\u10d8\u10e0\u10d4 \u10d7\u10dd\u10e0\u10d4\u10d1 \u10d2\u10d0\u10db\u10dd\u10eb\u10d0\u10ee\u10d4\u10d1\u10d4\u10d1\u10d8 \u10d3\u10d0\u10d0\u10d2\u10d4\u10dc\u10d4\u10e0\u10d8\u10e0\u10d4\u10d1\u10d4\u10dc \u10e8\u10d4\u10ea\u10d3\u10dd\u10db\u10d4\u10d1\u10e1 \u10da\u10dd\u10d2\u10e8\u10d8", - "title": "Tuya-\u10e1 \u10de\u10d0\u10e0\u10d0\u10db\u10d4\u10e2\u10e0\u10d4\u10d1\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json deleted file mode 100644 index afa2541e7b9e0d..00000000000000 --- a/homeassistant/components/tuya/translations/ko.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." - }, - "error": { - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" - }, - "flow_title": "Tuya \uad6c\uc131\ud558\uae30", - "step": { - "user": { - "data": { - "country_code": "\uacc4\uc815 \uad6d\uac00 \ucf54\ub4dc (\uc608 : \ubbf8\uad6d\uc758 \uacbd\uc6b0 1, \uc911\uad6d\uc758 \uacbd\uc6b0 86)", - "password": "\ube44\ubc00\ubc88\ud638", - "platform": "\uacc4\uc815\uc774 \ub4f1\ub85d\ub41c \uc571", - "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - }, - "description": "Tuya \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" - }, - "error": { - "dev_multi_type": "\uc120\ud0dd\ud55c \uc5ec\ub7ec \uae30\uae30\ub97c \uad6c\uc131\ud558\ub824\uba74 \uc720\ud615\uc774 \ub3d9\uc77c\ud574\uc57c \ud569\ub2c8\ub2e4", - "dev_not_config": "\uae30\uae30 \uc720\ud615\uc744 \uad6c\uc131\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "dev_not_found": "\uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \ubc1d\uae30 \ubc94\uc704", - "curr_temp_divider": "\ud604\uc7ac \uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", - "max_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\ub300 \uc0c9\uc628\ub3c4", - "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", - "min_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\uc18c \uc0c9\uc628\ub3c4", - "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", - "set_temp_divided": "\uc124\uc815 \uc628\ub3c4 \uba85\ub839\uc5d0 \ubd84\ud560\ub41c \uc628\ub3c4 \uac12 \uc0ac\uc6a9\ud558\uae30", - "support_color": "\uc0c9\uc0c1 \uc9c0\uc6d0 \uac15\uc81c \uc801\uc6a9\ud558\uae30", - "temp_divider": "\uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", - "temp_step_override": "\ud76c\ub9dd \uc628\ub3c4 \ub2e8\uacc4", - "tuya_max_coltemp": "\uae30\uae30\uc5d0\uc11c \ubcf4\uace0\ud55c \ucd5c\ub300 \uc0c9\uc628\ub3c4", - "unit_of_measurement": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704" - }, - "description": "{device_type} `{device_name}` \uae30\uae30\uc5d0 \ub300\ud574 \ud45c\uc2dc\ub418\ub294 \uc815\ubcf4\ub97c \uc870\uc815\ud558\ub294 \uc635\uc158 \uad6c\uc131\ud558\uae30", - "title": "Tuya \uae30\uae30 \uad6c\uc131\ud558\uae30" - }, - "init": { - "data": { - "discovery_interval": "\uae30\uae30 \uac80\uc0c9 \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)", - "list_devices": "\uad6c\uc131\uc744 \uc800\uc7a5\ud558\ub824\uba74 \uad6c\uc131\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud558\uac70\ub098 \ube44\uc6cc \ub450\uc138\uc694", - "query_device": "\ube60\ub978 \uc0c1\ud0dc \uc5c5\ub370\uc774\ud2b8\ub97c \uc704\ud574 \ucffc\ub9ac \ubc29\ubc95\uc744 \uc0ac\uc6a9\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", - "query_interval": "\uae30\uae30 \ucffc\ub9ac \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)" - }, - "description": "\ud3f4\ub9c1 \uac04\uaca9 \uac12\uc744 \ub108\ubb34 \ub0ae\uac8c \uc124\uc815\ud558\uc9c0 \ub9d0\uc544 \uc8fc\uc138\uc694. \uadf8\ub807\uc9c0 \uc54a\uc73c\uba74 \ud638\ucd9c\uc5d0 \uc2e4\ud328\ud558\uace0 \ub85c\uadf8\uc5d0 \uc624\ub958 \uba54\uc2dc\uc9c0\uac00 \uc0dd\uc131\ub429\ub2c8\ub2e4.", - "title": "Tuya \uc635\uc158 \uad6c\uc131\ud558\uae30" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/lb.json b/homeassistant/components/tuya/translations/lb.json deleted file mode 100644 index 0000f9ef6e62a5..00000000000000 --- a/homeassistant/components/tuya/translations/lb.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Feeler beim verbannen", - "invalid_auth": "Ong\u00eblteg Authentifikatioun", - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." - }, - "error": { - "invalid_auth": "Ong\u00eblteg Authentifikatioun" - }, - "flow_title": "Tuya Konfiguratioun", - "step": { - "user": { - "data": { - "country_code": "De L\u00e4nner Code fir d\u00e4i Kont (beispill 1 fir USA oder 86 fir China)", - "password": "Passwuert", - "platform": "d'App wou den Kont registr\u00e9iert ass", - "username": "Benotzernumm" - }, - "description": "F\u00ebll deng Tuya Umeldungs Informatiounen aus.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Feeler beim verbannen" - }, - "error": { - "dev_multi_type": "Multiple ausgewielte Ger\u00e4ter fir ze konfigur\u00e9ieren musse vum selwechten Typ sinn", - "dev_not_config": "Typ vun Apparat net konfigur\u00e9ierbar", - "dev_not_found": "Apparat net fonnt" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Hellegkeetsber\u00e4ich vum Apparat", - "curr_temp_divider": "Aktuell Temperatur W\u00e4erter Deeler (0= benotz Standard)", - "max_kelvin": "Maximal Faarftemperatur \u00ebnnerst\u00ebtzt a Kelvin", - "max_temp": "Maximal Zil Temperatur (benotz min a max = 0 fir standard)", - "min_kelvin": "Minimal Faarftemperatur \u00ebnnerst\u00ebtzt a Kelvin", - "min_temp": "Minimal Zil Temperatur (benotz min a max = 0 fir standard)", - "support_color": "Forc\u00e9ier Faarf \u00cbnnerst\u00ebtzung", - "temp_divider": "Temperatur W\u00e4erter Deeler (0= benotz Standard)", - "tuya_max_coltemp": "Max Faarftemperatur vum Apparat gemellt", - "unit_of_measurement": "Temperatur Eenheet vum Apparat" - }, - "description": "Konfigur\u00e9ier Optioune fir ugewisen Informatioune fir {device_type} Apparat `{device_name}` unzepassen", - "title": "Tuya Apparat ariichten" - }, - "init": { - "data": { - "list_devices": "Wiel d'Apparater fir ze konfigur\u00e9ieren aus oder loss se eidel fir d'Konfiguratioun ze sp\u00e4icheren" - }, - "title": "Tuya Optioune konfigur\u00e9ieren" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index ed0488f524d09f..2e32bdb66bbe8c 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -1,65 +1,34 @@ { "config": { "abort": { - "cannot_connect": "Kan geen verbinding maken", - "invalid_auth": "Ongeldige authenticatie", - "single_instance_allowed": "Al geconfigureerd. Er is maar een configuratie mogelijk." + "cannot_connect": "Verbindingsfout", + "invalid_auth": "Authenticatiefout", + "single_instance_allowed": "Reeds geconfigureerd. Slechts één configuratie is mogelijk." }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Authenticatiefout" }, - "flow_title": "Tuya-configuratie", + "flow_title": "Tuya Configuratie", "step": { + "project_type":{ + "title":"Tuya Integratie", + "data":{ + "tuya_project_type": "Tuya cloud projecttype" + } + }, "user": { "data": { - "country_code": "De landcode van uw account (bijvoorbeeld 1 voor de VS of 86 voor China)", - "password": "Wachtwoord", - "platform": "De app waar uw account is geregistreerd", - "username": "Gebruikersnaam" + "endpoint": "Availability Zone", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "Mobile App", + "country_code": "Landscode", + "username": "Gebruikersnaam", + "password": "Wachtwoord" }, - "description": "Voer uw Tuya-inloggegevens in.", + "description": "Voer je Tuya aanmeldgegevens in.", "title": "Tuya" } } - }, - "options": { - "abort": { - "cannot_connect": "Kan geen verbinding maken" - }, - "error": { - "dev_multi_type": "Meerdere geselecteerde apparaten om te configureren moeten van hetzelfde type zijn", - "dev_not_config": "Apparaattype kan niet worden geconfigureerd", - "dev_not_found": "Apparaat niet gevonden" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Helderheidsbereik gebruikt door apparaat", - "curr_temp_divider": "Huidige temperatuurwaarde deler (0 = standaardwaarde)", - "max_kelvin": "Max kleurtemperatuur in kelvin", - "max_temp": "Maximale doeltemperatuur (gebruik min en max = 0 voor standaardwaarde)", - "min_kelvin": "Minimaal ondersteunde kleurtemperatuur in kelvin", - "min_temp": "Min. gewenste temperatuur (gebruik min en max = 0 voor standaard)", - "set_temp_divided": "Gedeelde temperatuurwaarde gebruiken voor ingestelde temperatuuropdracht", - "support_color": "Forceer kleurenondersteuning", - "temp_divider": "Temperatuurwaarde deler (0 = standaardwaarde)", - "temp_step_override": "Doeltemperatuur stap", - "tuya_max_coltemp": "Max. kleurtemperatuur gerapporteerd door apparaat", - "unit_of_measurement": "Temperatuureenheid gebruikt door apparaat" - }, - "description": "Configureer opties om weergegeven informatie aan te passen voor {device_type} apparaat `{device_name}`", - "title": "Configureer Tuya Apparaat" - }, - "init": { - "data": { - "discovery_interval": "Polling-interval van ontdekt apparaat in seconden", - "list_devices": "Selecteer de te configureren apparaten of laat leeg om de configuratie op te slaan", - "query_device": "Selecteer apparaat dat query-methode zal gebruiken voor snellere statusupdate", - "query_interval": "Peilinginterval van het apparaat in seconden" - }, - "description": "Stel de waarden voor het pollinginterval niet te laag in, anders zullen de oproepen geen foutmelding in het logboek genereren", - "title": "Configureer Tuya opties" - } - } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/no.json b/homeassistant/components/tuya/translations/no.json deleted file mode 100644 index eedf24be696fed..00000000000000 --- a/homeassistant/components/tuya/translations/no.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Tilkobling mislyktes", - "invalid_auth": "Ugyldig godkjenning", - "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." - }, - "error": { - "invalid_auth": "Ugyldig godkjenning" - }, - "flow_title": "Tuya konfigurasjon", - "step": { - "user": { - "data": { - "country_code": "Din landskode for kontoen din (f.eks. 1 for USA eller 86 for Kina)", - "password": "Passord", - "platform": "Appen der kontoen din er registrert", - "username": "Brukernavn" - }, - "description": "Angi Tuya-legitimasjonen din.", - "title": "" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Tilkobling mislyktes" - }, - "error": { - "dev_multi_type": "Flere valgte enheter som skal konfigureres, m\u00e5 v\u00e6re av samme type", - "dev_not_config": "Enhetstype kan ikke konfigureres", - "dev_not_found": "Finner ikke enheten" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Lysstyrkeomr\u00e5de som brukes av enheten", - "curr_temp_divider": "N\u00e5v\u00e6rende temperaturverdi (0 = bruk standard)", - "max_kelvin": "Maks fargetemperatur st\u00f8ttet i kelvin", - "max_temp": "Maks m\u00e5ltemperatur (bruk min og maks = 0 for standard)", - "min_kelvin": "Min fargetemperatur st\u00f8ttet i kelvin", - "min_temp": "Min m\u00e5ltemperatur (bruk min og maks = 0 for standard)", - "set_temp_divided": "Bruk delt temperaturverdi for innstilt temperaturkommando", - "support_color": "Tving fargest\u00f8tte", - "temp_divider": "Deler temperaturverdier (0 = bruk standard)", - "temp_step_override": "Trinn for m\u00e5ltemperatur", - "tuya_max_coltemp": "Maks fargetemperatur rapportert av enheten", - "unit_of_measurement": "Temperaturenhet som brukes av enheten" - }, - "description": "Konfigurer alternativer for \u00e5 justere vist informasjon for {device_type} device ` {device_name} `", - "title": "Konfigurere Tuya-enhet" - }, - "init": { - "data": { - "discovery_interval": "Avsp\u00f8rringsintervall for discovery-enheten i l\u00f8pet av sekunder", - "list_devices": "Velg enhetene du vil konfigurere, eller la de v\u00e6re tomme for \u00e5 lagre konfigurasjonen", - "query_device": "Velg enhet som skal bruke sp\u00f8rringsmetode for raskere statusoppdatering", - "query_interval": "Sp\u00f8rringsintervall for intervall i sekunder" - }, - "description": "Ikke angi pollingsintervallverdiene for lave, ellers vil ikke anropene generere feilmelding i loggen", - "title": "Konfigurer Tuya-alternativer" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json deleted file mode 100644 index 92ced00e733d17..00000000000000 --- a/homeassistant/components/tuya/translations/pl.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "invalid_auth": "Niepoprawne uwierzytelnienie", - "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." - }, - "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie" - }, - "flow_title": "Konfiguracja integracji Tuya", - "step": { - "user": { - "data": { - "country_code": "Kod kraju twojego konta (np. 1 dla USA lub 86 dla Chin)", - "password": "Has\u0142o", - "platform": "Aplikacja, w kt\u00f3rej zarejestrowane jest Twoje konto", - "username": "Nazwa u\u017cytkownika" - }, - "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" - }, - "error": { - "dev_multi_type": "Wybrane urz\u0105dzenia do skonfigurowania musz\u0105 by\u0107 tego samego typu", - "dev_not_config": "Typ urz\u0105dzenia nie jest konfigurowalny", - "dev_not_found": "Nie znaleziono urz\u0105dzenia" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Zakres jasno\u015bci u\u017cywany przez urz\u0105dzenie", - "curr_temp_divider": "Dzielnik aktualnej warto\u015bci temperatury (0 = u\u017cyj warto\u015bci domy\u015blnej)", - "max_kelvin": "Maksymalna obs\u0142ugiwana temperatura barwy w kelwinach", - "max_temp": "Maksymalna temperatura docelowa (u\u017cyj min i max = 0 dla warto\u015bci domy\u015blnej)", - "min_kelvin": "Minimalna obs\u0142ugiwana temperatura barwy w kelwinach", - "min_temp": "Minimalna temperatura docelowa (u\u017cyj min i max = 0 dla warto\u015bci domy\u015blnej)", - "set_temp_divided": "U\u017cyj podzielonej warto\u015bci temperatury dla polecenia ustawienia temperatury", - "support_color": "Wymu\u015b obs\u0142ug\u0119 kolor\u00f3w", - "temp_divider": "Dzielnik warto\u015bci temperatury (0 = u\u017cyj warto\u015bci domy\u015blnej)", - "temp_step_override": "Krok docelowej temperatury", - "tuya_max_coltemp": "Maksymalna temperatura barwy raportowana przez urz\u0105dzenie", - "unit_of_measurement": "Jednostka temperatury u\u017cywana przez urz\u0105dzenie" - }, - "description": "Skonfiguruj opcje, aby dostosowa\u0107 wy\u015bwietlane informacje dla urz\u0105dzenia {device_type} `{device_name}'", - "title": "Konfiguracja urz\u0105dzenia Tuya" - }, - "init": { - "data": { - "discovery_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania nowych urz\u0105dze\u0144 (w sekundach)", - "list_devices": "Wybierz urz\u0105dzenia do skonfigurowania lub pozostaw puste, aby zapisa\u0107 konfiguracj\u0119", - "query_device": "Wybierz urz\u0105dzenie, kt\u00f3re b\u0119dzie u\u017cywa\u0107 metody odpytywania w celu szybszej aktualizacji statusu", - "query_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania odpytywanego urz\u0105dzenia w sekundach" - }, - "description": "Nie ustawiaj zbyt niskich warto\u015bci skanowania, bo zako\u0144cz\u0105 si\u0119 niepowodzeniem, generuj\u0105c komunikat o b\u0142\u0119dzie w logu", - "title": "Konfiguracja opcji Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/pt-BR.json b/homeassistant/components/tuya/translations/pt-BR.json deleted file mode 100644 index 8dc537e7549eab..00000000000000 --- a/homeassistant/components/tuya/translations/pt-BR.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "flow_title": "Configura\u00e7\u00e3o Tuya", - "step": { - "user": { - "data": { - "country_code": "O c\u00f3digo do pa\u00eds da sua conta (por exemplo, 1 para os EUA ou 86 para a China)", - "password": "Senha", - "platform": "O aplicativo onde sua conta \u00e9 registrada", - "username": "Nome de usu\u00e1rio" - }, - "description": "Digite sua credencial Tuya.", - "title": "Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/pt.json b/homeassistant/components/tuya/translations/pt.json index 566746538c041f..9213d5c58a054e 100644 --- a/homeassistant/components/tuya/translations/pt.json +++ b/homeassistant/components/tuya/translations/pt.json @@ -1,25 +1,34 @@ { "config": { "abort": { - "cannot_connect": "Falha na liga\u00e7\u00e3o", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "cannot_connect": "Falha na conexão", + "invalid_auth": "Autenticação inválida", + "single_instance_allowed": "Já configurado. Só é possível uma configuração desta integração." }, "error": { - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autenticação inválida" }, + "flow_title": "Configuração Tuya", "step": { + "project_type":{ + "title":"Integração Tuya", + "data":{ + "tuya_project_type": "Tipo de projeto Tuya Cloud" + } + }, "user": { "data": { - "password": "Palavra-passe", - "username": "Nome de Utilizador" - } + "endpoint": "Região", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "Aplicativo", + "country_code": "Código do País", + "username": "Conta", + "password": "Senha" + }, + "description": "Insira as credenciais Tuya.", + "title": "Tuya" } } - }, - "options": { - "abort": { - "cannot_connect": "Falha na liga\u00e7\u00e3o" - } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json deleted file mode 100644 index 7b46689bc50357..00000000000000 --- a/homeassistant/components/tuya/translations/ru.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", - "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." - }, - "error": { - "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." - }, - "flow_title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Tuya", - "step": { - "user": { - "data": { - "country_code": "\u041a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0438\u043b\u0438 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044f)", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "platform": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c", - "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" - }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." - }, - "error": { - "dev_multi_type": "\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430.", - "dev_not_config": "\u0422\u0438\u043f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044f.", - "dev_not_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u044f\u0440\u043a\u043e\u0441\u0442\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c", - "curr_temp_divider": "\u0414\u0435\u043b\u0438\u0442\u0435\u043b\u044c \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b (0 = \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e)", - "max_kelvin": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0438\u043d\u0430\u0445)", - "max_temp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 min \u0438 max = 0)", - "min_kelvin": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0438\u043d\u0430\u0445)", - "min_temp": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 min \u0438 max = 0)", - "set_temp_divided": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", - "support_color": "\u041f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0446\u0432\u0435\u0442\u0430", - "temp_divider": "\u0414\u0435\u043b\u0438\u0442\u0435\u043b\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b (0 = \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e)", - "temp_step_override": "\u0428\u0430\u0433 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", - "tuya_max_coltemp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430, \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c\u0430\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c", - "unit_of_measurement": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c" - }, - "description": "\u041e\u043f\u0446\u0438\u0438 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0434\u043b\u044f {device_type} \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 `{device_name}`", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Tuya" - }, - "init": { - "data": { - "discovery_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "list_devices": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438", - "query_device": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0430", - "query_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" - }, - "description": "\u041d\u0435 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0439\u0442\u0435 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043d\u0438\u0437\u043a\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430 \u043e\u043f\u0440\u043e\u0441\u0430, \u0438\u043d\u0430\u0447\u0435 \u0432\u044b\u0437\u043e\u0432\u044b \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u0432 \u0436\u0443\u0440\u043d\u0430\u043b\u0435.", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sl.json b/homeassistant/components/tuya/translations/sl.json deleted file mode 100644 index b07ad70adac513..00000000000000 --- a/homeassistant/components/tuya/translations/sl.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "options": { - "abort": { - "cannot_connect": "Povezovanje ni uspelo." - }, - "error": { - "dev_not_config": "Vrsta naprave ni nastavljiva", - "dev_not_found": "Naprave ni mogo\u010de najti" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sv.json b/homeassistant/components/tuya/translations/sv.json deleted file mode 100644 index 85cc9c57fd39be..00000000000000 --- a/homeassistant/components/tuya/translations/sv.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "flow_title": "Tuya-konfiguration", - "step": { - "user": { - "data": { - "country_code": "Landskod f\u00f6r ditt konto (t.ex. 1 f\u00f6r USA eller 86 f\u00f6r Kina)", - "password": "L\u00f6senord", - "platform": "Appen d\u00e4r ditt konto registreras", - "username": "Anv\u00e4ndarnamn" - }, - "description": "Ange dina Tuya anv\u00e4ndaruppgifter.", - "title": "Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/tr.json b/homeassistant/components/tuya/translations/tr.json deleted file mode 100644 index 2edf3276b6c503..00000000000000 --- a/homeassistant/components/tuya/translations/tr.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "Ba\u011flanma hatas\u0131", - "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", - "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." - }, - "error": { - "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" - }, - "flow_title": "Tuya yap\u0131land\u0131rmas\u0131", - "step": { - "user": { - "data": { - "country_code": "Hesap \u00fclke kodunuz (\u00f6r. ABD i\u00e7in 1 veya \u00c7in i\u00e7in 86)", - "password": "Parola", - "platform": "Hesab\u0131n\u0131z\u0131n kay\u0131tl\u0131 oldu\u011fu uygulama", - "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "description": "Tuya kimlik bilgilerinizi girin.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Ba\u011flanma hatas\u0131" - }, - "error": { - "dev_not_config": "Cihaz t\u00fcr\u00fc yap\u0131land\u0131r\u0131lamaz", - "dev_not_found": "Cihaz bulunamad\u0131" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Cihaz\u0131n kulland\u0131\u011f\u0131 parlakl\u0131k aral\u0131\u011f\u0131", - "max_temp": "Maksimum hedef s\u0131cakl\u0131k (varsay\u0131lan olarak min ve maks = 0 kullan\u0131n)", - "min_kelvin": "Kelvin destekli min renk s\u0131cakl\u0131\u011f\u0131", - "min_temp": "Minimum hedef s\u0131cakl\u0131k (varsay\u0131lan i\u00e7in min ve maks = 0 kullan\u0131n)", - "support_color": "Vurgu rengi", - "temp_divider": "S\u0131cakl\u0131k de\u011ferleri ay\u0131r\u0131c\u0131 (0 = varsay\u0131lan\u0131 kullan)", - "tuya_max_coltemp": "Cihaz taraf\u0131ndan bildirilen maksimum renk s\u0131cakl\u0131\u011f\u0131", - "unit_of_measurement": "Cihaz\u0131n kulland\u0131\u011f\u0131 s\u0131cakl\u0131k birimi" - }, - "description": "{device_type} ayg\u0131t\u0131 '{device_name}' i\u00e7in g\u00f6r\u00fcnt\u00fclenen bilgileri ayarlamak i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n", - "title": "Tuya Cihaz\u0131n\u0131 Yap\u0131land\u0131r\u0131n" - }, - "init": { - "data": { - "discovery_interval": "Cihaz\u0131 yoklama aral\u0131\u011f\u0131 saniye cinsinden", - "list_devices": "Yap\u0131land\u0131rmay\u0131 kaydetmek i\u00e7in yap\u0131land\u0131r\u0131lacak veya bo\u015f b\u0131rak\u0131lacak cihazlar\u0131 se\u00e7in", - "query_device": "Daha h\u0131zl\u0131 durum g\u00fcncellemesi i\u00e7in sorgu y\u00f6ntemini kullanacak cihaz\u0131 se\u00e7in", - "query_interval": "Ayg\u0131t yoklama aral\u0131\u011f\u0131 saniye cinsinden" - }, - "description": "Yoklama aral\u0131\u011f\u0131 de\u011ferlerini \u00e7ok d\u00fc\u015f\u00fck ayarlamay\u0131n, aksi takdirde \u00e7a\u011fr\u0131lar g\u00fcnl\u00fckte hata mesaj\u0131 olu\u015fturarak ba\u015far\u0131s\u0131z olur", - "title": "Tuya Se\u00e7eneklerini Konfig\u00fcre Et" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/uk.json b/homeassistant/components/tuya/translations/uk.json deleted file mode 100644 index 1d2709d260a0c6..00000000000000 --- a/homeassistant/components/tuya/translations/uk.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "config": { - "abort": { - "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", - "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." - }, - "error": { - "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." - }, - "flow_title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya", - "step": { - "user": { - "data": { - "country_code": "\u041a\u043e\u0434 \u043a\u0440\u0430\u0457\u043d\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044e)", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "platform": "\u0414\u043e\u0434\u0430\u0442\u043e\u043a, \u0432 \u044f\u043a\u043e\u043c\u0443 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441", - "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" - }, - "error": { - "dev_multi_type": "\u041a\u0456\u043b\u044c\u043a\u0430 \u043e\u0431\u0440\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0443.", - "dev_not_config": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", - "dev_not_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u0414\u0456\u0430\u043f\u0430\u0437\u043e\u043d \u044f\u0441\u043a\u0440\u0430\u0432\u043e\u0441\u0442\u0456, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", - "curr_temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", - "max_kelvin": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", - "max_temp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", - "min_kelvin": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", - "min_temp": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", - "support_color": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 \u043a\u043e\u043b\u044c\u043e\u0440\u0443", - "temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u0437\u043d\u0430\u0447\u0435\u043d\u044c \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", - "tuya_max_coltemp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430, \u044f\u043a\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u044f\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", - "unit_of_measurement": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438, \u044f\u043a\u0430 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c" - }, - "description": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u0434\u0438\u043c\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u0434\u043b\u044f {device_type} \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e '{device_name}'", - "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Tuya" - }, - "init": { - "data": { - "discovery_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "list_devices": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0431\u043e \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u0434\u043b\u044f \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457", - "query_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0438\u0442\u0443 \u0434\u043b\u044f \u0431\u0456\u043b\u044c\u0448 \u0448\u0432\u0438\u0434\u043a\u043e\u0433\u043e \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0443", - "query_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" - }, - "description": "\u041d\u0435 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u044e\u0439\u0442\u0435 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u043d\u0438\u0437\u044c\u043a\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443 \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0456\u043d\u0430\u043a\u0448\u0435 \u0432\u0438\u043a\u043b\u0438\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442\u044c \u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u043e \u043f\u043e\u043c\u0438\u043b\u043a\u0443 \u0432 \u0436\u0443\u0440\u043d\u0430\u043b\u0456.", - "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/zh-Hans.json b/homeassistant/components/tuya/translations/zh-Hans.json index ff3887c840d0eb..e682bbc12d61ed 100644 --- a/homeassistant/components/tuya/translations/zh-Hans.json +++ b/homeassistant/components/tuya/translations/zh-Hans.json @@ -1,60 +1,34 @@ { "config": { "abort": { - "cannot_connect": "\u8fde\u63a5\u5931\u8d25", - "invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548", - "single_instance_allowed": "\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86\uff0c\u4e14\u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002" + "cannot_connect": "连接失败", + "invalid_auth": "身份认证无效", + "single_instance_allowed": "已经配置过了,且只能配置一次。" }, "error": { - "invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548" + "invalid_auth": "身份认证无效" }, - "flow_title": "\u6d82\u9e26\u914d\u7f6e", + "flow_title": "涂鸦配置", "step": { - "user": { - "data": { - "country_code": "\u60a8\u7684\u5e10\u6237\u56fd\u5bb6(\u5730\u533a)\u4ee3\u7801\uff08\u4f8b\u5982\u4e2d\u56fd\u4e3a 86\uff0c\u7f8e\u56fd\u4e3a 1\uff09", - "password": "\u5bc6\u7801", - "platform": "\u60a8\u6ce8\u518c\u5e10\u6237\u7684\u5e94\u7528", - "username": "\u7528\u6237\u540d" - }, - "description": "\u8bf7\u8f93\u5165\u6d82\u9e26\u8d26\u6237\u4fe1\u606f\u3002", - "title": "\u6d82\u9e26" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u8fde\u63a5\u5931\u8d25" - }, - "error": { - "dev_multi_type": "\u591a\u4e2a\u8981\u914d\u7f6e\u7684\u8bbe\u5907\u5fc5\u987b\u5177\u6709\u76f8\u540c\u7684\u7c7b\u578b", - "dev_not_config": "\u8bbe\u5907\u7c7b\u578b\u4e0d\u53ef\u914d\u7f6e", - "dev_not_found": "\u672a\u627e\u5230\u8bbe\u5907" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u8bbe\u5907\u4f7f\u7528\u7684\u4eae\u5ea6\u8303\u56f4", - "max_kelvin": "\u6700\u9ad8\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09", - "max_temp": "\u6700\u9ad8\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09", - "min_kelvin": "\u6700\u4f4e\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09", - "min_temp": "\u6700\u4f4e\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09", - "support_color": "\u5f3a\u5236\u652f\u6301\u8c03\u8272", - "tuya_max_coltemp": "\u8bbe\u5907\u62a5\u544a\u7684\u6700\u9ad8\u8272\u6e29", - "unit_of_measurement": "\u8bbe\u5907\u4f7f\u7528\u7684\u6e29\u5ea6\u5355\u4f4d" - }, - "title": "\u914d\u7f6e\u6d82\u9e26\u8bbe\u5907" + "project_type":{ + "title":"Tuya插件", + "data":{ + "tuya_project_type": "涂鸦云项目类型" + } }, - "init": { + "user": { "data": { - "discovery_interval": "\u53d1\u73b0\u8bbe\u5907\u8f6e\u8be2\u95f4\u9694\uff08\u4ee5\u79d2\u4e3a\u5355\u4f4d\uff09", - "list_devices": "\u8bf7\u9009\u62e9\u8981\u914d\u7f6e\u7684\u8bbe\u5907\uff0c\u6216\u7559\u7a7a\u4ee5\u4fdd\u5b58\u914d\u7f6e", - "query_device": "\u8bf7\u9009\u62e9\u4f7f\u7528\u67e5\u8be2\u65b9\u6cd5\u7684\u8bbe\u5907\uff0c\u4ee5\u4fbf\u66f4\u5feb\u5730\u66f4\u65b0\u72b6\u6001", - "query_interval": "\u67e5\u8be2\u8bbe\u5907\u8f6e\u8be2\u95f4\u9694\uff08\u4ee5\u79d2\u4e3a\u5355\u4f4d\uff09" + "endpoint": "可用区域", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "移动应用", + "country_code": "国家码", + "username": "账号", + "password": "密码" }, - "description": "\u8bf7\u4e0d\u8981\u5c06\u8f6e\u8be2\u95f4\u9694\u8bbe\u7f6e\u5f97\u592a\u4f4e\uff0c\u5426\u5219\u5c06\u8c03\u7528\u5931\u8d25\u5e76\u5728\u65e5\u5fd7\u751f\u6210\u9519\u8bef\u6d88\u606f", - "title": "\u914d\u7f6e\u6d82\u9e26\u9009\u9879" + "description": "请输入涂鸦账户信息。", + "title": "涂鸦" } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tuya/translations/zh-Hant.json b/homeassistant/components/tuya/translations/zh-Hant.json index 7221c86eb6370f..6c7a9e8d2f5365 100644 --- a/homeassistant/components/tuya/translations/zh-Hant.json +++ b/homeassistant/components/tuya/translations/zh-Hant.json @@ -1,65 +1,34 @@ { "config": { "abort": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "cannot_connect": "連線失敗", + "invalid_auth": "帳號資訊有誤", + "single_instance_allowed": "已設定,且無法重複設定" }, "error": { - "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + "invalid_auth": "帳號資訊有誤" }, - "flow_title": "Tuya \u8a2d\u5b9a", + "flow_title": "塗鴉設定", "step": { - "user": { - "data": { - "country_code": "\u5e33\u865f\u570b\u5bb6\u4ee3\u78bc\uff08\u4f8b\u5982\uff1a\u7f8e\u570b 1 \u6216\u4e2d\u570b 86\uff09", - "password": "\u5bc6\u78bc", - "platform": "\u5e33\u6236\u8a3b\u518a\u6240\u5728\u4f4d\u7f6e", - "username": "\u4f7f\u7528\u8005\u540d\u7a31" - }, - "description": "\u8f38\u5165 Tuya \u6191\u8b49\u3002", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u9023\u7dda\u5931\u6557" - }, - "error": { - "dev_multi_type": "\u591a\u91cd\u9078\u64c7\u8a2d\u88dd\u7f6e\u4ee5\u8a2d\u5b9a\u4f7f\u7528\u76f8\u540c\u985e\u578b", - "dev_not_config": "\u88dd\u7f6e\u985e\u578b\u7121\u6cd5\u8a2d\u5b9a", - "dev_not_found": "\u627e\u4e0d\u5230\u88dd\u7f6e" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u88dd\u7f6e\u6240\u4f7f\u7528\u4e4b\u4eae\u5ea6\u7bc4\u570d", - "curr_temp_divider": "\u76ee\u524d\u8272\u6eab\u503c\u5206\u914d\u5668\uff080 = \u4f7f\u7528\u9810\u8a2d\uff09", - "max_kelvin": "Kelvin \u652f\u63f4\u6700\u9ad8\u8272\u6eab", - "max_temp": "\u6700\u9ad8\u76ee\u6a19\u8272\u6eab\uff08\u4f7f\u7528\u6700\u4f4e\u8207\u6700\u9ad8 = 0 \u4f7f\u7528\u9810\u8a2d\uff09", - "min_kelvin": "Kelvin \u652f\u63f4\u6700\u4f4e\u8272\u6eab", - "min_temp": "\u6700\u4f4e\u76ee\u6a19\u8272\u6eab\uff08\u4f7f\u7528\u6700\u4f4e\u8207\u6700\u9ad8 = 0 \u4f7f\u7528\u9810\u8a2d\uff09", - "set_temp_divided": "\u4f7f\u7528\u5206\u9694\u865f\u6eab\u5ea6\u503c\u4ee5\u57f7\u884c\u8a2d\u5b9a\u6eab\u5ea6\u6307\u4ee4", - "support_color": "\u5f37\u5236\u8272\u6eab\u652f\u63f4", - "temp_divider": "\u8272\u6eab\u503c\u5206\u914d\u5668\uff080 = \u4f7f\u7528\u9810\u8a2d\uff09", - "temp_step_override": "\u76ee\u6a19\u6eab\u5ea6\u8a2d\u5b9a", - "tuya_max_coltemp": "\u88dd\u7f6e\u56de\u5831\u6700\u9ad8\u8272\u6eab", - "unit_of_measurement": "\u88dd\u7f6e\u6240\u4f7f\u7528\u4e4b\u6eab\u5ea6\u55ae\u4f4d" - }, - "description": "\u8a2d\u5b9a\u9078\u9805\u4ee5\u8abf\u6574 {device_type} \u88dd\u7f6e `{device_name}` \u986f\u793a\u8cc7\u8a0a", - "title": "\u8a2d\u5b9a Tuya \u88dd\u7f6e" + "project_type":{ + "title":"Tuya 套件", + "data":{ + "tuya_project_type": "塗鴉雲端專案類型" + } }, - "init": { + "user": { "data": { - "discovery_interval": "\u63a2\u7d22\u88dd\u7f6e\u66f4\u65b0\u79d2\u9593\u8ddd", - "list_devices": "\u9078\u64c7\u88dd\u7f6e\u4ee5\u8a2d\u5b9a\u3001\u6216\u4fdd\u6301\u7a7a\u767d\u4ee5\u5132\u5b58\u8a2d\u5b9a", - "query_device": "\u9078\u64c7\u88dd\u7f6e\u5c07\u4f7f\u7528\u67e5\u8a62\u65b9\u5f0f\u4ee5\u7372\u5f97\u66f4\u5feb\u7684\u72c0\u614b\u66f4\u65b0", - "query_interval": "\u67e5\u8a62\u88dd\u7f6e\u66f4\u65b0\u79d2\u9593\u8ddd" + "endpoint": "區域", + "access_id": "Access ID", + "access_secret": "Access Secret", + "tuya_app_type": "App", + "country_code": "國家代碼", + "username": "帳號", + "password": "密碼" }, - "description": "\u66f4\u65b0\u9593\u8ddd\u4e0d\u8981\u8a2d\u5b9a\u7684\u904e\u4f4e\u3001\u53ef\u80fd\u6703\u5c0e\u81f4\u65bc\u65e5\u8a8c\u4e2d\u7522\u751f\u932f\u8aa4\u8a0a\u606f", - "title": "\u8a2d\u5b9a Tuya \u9078\u9805" + "description": "請輸入塗鴉帳號資訊。", + "title": "塗鴉" } } } -} \ No newline at end of file +} diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 82b09e5f7efec9..8fa4c99fe832e5 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -329,30 +329,6 @@ "hostname": "lb*", "macaddress": "B09575*" }, - { - "domain": "tuya", - "macaddress": "508A06*" - }, - { - "domain": "tuya", - "macaddress": "7CF666*" - }, - { - "domain": "tuya", - "macaddress": "10D561*" - }, - { - "domain": "tuya", - "macaddress": "D4A651*" - }, - { - "domain": "tuya", - "macaddress": "68572D*" - }, - { - "domain": "tuya", - "macaddress": "1869D8*" - }, { "domain": "verisure", "macaddress": "0023C1*" diff --git a/requirements_all.txt b/requirements_all.txt index 60af0b3585135a..288f2c58e33fc8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2289,7 +2289,7 @@ tp-connected==0.0.4 transmissionrpc==0.11 # homeassistant.components.tuya -tuyaha==0.0.10 +tuya-iot-py-sdk==0.4.0 # homeassistant.components.twentemilieu twentemilieu==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cad87b46a2c8a4..9b75daa80e1b68 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1246,7 +1246,7 @@ total_connect_client==0.57 transmissionrpc==0.11 # homeassistant.components.tuya -tuyaha==0.0.10 +tuya-iot-py-sdk==0.4.0 # homeassistant.components.twentemilieu twentemilieu==0.3.0 diff --git a/tests/components/tuya/test_config_flow.py b/tests/components/tuya/test_config_flow.py index 6ea28cf8d2b8c1..ac61ef1fc06293 100644 --- a/tests/components/tuya/test_config_flow.py +++ b/tests/components/tuya/test_config_flow.py @@ -1,68 +1,87 @@ """Tests for the Tuya config flow.""" -from unittest.mock import Mock, patch +from unittest.mock import MagicMock, Mock, patch import pytest -from tuyaha.devices.climate import STEP_HALVES -from tuyaha.tuyaapi import TuyaAPIException, TuyaNetException from homeassistant import config_entries, data_entry_flow from homeassistant.components.tuya.config_flow import ( - CONF_LIST_DEVICES, - ERROR_DEV_MULTI_TYPE, - ERROR_DEV_NOT_CONFIG, - ERROR_DEV_NOT_FOUND, RESULT_AUTH_FAILED, - RESULT_CONN_ERROR, RESULT_SINGLE_INSTANCE, ) from homeassistant.components.tuya.const import ( - CONF_BRIGHTNESS_RANGE_MODE, - CONF_COUNTRYCODE, - CONF_CURR_TEMP_DIVIDER, - CONF_DISCOVERY_INTERVAL, - CONF_MAX_KELVIN, - CONF_MAX_TEMP, - CONF_MIN_KELVIN, - CONF_MIN_TEMP, - CONF_QUERY_DEVICE, - CONF_QUERY_INTERVAL, - CONF_SET_TEMP_DIVIDED, - CONF_SUPPORT_COLOR, - CONF_TEMP_DIVIDER, - CONF_TEMP_STEP_OVERRIDE, - CONF_TUYA_MAX_COLTEMP, - DOMAIN, - TUYA_DATA, -) -from homeassistant.const import ( + CONF_ACCESS_ID, + CONF_ACCESS_SECRET, + CONF_APP_TYPE, + CONF_COUNTRY_CODE, + CONF_ENDPOINT, CONF_PASSWORD, - CONF_PLATFORM, - CONF_UNIT_OF_MEASUREMENT, + CONF_PROJECT_TYPE, CONF_USERNAME, - TEMP_CELSIUS, + DOMAIN, ) -from .common import CLIMATE_ID, LIGHT_ID, LIGHT_ID_FAKE1, LIGHT_ID_FAKE2, MockTuya - from tests.common import MockConfigEntry -USERNAME = "myUsername" -PASSWORD = "myPassword" -COUNTRY_CODE = "1" -TUYA_PLATFORM = "tuya" +MOCK_SMART_HOME_PROJECT_TYPE = 0 +MOCK_INDUSTRY_PROJECT_TYPE = 1 + +MOCK_ACCESS_ID = "myAccessId" +MOCK_ACCESS_SECRET = "myAccessSecret" +MOCK_USERNAME = "myUsername" +MOCK_PASSWORD = "myPassword" +MOCK_COUNTRY_CODE = "1" +MOCK_APP_TYPE = "smartlife" +MOCK_ENDPOINT = "https://openapi-ueaz.tuyaus.com" + +TUYA_SMART_HOME_PROJECT_DATA = { + CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE, +} +TUYA_INDUSTRY_PROJECT_DATA = { + CONF_PROJECT_TYPE: MOCK_INDUSTRY_PROJECT_TYPE, +} + +TUYA_INPUT_SMART_HOME_DATA = { + CONF_ACCESS_ID: MOCK_ACCESS_ID, + CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET, + CONF_USERNAME: MOCK_USERNAME, + CONF_PASSWORD: MOCK_PASSWORD, + CONF_COUNTRY_CODE: MOCK_COUNTRY_CODE, + CONF_APP_TYPE: MOCK_APP_TYPE, +} + +TUYA_INPUT_INDUSTRY_DATA = { + CONF_ENDPOINT: MOCK_ENDPOINT, + CONF_ACCESS_ID: MOCK_ACCESS_ID, + CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET, + CONF_USERNAME: MOCK_USERNAME, + CONF_PASSWORD: MOCK_PASSWORD, +} + +TUYA_IMPORT_SMART_HOME_DATA = { + CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE, + CONF_ACCESS_ID: MOCK_ACCESS_ID, + CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET, + CONF_USERNAME: MOCK_USERNAME, + CONF_PASSWORD: MOCK_PASSWORD, + CONF_COUNTRY_CODE: MOCK_COUNTRY_CODE, + CONF_APP_TYPE: MOCK_APP_TYPE, +} + -TUYA_USER_DATA = { - CONF_USERNAME: USERNAME, - CONF_PASSWORD: PASSWORD, - CONF_COUNTRYCODE: COUNTRY_CODE, - CONF_PLATFORM: TUYA_PLATFORM, +TUYA_IMPORT_INDUSTRY_DATA = { + CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE, + CONF_ENDPOINT: MOCK_ENDPOINT, + CONF_ACCESS_ID: MOCK_ACCESS_ID, + CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET, + CONF_USERNAME: MOCK_USERNAME, + CONF_PASSWORD: MOCK_PASSWORD, } @pytest.fixture(name="tuya") def tuya_fixture() -> Mock: """Patch libraries.""" - with patch("homeassistant.components.tuya.config_flow.TuyaApi") as tuya: + with patch("homeassistant.components.tuya.config_flow.TuyaOpenAPI") as tuya: yield tuya @@ -73,202 +92,123 @@ def tuya_setup_fixture(): yield -async def test_user(hass, tuya): - """Test user config.""" +async def test_industry_user(hass, tuya): + """Test industry user config.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" + assert result["step_id"] == "project_type" result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=TUYA_USER_DATA + result["flow_id"], user_input=TUYA_INDUSTRY_PROJECT_DATA ) await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USERNAME - assert result["data"][CONF_USERNAME] == USERNAME - assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_COUNTRYCODE] == COUNTRY_CODE - assert result["data"][CONF_PLATFORM] == TUYA_PLATFORM - assert not result["result"].unique_id - - -async def test_abort_if_already_setup(hass, tuya): - """Test we abort if Tuya is already setup.""" - MockConfigEntry(domain=DOMAIN, data=TUYA_USER_DATA).add_to_hass(hass) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" - # Should fail, config exist (import) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, data=TUYA_USER_DATA + tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=TUYA_INPUT_INDUSTRY_DATA ) + await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == RESULT_SINGLE_INSTANCE - + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == MOCK_USERNAME + assert result["data"][CONF_ACCESS_ID] == MOCK_ACCESS_ID + assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET + assert result["data"][CONF_USERNAME] == MOCK_USERNAME + assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD + assert not result["result"].unique_id -async def test_abort_on_invalid_credentials(hass, tuya): - """Test when we have invalid credentials.""" - tuya().init.side_effect = TuyaAPIException("Boom") +async def test_smart_home_user(hass, tuya): + """Test smart home user config.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, data=TUYA_USER_DATA + DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": RESULT_AUTH_FAILED} - - -async def test_abort_on_connection_error(hass, tuya): - """Test when we have a network error.""" - tuya().init.side_effect = TuyaNetException("Boom") - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, data=TUYA_USER_DATA - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == RESULT_CONN_ERROR + assert result["step_id"] == "project_type" - -async def test_options_flow(hass): - """Test config flow options.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data=TUYA_USER_DATA, + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=TUYA_SMART_HOME_PROJECT_DATA ) - config_entry.add_to_hass(hass) - - # Set up the integration to make sure the config flow module is loaded. - assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - # Unload the integration to prepare for the test. - with patch("homeassistant.components.tuya.async_unload_entry", return_value=True): - assert await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - - # Test check for integration not loaded - result = await hass.config_entries.options.async_init(config_entry.entry_id) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == RESULT_CONN_ERROR + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" - # Load integration and enter options - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - hass.data[DOMAIN] = {TUYA_DATA: MockTuya()} - result = await hass.config_entries.options.async_init(config_entry.entry_id) + with patch( + "homeassistant.components.tuya.config_flow.TuyaConfigFlow._try_login", + return_value={"success": False, "result": ""}, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=TUYA_INPUT_SMART_HOME_DATA + ) + await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "init" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" - # Test dev not found error - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID_FAKE1}"]}, - ) + with patch( + "homeassistant.components.tuya.config_flow.TuyaConfigFlow._try_login", + return_value={"success": True, "result": ""}, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=TUYA_INPUT_SMART_HOME_DATA + ) + await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "init" - assert result["errors"] == {"base": ERROR_DEV_NOT_FOUND} + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == MOCK_USERNAME + assert result["data"][CONF_ACCESS_ID] == MOCK_ACCESS_ID + assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET + assert result["data"][CONF_USERNAME] == MOCK_USERNAME + assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD + assert result["data"][CONF_COUNTRY_CODE] == MOCK_COUNTRY_CODE + assert result["data"][CONF_APP_TYPE] == MOCK_APP_TYPE + assert not result["result"].unique_id - # Test dev type error - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID_FAKE2}"]}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "init" - assert result["errors"] == {"base": ERROR_DEV_NOT_CONFIG} +# async def test_input_error_when_setup(hass, tuya): +# result = await hass.config_entries.flow.async_init( +# DOMAIN, context={"source": config_entries.SOURCE_USER} +# ) +# await hass.async_block_till_done() - # Test multi dev error - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_LIST_DEVICES: [f"climate-{CLIMATE_ID}", f"light-{LIGHT_ID}"]}, - ) +# result = await hass.config_entries.flow.async_configure( +# result["flow_id"], user_input=TUYA_PROJECT_DATA +# ) +# await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "init" - assert result["errors"] == {"base": ERROR_DEV_MULTI_TYPE} - # Test climate options form - result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_LIST_DEVICES: [f"climate-{CLIMATE_ID}"]} - ) +async def test_abort_if_already_setup(hass, tuya): + """Test we abort if Tuya is already setup.""" + MockConfigEntry(domain=DOMAIN, data=TUYA_IMPORT_SMART_HOME_DATA).add_to_hass(hass) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "device" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - CONF_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - CONF_TEMP_DIVIDER: 10, - CONF_CURR_TEMP_DIVIDER: 5, - CONF_SET_TEMP_DIVIDED: False, - CONF_TEMP_STEP_OVERRIDE: STEP_HALVES, - CONF_MIN_TEMP: 12, - CONF_MAX_TEMP: 22, - }, + # Should fail, config exist (import) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=TUYA_IMPORT_SMART_HOME_DATA, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "init" + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == RESULT_SINGLE_INSTANCE - # Test light options form - result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID}"]} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "device" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - CONF_SUPPORT_COLOR: True, - CONF_BRIGHTNESS_RANGE_MODE: 1, - CONF_MIN_KELVIN: 4000, - CONF_MAX_KELVIN: 5000, - CONF_TUYA_MAX_COLTEMP: 12000, - }, - ) +async def test_abort_on_invalid_credentials(hass, tuya): + """Test when we have invalid credentials.""" + tuya().login = MagicMock(return_value={"success": False, "errorCode": 1024}) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "init" - - # Test common options - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - CONF_DISCOVERY_INTERVAL: 100, - CONF_QUERY_INTERVAL: 50, - CONF_QUERY_DEVICE: LIGHT_ID, - }, + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=TUYA_IMPORT_SMART_HOME_DATA, ) - # Verify results - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - - climate_options = config_entry.options[CLIMATE_ID] - assert climate_options[CONF_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS - assert climate_options[CONF_TEMP_DIVIDER] == 10 - assert climate_options[CONF_CURR_TEMP_DIVIDER] == 5 - assert climate_options[CONF_SET_TEMP_DIVIDED] is False - assert climate_options[CONF_TEMP_STEP_OVERRIDE] == STEP_HALVES - assert climate_options[CONF_MIN_TEMP] == 12 - assert climate_options[CONF_MAX_TEMP] == 22 - - light_options = config_entry.options[LIGHT_ID] - assert light_options[CONF_SUPPORT_COLOR] is True - assert light_options[CONF_BRIGHTNESS_RANGE_MODE] == 1 - assert light_options[CONF_MIN_KELVIN] == 4000 - assert light_options[CONF_MAX_KELVIN] == 5000 - assert light_options[CONF_TUYA_MAX_COLTEMP] == 12000 - - assert config_entry.options[CONF_DISCOVERY_INTERVAL] == 100 - assert config_entry.options[CONF_QUERY_INTERVAL] == 50 - assert config_entry.options[CONF_QUERY_DEVICE] == LIGHT_ID + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == RESULT_AUTH_FAILED