Tuya v2 Integration Release (#56820)

Co-authored-by: 乾启 <18442047+tsutsuku@users.noreply.github.com>
Co-authored-by: dengweijun <dengweijunben@gmail.com>
Co-authored-by: dengweijun <mengzi.deng@tuya.com>
Co-authored-by: erchuan <jie.zheng@tuya.com>
Co-authored-by: erchuan <erchuan365@outlook.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Oliver Ou 2021-09-30 18:02:56 +08:00 committed by GitHub
parent 26042bdad7
commit d3b1ccb668
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1732 additions and 3107 deletions

View File

@ -1115,9 +1115,9 @@ omit =
homeassistant/components/transmission/errors.py
homeassistant/components/travisci/sensor.py
homeassistant/components/tuya/__init__.py
homeassistant/components/tuya/base.py
homeassistant/components/tuya/climate.py
homeassistant/components/tuya/const.py
homeassistant/components/tuya/cover.py
homeassistant/components/tuya/fan.py
homeassistant/components/tuya/light.py
homeassistant/components/tuya/scene.py

View File

@ -544,7 +544,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

View File

@ -1,376 +1,208 @@
#!/usr/bin/env python3
"""Support for Tuya Smart devices."""
from datetime import timedelta
import itertools
import logging
from tuyaha import TuyaApi
from tuyaha.tuyaapi import (
TuyaAPIException,
TuyaAPIRateLimitException,
TuyaFrequentlyInvokeException,
TuyaNetException,
TuyaServerException,
from tuya_iot import (
ProjectType,
TuyaDevice,
TuyaDeviceListener,
TuyaDeviceManager,
TuyaHomeManager,
TuyaOpenAPI,
TuyaOpenMQ,
)
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
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 import device_registry
from homeassistant.helpers.dispatcher import dispatcher_send
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,
PLATFORMS,
TUYA_DEVICE_MANAGER,
TUYA_DISCOVERY_NEW,
TUYA_PLATFORMS,
TUYA_TYPE_NOT_QUERY,
TUYA_HA_DEVICES,
TUYA_HA_SIGNAL_UPDATE_ENTITY,
TUYA_HA_TUYA_MAP,
TUYA_HOME_MANAGER,
TUYA_MQTT_LISTENER,
)
_LOGGER = logging.getLogger(__name__)
ATTR_TUYA_DEV_ID = "tuya_device_id"
ENTRY_IS_SETUP = "tuya_entry_is_setup"
SERVICE_FORCE_UPDATE = "force_update"
SERVICE_PULL_DEVICES = "pull_devices"
TUYA_TYPE_TO_HA = {
"climate": "climate",
"cover": "cover",
"fan": "fan",
"light": "light",
"scene": "scene",
"switch": "switch",
}
TUYA_TRACKER = "tuya_tracker"
STOP_CANCEL = "stop_event_cancel"
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
def _update_discovery_interval(hass, interval):
tuya = hass.data[DOMAIN].get(TUYA_DATA)
if not tuya:
return
try:
tuya.discovery_interval = interval
_LOGGER.info("Tuya discovery device poll interval set to %s seconds", interval)
except ValueError as ex:
_LOGGER.warning(ex)
def _update_query_interval(hass, interval):
tuya = hass.data[DOMAIN].get(TUYA_DATA)
if not tuya:
return
try:
tuya.query_interval = interval
_LOGGER.info("Tuya query device poll interval set to %s seconds", interval)
except ValueError as ex:
_LOGGER.warning(ex)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Tuya platform."""
"""Async setup hass config entry."""
tuya = TuyaApi()
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
country_code = entry.data[CONF_COUNTRYCODE]
platform = entry.data[CONF_PLATFORM]
_LOGGER.debug("tuya.__init__.async_setup_entry-->%s", entry.data)
try:
await hass.async_add_executor_job(
tuya.init, username, password, country_code, platform
)
except (
TuyaNetException,
TuyaServerException,
TuyaFrequentlyInvokeException,
) as exc:
raise ConfigEntryNotReady() from exc
hass.data[DOMAIN] = {entry.entry_id: {TUYA_HA_TUYA_MAP: {}, TUYA_HA_DEVICES: set()}}
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,
)
success = await _init_tuya_sdk(hass, entry)
if not success:
return False
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)
)
_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)
)
@callback
def _async_cancel_tuya_tracker(event):
domain_data[TUYA_TRACKER]() # pylint: disable=not-callable
domain_data[STOP_CANCEL] = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _async_cancel_tuya_tracker
)
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)
hass.services.async_register(DOMAIN, SERVICE_FORCE_UPDATE, async_force_update)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def _init_tuya_sdk(hass: HomeAssistant, entry: ConfigEntry) -> bool:
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,
)
api.set_dev_channel("hass")
if project_type == ProjectType.INDUSTY_SOLUTIONS:
response = await hass.async_add_executor_job(
api.login, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]
)
else:
response = 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("Tuya login error response: %s", response)
return False
tuya_mq = TuyaOpenMQ(api)
tuya_mq.start()
device_manager = TuyaDeviceManager(api, tuya_mq)
# 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][entry.entry_id][TUYA_HOME_MANAGER] = home_manager
listener = DeviceListener(hass, entry)
hass.data[DOMAIN][entry.entry_id][TUYA_MQTT_LISTENER] = listener
device_manager.add_device_listener(listener)
hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER] = device_manager
# Clean up device entities
await cleanup_device_registry(hass, entry)
_LOGGER.debug("init support type->%s", PLATFORMS)
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True
async def cleanup_device_registry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Remove deleted device registry entry if there are no remaining entities."""
device_registry_object = device_registry.async_get(hass)
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
for dev_id, device_entry in list(device_registry_object.devices.items()):
for item in device_entry.identifiers:
if DOMAIN == item[0] and item[1] not in device_manager.device_map:
device_registry_object.async_remove_device(dev_id)
break
@callback
def async_remove_hass_device(hass: HomeAssistant, device_id: str) -> None:
"""Remove device from hass cache."""
device_registry_object = device_registry.async_get(hass)
for device_entry in list(device_registry_object.devices.values()):
if device_id in list(device_entry.identifiers)[0]:
device_registry_object.async_remove_device(device_entry.id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""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.debug("integration unload")
unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload:
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
device_manager.mq.stop()
device_manager.remove_device_listener(
hass.data[DOMAIN][entry.entry_id][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)
class DeviceListener(TuyaDeviceListener):
"""Device Update Listener."""
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Init DeviceListener."""
async def cleanup_device_registry(hass: HomeAssistant, device_id):
"""Remove device registry entry if there are no remaining entities."""
self.hass = hass
self.entry = entry
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:
def update_device(self, device: TuyaDevice) -> None:
"""Update device status."""
if device.id in self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_DEVICES]:
_LOGGER.debug(
"Configuration for deviceID %s: %s", self.object_id, str(dev_conf)
"_update-->%s;->>%s",
self,
device.id,
)
return dev_conf
dispatcher_send(self.hass, f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{device.id}")
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()
def add_device(self, device: TuyaDevice) -> None:
"""Add device added listener."""
device_add = False
async def async_will_remove_from_hass(self):
"""Call when entity is removed from hass."""
self._dec_device_count()
if device.category in itertools.chain(
*self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_TUYA_MAP].values()
):
ha_tuya_map = self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_TUYA_MAP]
self.hass.add_job(async_remove_hass_device, self.hass, device.id)
@property
def object_id(self):
"""Return Tuya device id."""
return self._tuya.object_id()
for domain, tuya_list in ha_tuya_map.items():
if device.category in tuya_list:
device_add = True
_LOGGER.debug(
"Add device category->%s; domain-> %s",
device.category,
domain,
)
self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_DEVICES].add(
device.id
)
dispatcher_send(
self.hass, TUYA_DISCOVERY_NEW.format(domain), [device.id]
)
@property
def unique_id(self):
"""Return a unique ID."""
return f"tuya.{self._tuya.object_id()}"
if device_add:
device_manager = self.hass.data[DOMAIN][self.entry.entry_id][
TUYA_DEVICE_MANAGER
]
device_manager.mq.stop()
tuya_mq = TuyaOpenMQ(device_manager.api)
tuya_mq.start()
@property
def name(self):
"""Return Tuya device name."""
return self._tuya.name()
device_manager.mq = tuya_mq
tuya_mq.add_message_listener(device_manager.on_message)
@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)
def remove_device(self, device_id: str) -> None:
"""Add device removed listener."""
_LOGGER.debug("tuya remove device:%s", device_id)
self.hass.add_job(async_remove_hass_device, self.hass, device_id)

View File

@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Tuya Home Assistant Base Device Model."""
from __future__ import annotations
from typing import Any
from tuya_iot import TuyaDevice, TuyaDeviceManager
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from .const import DOMAIN, TUYA_HA_SIGNAL_UPDATE_ENTITY
class TuyaHaEntity(Entity):
"""Tuya base device."""
def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
"""Init TuyaHaEntity."""
super().__init__()
self.tuya_device = device
self.tuya_device_manager = device_manager
@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.tuya_device.id}")},
"manufacturer": "Tuya",
"name": self.tuya_device.name,
"model": self.tuya_device.product_name,
}
return _device_info
@property
def available(self) -> bool:
"""Return if the device is available."""
return self.tuya_device.online
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.async_on_remove(
async_dispatcher_connect(
self.hass,
f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{self.tuya_device.id}",
self.async_write_ha_state,
)
)
def _send_command(self, commands: list[dict[str, Any]]) -> None:
"""Send command to the device."""
self.tuya_device_manager.send_commands(self.tuya_device.id, commands)

View File

@ -1,261 +1,484 @@
"""Support for the Tuya climate devices."""
from datetime import timedelta
"""Support for Tuya Climate."""
from homeassistant.components.climate import (
DOMAIN as SENSOR_DOMAIN,
ENTITY_ID_FORMAT,
ClimateEntity,
)
from __future__ import annotations
import json
import logging
from typing import Any
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,
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, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TuyaDevice
from .base import TuyaHaEntity
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"
_LOGGER = logging.getLogger(__name__)
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",
# 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"
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35
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()}
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: AddEntitiesCallback
) -> None:
"""Set up tuya climate dynamically through tuya discovery."""
_LOGGER.debug("climate init")
platform = config_entry.data[CONF_PLATFORM]
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
DEVICE_DOMAIN
] = TUYA_SUPPORT_TYPE
async def async_discover_sensor(dev_ids):
"""Discover and add a discovered tuya sensor."""
@callback
def async_discover_device(dev_ids: list[str]) -> None:
"""Discover and add a discovered tuya climate."""
_LOGGER.debug("climate add-> %s", dev_ids)
if not dev_ids:
return
entities = await hass.async_add_executor_job(
_setup_entities,
hass,
dev_ids,
platform,
)
entities = _setup_entities(hass, entry, dev_ids)
async_add_entities(entities)
async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
entry.async_on_unload(
async_dispatcher_connect(
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][entry.entry_id][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)
async_discover_device(device_ids)
def _setup_entities(hass, dev_ids, platform):
"""Set up Tuya Climate device."""
tuya = hass.data[DOMAIN][TUYA_DATA]
entities = []
for dev_id in dev_ids:
device = tuya.get_device_by_id(dev_id)
def _setup_entities(
hass: HomeAssistant, entry: ConfigEntry, device_ids: list[str]
) -> list[Entity]:
"""Set up Tuya Climate."""
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
entities: list[Entity] = []
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))
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(device_id)
return entities
class TuyaClimateEntity(TuyaDevice, ClimateEntity):
"""Tuya climate devices,include air conditioner,heater."""
class TuyaHaClimate(TuyaHaEntity, ClimateEntity):
"""Tuya Switch Device."""
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
def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
"""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
self.dp_temp_unit = DPCODE_TEMP_UNIT_CONVERT
async def async_added_to_hass(self):
"""Create operation list when add to hass."""
await super().async_added_to_hass()
self._process_config()
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
)
)
modes = self._tuya.operation_list()
if modes is None:
if self._def_hvac_mode not in self.operations:
self.operations.append(self._def_hvac_mode)
return
for mode in modes:
if mode not in TUYA_STATE_TO_HA:
continue
ha_mode = TUYA_STATE_TO_HA[mode]
if ha_mode not in self.operations:
self.operations.append(ha_mode)
self._has_operation = True
@property
def 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
@property
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
if not self._tuya.state():
return HVAC_MODE_OFF
if not self._has_operation:
return self._def_hvac_mode
mode = self._tuya.current_operation()
if mode is None:
def get_temp_set_scale(self) -> int | None:
"""Get temperature set scale."""
dp_temp_set = DPCODE_TEMP_SET if self.is_celsius() else DPCODE_TEMP_SET_F
temp_set_value_range_item = self.tuya_device.status_range.get(dp_temp_set)
if not temp_set_value_range_item:
return None
return TUYA_STATE_TO_HA.get(mode)
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
return self.operations
temp_set_value_range = json.loads(temp_set_value_range_item.values)
return temp_set_value_range.get("scale")
@property
def current_temperature(self):
"""Return the current temperature."""
return self._tuya.current_temperature()
def get_temp_current_scale(self) -> int | None:
"""Get temperature current scale."""
dp_temp_current = (
DPCODE_TEMP_CURRENT if self.is_celsius() else DPCODE_TEMP_CURRENT_F
)
temp_current_value_range_item = self.tuya_device.status_range.get(
dp_temp_current
)
if not temp_current_value_range_item:
return None
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._tuya.target_temperature()
temp_current_value_range = json.loads(temp_current_value_range_item.values)
return temp_current_value_range.get("scale")
@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()
# Functions
@property
def fan_mode(self):
"""Return the fan setting."""
return self._tuya.current_fan_mode()
@property
def fan_modes(self):
"""Return the list of available fan modes."""
return self._tuya.fan_list()
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)
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
self._tuya.set_fan_mode(fan_mode)
def set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
commands = []
if hvac_mode == HVAC_MODE_OFF:
self._tuya.turn_off()
commands.append({"code": DPCODE_SWITCH, "value": False})
else:
commands.append({"code": DPCODE_SWITCH, "value": True})
for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items():
if ha_mode == hvac_mode:
commands.append({"code": DPCODE_MODE, "value": tuya_mode})
break
self._send_command(commands)
def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
self._send_command([{"code": DPCODE_FAN_SPEED_ENUM, "value": fan_mode}])
def set_humidity(self, humidity: float) -> None:
"""Set new target humidity."""
self._send_command([{"code": DPCODE_HUMIDITY_SET, "value": int(humidity)}])
def set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
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: Any) -> None:
"""Set new target temperature."""
_LOGGER.debug("climate temp-> %s", kwargs)
code = DPCODE_TEMP_SET if self.is_celsius() else DPCODE_TEMP_SET_F
temp_set_scale = self.get_temp_set_scale()
if not temp_set_scale:
return
if not self._tuya.state():
self._tuya.turn_on()
if self._has_operation:
self._tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode))
@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
@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()
self._send_command(
[
{
"code": code,
"value": int(kwargs["temperature"] * (10 ** temp_set_scale)),
}
]
)
if min_temp is not None:
return min_temp
return super().min_temp
def is_celsius(self) -> bool:
"""Return True if device reports in Celsius."""
if (
self.dp_temp_unit in self.tuya_device.status
and self.tuya_device.status.get(self.dp_temp_unit).lower() == "c"
):
return True
if (
DPCODE_TEMP_SET in self.tuya_device.status
or DPCODE_TEMP_CURRENT in self.tuya_device.status
):
return True
return False
@property
def max_temp(self):
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) -> float | None:
"""Return the 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
temp_current_scale = self.get_temp_current_scale()
if not temp_current_scale:
return None
if self.is_celsius():
temperature = self.tuya_device.status.get(DPCODE_TEMP_CURRENT)
if not temperature:
return None
return temperature * 1.0 / (10 ** temp_current_scale)
temperature = self.tuya_device.status.get(DPCODE_TEMP_CURRENT_F)
if not temperature:
return None
return temperature * 1.0 / (10 ** temp_current_scale)
@property
def current_humidity(self) -> int:
"""Return the current humidity."""
return int(self.tuya_device.status.get(DPCODE_HUMIDITY_CURRENT, 0))
@property
def target_temperature(self) -> float | None:
"""Return the temperature currently set to be reached."""
temp_set_scale = self.get_temp_set_scale()
if temp_set_scale is None:
return None
dpcode_temp_set = self.tuya_device.status.get(DPCODE_TEMP_SET)
if dpcode_temp_set is None:
return None
return dpcode_temp_set * 1.0 / (10 ** temp_set_scale)
@property
def max_temp(self) -> float:
"""Return the maximum temperature."""
max_temp = (
self._max_temp if self._max_temp is not None else self._tuya.max_temp()
scale = self.get_temp_set_scale()
if scale is None:
return DEFAULT_MAX_TEMP
if self.is_celsius():
if DPCODE_TEMP_SET not in self.tuya_device.function:
return DEFAULT_MAX_TEMP
function_item = self.tuya_device.function.get(DPCODE_TEMP_SET)
if function_item is None:
return DEFAULT_MAX_TEMP
temp_value = json.loads(function_item.values)
temp_max = temp_value.get("max")
if temp_max is None:
return DEFAULT_MAX_TEMP
return temp_max * 1.0 / (10 ** scale)
if DPCODE_TEMP_SET_F not in self.tuya_device.function:
return DEFAULT_MAX_TEMP
function_item_f = self.tuya_device.function.get(DPCODE_TEMP_SET_F)
if function_item_f is None:
return DEFAULT_MAX_TEMP
temp_value_f = json.loads(function_item_f.values)
temp_max_f = temp_value_f.get("max")
if temp_max_f is None:
return DEFAULT_MAX_TEMP
return temp_max_f * 1.0 / (10 ** scale)
@property
def min_temp(self) -> float:
"""Return the minimum temperature."""
temp_set_scal = self.get_temp_set_scale()
if temp_set_scal is None:
return DEFAULT_MIN_TEMP
if self.is_celsius():
if DPCODE_TEMP_SET not in self.tuya_device.function:
return DEFAULT_MIN_TEMP
function_temp_item = self.tuya_device.function.get(DPCODE_TEMP_SET)
if function_temp_item is None:
return DEFAULT_MIN_TEMP
temp_value = json.loads(function_temp_item.values)
temp_min = temp_value.get("min")
if temp_min is None:
return DEFAULT_MIN_TEMP
return temp_min * 1.0 / (10 ** temp_set_scal)
if DPCODE_TEMP_SET_F not in self.tuya_device.function:
return DEFAULT_MIN_TEMP
temp_value_temp_f = self.tuya_device.function.get(DPCODE_TEMP_SET_F)
if temp_value_temp_f is None:
return DEFAULT_MIN_TEMP
temp_value_f = json.loads(temp_value_temp_f.values)
temp_min_f = temp_value_f.get("min")
if temp_min_f is None:
return DEFAULT_MIN_TEMP
return temp_min_f * 1.0 / (10 ** temp_set_scal)
@property
def target_temperature_step(self) -> float | None:
"""Return target temperature setp."""
if (
DPCODE_TEMP_SET not in self.tuya_device.status_range
and DPCODE_TEMP_SET_F not in self.tuya_device.status_range
):
return 1.0
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
)
if max_temp is not None:
return max_temp
return super().max_temp
step = temp_set_value_range.get("step")
if step is None:
return None
temp_set_scale = self.get_temp_set_scale()
if temp_set_scale is None:
return None
return step * 1.0 / (10 ** temp_set_scale)
@property
def target_humidity(self) -> int:
"""Return target humidity."""
return int(self.tuya_device.status.get(DPCODE_HUMIDITY_SET, 0))
@property
def hvac_mode(self) -> str:
"""Return hvac mode."""
if not self.tuya_device.status.get(DPCODE_SWITCH, False):
return HVAC_MODE_OFF
if DPCODE_MODE not in self.tuya_device.status:
return HVAC_MODE_OFF
if self.tuya_device.status.get(DPCODE_MODE) is not None:
return TUYA_HVAC_TO_HA[self.tuya_device.status[DPCODE_MODE]]
return HVAC_MODE_OFF
@property
def hvac_modes(self) -> list[str]:
"""Return hvac modes for select."""
if DPCODE_MODE not in self.tuya_device.function:
return []
modes = json.loads(self.tuya_device.function.get(DPCODE_MODE, {}).values).get(
"range"
)
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 fan_mode(self) -> str | None:
"""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[str]:
"""Return swing mode for select."""
return [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH]
@property
def supported_features(self) -> int:
"""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 |= SUPPORT_TARGET_TEMPERATURE
if DPCODE_FAN_SPEED_ENUM in self.tuya_device.status:
supports |= SUPPORT_FAN_MODE
if DPCODE_HUMIDITY_SET in self.tuya_device.status:
supports |= SUPPORT_TARGET_HUMIDITY
if (
DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status
or DPCODE_SWITCH_VERTICAL in self.tuya_device.status
):
supports |= SUPPORT_SWING_MODE
return supports

View File

@ -1,409 +1,140 @@
#!/usr/bin/env python3
"""Config flow for Tuya."""
from __future__ import annotations
import logging
from typing import Any
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"
TUYA_ENDPOINT_BASE = "https://openapi.tuyacn.com"
_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(
# INDUSTY_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
"""Init tuya config flow."""
super().__init__()
self.conf_project_type = 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,
},
@staticmethod
def _try_login(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,
)
api.set_dev_channel("hass")
def _try_connect(self):
"""Try to connect and check auth."""
tuya = TuyaApi()
try:
tuya.init(
self._username, self._password, self._country_code, self._platform
if project_type == ProjectType.INDUSTY_SOLUTIONS:
response = api.login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
else:
api.endpoint = TUYA_ENDPOINT_BASE
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
if response.get("success", False) and isinstance(
api.token_info.platform_url, str
):
api.endpoint = api.token_info.platform_url
user_input[CONF_ENDPOINT] = api.token_info.platform_url
return RESULT_SUCCESS
_LOGGER.debug("TuyaConfigFlow._try_login finish, response:, %s", response)
return response
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)
"""Step user."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA_PROJECT_TYPE
)
self.conf_project_type = user_input[CONF_PROJECT_TYPE]
return await self.async_step_login()
async def async_step_login(self, user_input=None):
"""Step login."""
errors = {}
if user_input is not None:
assert self.conf_project_type is not None
user_input[CONF_PROJECT_TYPE] = self.conf_project_type
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: dict[str, Any] = {}
self._form_error = None
def _get_form_error(self):
"""Set the error to be shown in the options form."""
errors = {}
if self._form_error:
errors["base"] = self._form_error
self._form_error = None
return errors
def _get_tuya_devices_filtered(self, types, exclude_mode=False, type_prefix=True):
"""Get the list of Tuya device to filtered by types."""
config_list = {}
types_filter = set(types)
tuya = self.hass.data[DOMAIN][TUYA_DATA]
devices_list = tuya.get_all_devices()
for device in devices_list:
dev_type = device.device_type()
exclude = (
dev_type in types_filter
if exclude_mode
else dev_type not in types_filter
)
if exclude:
continue
dev_id = device.object_id()
if type_prefix:
dev_id = f"{dev_type}-{dev_id}"
config_list[dev_id] = f"{device.name()} ({dev_type})"
return config_list
def _get_device(self, dev_id):
"""Get specific device from tuya library."""
tuya = self.hass.data[DOMAIN][TUYA_DATA]
return tuya.get_device_by_id(dev_id)
def _save_config(self, data):
"""Save the updated options."""
curr_conf = self.config_entry.options.copy()
curr_conf.update(data)
curr_conf.update(self._conf_devs_option)
return self.async_create_entry(title="", data=curr_conf)
async def _async_device_form(self, devs_id):
"""Return configuration form for devices."""
conf_devs_id = []
for count, dev_id in enumerate(devs_id):
device_info = dev_id.split("-")
if count == 0:
device_type = device_info[0]
device_id = device_info[1]
elif device_type != device_info[0]:
self._form_error = ERROR_DEV_MULTI_TYPE
return await self.async_step_init()
conf_devs_id.append(device_info[1])
device = self._get_device(device_id)
if not device:
self._form_error = ERROR_DEV_NOT_FOUND
return await self.async_step_init()
curr_conf = self._conf_devs_option.get(
device_id, self.config_entry.options.get(device_id, {})
)
config_schema = self._get_device_schema(device_type, curr_conf, device)
if not config_schema:
self._form_error = ERROR_DEV_NOT_CONFIG
return await self.async_step_init()
self._conf_devs_id = conf_devs_id
device_name = (
"(multiple devices selected)" if len(conf_devs_id) > 1 else device.name()
)
return self.async_show_form(
step_id="device",
data_schema=config_schema,
description_placeholders={
"device_type": device_type,
"device_name": device_name,
},
)
async def async_step_init(self, user_input=None):
"""Handle options flow."""
if 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 user_input is not None:
dev_ids = user_input.get(CONF_LIST_DEVICES)
if dev_ids:
return await self.async_step_device(None, dev_ids)
user_input.pop(CONF_LIST_DEVICES, [])
return self._save_config(data=user_input)
data_schema = vol.Schema(
{
vol.Optional(
CONF_DISCOVERY_INTERVAL,
default=self.config_entry.options.get(
CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL
),
): vol.All(vol.Coerce(int), vol.Clamp(min=30, max=900)),
}
)
query_devices = self._get_tuya_devices_filtered(
TUYA_TYPE_NOT_QUERY, True, False
)
if query_devices:
devices = {ENTITY_MATCH_NONE: "Default"}
devices.update(query_devices)
def_val = self.config_entry.options.get(CONF_QUERY_DEVICE)
if not def_val or not query_devices.get(def_val):
def_val = ENTITY_MATCH_NONE
data_schema = data_schema.extend(
{
vol.Optional(
CONF_QUERY_INTERVAL,
default=self.config_entry.options.get(
CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL
),
): vol.All(vol.Coerce(int), vol.Clamp(min=30, max=240)),
vol.Optional(CONF_QUERY_DEVICE, default=def_val): vol.In(devices),
}
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.debug("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 ProjectType(self.conf_project_type) == ProjectType.SMART_HOME:
return self.async_show_form(
step_id="login", data_schema=DATA_SCHEMA_SMART_HOME, errors=errors
)
return self.async_show_form(
step_id="init",
data_schema=data_schema,
errors=self._get_form_error(),
step_id="login",
data_schema=DATA_SCHEMA_INDUSTRY_SOLUTIONS,
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

View File

@ -1,39 +1,37 @@
#!/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_PLATFORMS = {
"tuya": "Tuya",
"smart_life": "Smart Life",
"jinvoo_smart": "Jinvoo Smart",
TUYA_HA_SIGNAL_UPDATE_ENTITY = "tuya_entry_update"
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"}
PLATFORMS = ["climate", "fan", "light", "scene", "switch"]

View File

@ -1,118 +0,0 @@
"""Support for Tuya covers."""
from datetime import timedelta
from homeassistant.components.cover import (
DOMAIN as SENSOR_DOMAIN,
ENTITY_ID_FORMAT,
SUPPORT_CLOSE,
SUPPORT_OPEN,
SUPPORT_STOP,
CoverEntity,
)
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import TuyaDevice
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
SCAN_INTERVAL = timedelta(seconds=15)
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_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)
async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
)
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN)
await async_discover_sensor(devices_ids)
def _setup_entities(hass, dev_ids, platform):
"""Set up Tuya Cover device."""
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(TuyaCover(device, platform))
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
@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
@property
def is_opening(self):
"""Return if the cover is opening or not."""
state = self._tuya.state()
if state == 1:
self._was_opening = True
self._was_closing = False
return True
return False
@property
def is_closing(self):
"""Return if the cover is closing or not."""
state = self._tuya.state()
if state == 2:
self._was_opening = False
self._was_closing = True
return True
return False
@property
def is_closed(self):
"""Return if the cover is closed or not."""
state = self._tuya.state()
if state != 2 and self._was_closing:
return True
if state != 1 and self._was_opening:
return False
return None
def open_cover(self, **kwargs):
"""Open the cover."""
self._tuya.open_cover()
def close_cover(self, **kwargs):
"""Close cover."""
self._tuya.close_cover()
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()

View File

@ -1,141 +1,259 @@
"""Support for Tuya fans."""
"""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, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
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 TuyaHaEntity
from .const import (
DOMAIN,
TUYA_DEVICE_MANAGER,
TUYA_DISCOVERY_NEW,
TUYA_HA_DEVICES,
TUYA_HA_TUYA_MAP,
)
SCAN_INTERVAL = timedelta(seconds=15)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up tuya sensors dynamically through tuya discovery."""
# 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"
platform = config_entry.data[CONF_PLATFORM]
# 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_discover_sensor(dev_ids):
"""Discover and add a discovered tuya sensor."""
TUYA_SUPPORT_TYPE = {
"fs", # Fan
"kj", # Air Purifier
}
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
):
"""Set up tuya fan dynamically through tuya discovery."""
_LOGGER.debug("fan init")
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
DEVICE_DOMAIN
] = TUYA_SUPPORT_TYPE
@callback
def async_discover_device(dev_ids: list[str]) -> None:
"""Discover and add a discovered tuya fan."""
_LOGGER.debug("fan add-> %s", dev_ids)
if not dev_ids:
return
entities = await hass.async_add_executor_job(
_setup_entities,
hass,
dev_ids,
platform,
)
entities = _setup_entities(hass, entry, dev_ids)
async_add_entities(entities)
async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
entry.async_on_unload(
async_dispatcher_connect(
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][entry.entry_id][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)
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: HomeAssistant, entry: ConfigEntry, device_ids: list[str]
) -> list[TuyaHaFan]:
"""Set up Tuya Fan."""
device_manager = hass.data[DOMAIN][entry.entry_id][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))
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(device_id)
return entities
class TuyaFanDevice(TuyaDevice, FanEntity):
"""Tuya fan devices."""
class TuyaHaFan(TuyaHaEntity, 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) -> None:
"""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())
self.ha_preset_modes = []
if DPCODE_MODE in self.tuya_device.function:
self.ha_preset_modes = json.loads(
self.tuya_device.function[DPCODE_MODE].values
).get("range", [])
# 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" and (
DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.function
or DPCODE_AP_FAN_SPEED in self.tuya_device.function
):
if DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.function:
self.dp_code_speed_enum = DPCODE_AP_FAN_SPEED_ENUM
else:
self.dp_code_speed_enum = DPCODE_AP_FAN_SPEED
data = json.loads(
self.tuya_device.function[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
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()
self._send_command([{"code": DPCODE_SWITCH, "value": True}])
def turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
self._tuya.turn_off()
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
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."""
if self.tuya_device.status[DPCODE_FAN_DIRECTION]:
return DIRECTION_FORWARD
return DIRECTION_REVERSE
@property
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[str]:
"""Return the list of available preset_modes."""
return self.ha_preset_modes
@property
def preset_mode(self) -> str:
"""Return the current preset_mode."""
return self.tuya_device.status[DPCODE_MODE]
@property
def percentage(self) -> int:
"""Return the current speed."""
if not self.is_on:
return 0
if (
self.tuya_device.category == "kj"
and self.air_purifier_speed_range_len > 1
and not self.air_purifier_speed_range_enum
and DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.status
):
# 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[DPCODE_AP_FAN_SPEED_ENUM],
)
return self.tuya_device.status[DPCODE_FAN_SPEED]
@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)
if self.tuya_device.category == "kj":
return self.air_purifier_speed_range_len
return super().speed_count
@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()
@property
def is_on(self):
"""Return true if the entity is on."""
return self._tuya.state()
@property
def percentage(self) -> int | None:
"""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())
@property
def supported_features(self) -> int:
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 |= SUPPORT_PRESET_MODE
if DPCODE_FAN_SPEED in self.tuya_device.status:
supports |= SUPPORT_SET_SPEED
if DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status:
supports |= SUPPORT_OSCILLATE
if DPCODE_FAN_DIRECTION in self.tuya_device.status:
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 |= SUPPORT_SET_SPEED
return supports

View File

@ -1,198 +1,387 @@
"""Support for the Tuya lights."""
from datetime import timedelta
from __future__ import annotations
import json
import logging
from typing import Any
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,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
COLOR_MODE_ONOFF,
DOMAIN as DEVICE_DOMAIN,
LightEntity,
)
from homeassistant.const import CONF_PLATFORM
from homeassistant.core import callback
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util import color as colorutil
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TuyaDevice
from .base import TuyaHaEntity
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__)
TUYA_BRIGHTNESS_RANGE0 = (1, 255)
TUYA_BRIGHTNESS_RANGE1 = (10, 1000)
BRIGHTNESS_MODES = {
0: TUYA_BRIGHTNESS_RANGE0,
1: TUYA_BRIGHTNESS_RANGE1,
# 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"
DPCODE_LIGHT = "light"
MIREDS_MAX = 500
MIREDS_MIN = 153
HSV_HA_HUE_MIN = 0
HSV_HA_HUE_MAX = 360
HSV_HA_SATURATION_MIN = 0
HSV_HA_SATURATION_MAX = 100
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
"xdd", # Ceiling Light
"xxj", # Diffuser's light
"fs", # Fan
}
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."""
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up tuya light dynamically through tuya discovery."""
_LOGGER.debug("light init")
platform = config_entry.data[CONF_PLATFORM]
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
DEVICE_DOMAIN
] = TUYA_SUPPORT_TYPE
async def async_discover_sensor(dev_ids):
"""Discover and add a discovered tuya sensor."""
@callback
def async_discover_device(dev_ids: list[str]):
"""Discover and add a discovered tuya light."""
_LOGGER.debug("light add-> %s", dev_ids)
if not dev_ids:
return
entities = await hass.async_add_executor_job(
_setup_entities,
hass,
dev_ids,
platform,
)
entities = _setup_entities(hass, entry, dev_ids)
async_add_entities(entities)
async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
entry.async_on_unload(
async_dispatcher_connect(
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][entry.entry_id][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)
async_discover_device(device_ids)
def _setup_entities(hass, dev_ids, platform):
def _setup_entities(
hass, entry: ConfigEntry, device_ids: list[str]
) -> list[TuyaHaLight]:
"""Set up Tuya Light device."""
tuya = hass.data[DOMAIN][TUYA_DATA]
device_manager = hass.data[DOMAIN][entry.entry_id][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))
tuya_ha_light = TuyaHaLight(device, device_manager)
entities.append(tuya_ha_light)
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(
tuya_ha_light.tuya_device.id
)
return entities
class TuyaLight(TuyaDevice, LightEntity):
class TuyaHaLight(TuyaHaEntity, 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()
def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
"""Init TuyaHaLight."""
self.dp_code_bright = DPCODE_BRIGHT_VALUE
self.dp_code_temp = DPCODE_TEMP_VALUE
self.dp_code_colour = DPCODE_COLOUR_DATA
@callback
def _process_config(self):
"""Set device config parameter."""
config = self._get_device_config()
if not config:
return
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
# support color config
supp_color = config.get(CONF_SUPPORT_COLOR, False)
if supp_color:
self._tuya.force_support_color()
# brightness range config
self._tuya.brightness_white_range = BRIGHTNESS_MODES.get(
config.get(CONF_BRIGHTNESS_RANGE_MODE, 0),
TUYA_BRIGHTNESS_RANGE0,
)
# color set temp range
min_tuya = self._tuya.max_color_temp()
min_kelvin = config.get(CONF_MIN_KELVIN, min_tuya)
max_tuya = self._tuya.min_color_temp()
max_kelvin = config.get(CONF_MAX_KELVIN, max_tuya)
self._min_kelvin = min(max(min_kelvin, min_tuya), max_tuya)
self._max_kelvin = min(max(max_kelvin, self._min_kelvin), max_tuya)
# color shown temp range
max_color_temp = max(
config.get(CONF_TUYA_MAX_COLTEMP, DEFAULT_TUYA_MAX_COLTEMP),
DEFAULT_TUYA_MAX_COLTEMP,
)
self._tuya.color_temp_range = (1000, max_color_temp)
async def async_added_to_hass(self):
"""Set config parameter when add to hass."""
await super().async_added_to_hass()
self._process_config()
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
)
)
return
super().__init__(device, device_manager)
@property
def brightness(self):
"""Return the brightness of the light."""
if self._tuya.brightness() is None:
return None
return int(self._tuya.brightness())
@property
def hs_color(self):
"""Return the hs_color of the light."""
return tuple(map(int, self._tuya.hs_color()))
@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):
def is_on(self) -> bool:
"""Return true if light is on."""
return self._tuya.state()
return self.tuya_device.status.get(DPCODE_SWITCH, False)
@property
def min_mireds(self):
"""Return color temperature min mireds."""
return colorutil.color_temperature_kelvin_to_mired(self._max_kelvin)
@property
def max_mireds(self):
"""Return color temperature max mireds."""
return colorutil.color_temperature_kelvin_to_mired(self._min_kelvin)
def turn_on(self, **kwargs):
def turn_on(self, **kwargs: Any) -> None:
"""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]
)
self._tuya.set_color_temp(color_temp)
commands = []
_LOGGER.debug("light kwargs-> %s", kwargs)
def turn_off(self, **kwargs):
if (
DPCODE_LIGHT in self.tuya_device.status
and DPCODE_SWITCH not in self.tuya_device.status
):
commands += [{"code": DPCODE_LIGHT, "value": True}]
else:
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()
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}]
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],
)
)
# 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."""
self._tuya.turn_off()
if (
DPCODE_LIGHT in self.tuya_device.status
and DPCODE_SWITCH not in self.tuya_device.status
):
commands = [{"code": DPCODE_LIGHT, "value": False}]
else:
commands = [{"code": DPCODE_SWITCH, "value": False}]
self._send_command(commands)
@property
def supported_features(self):
"""Flag supported features."""
supports = SUPPORT_BRIGHTNESS
if self._tuya.support_color():
supports = supports | SUPPORT_COLOR
if self._tuya.support_color_temp():
supports = supports | SUPPORT_COLOR_TEMP
return supports
def brightness(self) -> int | None:
"""Return the brightness of the light."""
old_range = self._tuya_brightness_range()
brightness = self.tuya_device.status.get(self.dp_code_bright, 0)
if self._work_mode().startswith(WORK_MODE_COLOUR):
colour_json = self.tuya_device.status.get(self.dp_code_colour)
if not colour_json:
return None
colour_data = json.loads(colour_json)
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_item = self.tuya_device.function.get(self.dp_code_bright)
if not bright_item:
return 0, 255
bright_value = json.loads(bright_item.values)
return bright_value.get("min", 0), bright_value.get("max", 255)
@property
def hs_color(self) -> tuple[float, float] | None:
"""Return the hs_color of the light."""
colour_json = self.tuya_device.status.get(self.dp_code_colour)
if not colour_json:
return None
colour_data = json.loads(colour_json)
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) -> int:
"""Return the color_temp of the light."""
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) -> int:
"""Return color temperature min mireds."""
return MIREDS_MIN
@property
def max_mireds(self) -> int:
"""Return color temperature max mireds."""
return MIREDS_MAX
def _tuya_temp_range(self) -> tuple[int, int]:
temp_item = self.tuya_device.function.get(self.dp_code_temp)
if not temp_item:
return 0, 255
temp_value = json.loads(temp_item.values)
return temp_value.get("min", 0), temp_value.get("max", 255)
def _tuya_hsv_s_range(self) -> tuple[int, int]:
hsv_data_range = self._tuya_hsv_function()
if hsv_data_range is not None:
hsv_s = hsv_data_range.get("s", {"min": 0, "max": 255})
return hsv_s.get("min", 0), hsv_s.get("max", 255)
return 0, 255
def _tuya_hsv_v_range(self) -> tuple[int, int]:
hsv_data_range = self._tuya_hsv_function()
if hsv_data_range is not None:
hsv_v = hsv_data_range.get("v", {"min": 0, "max": 255})
return hsv_v.get("min", 0), hsv_v.get("max", 255)
return 0, 255
def _tuya_hsv_function(self) -> dict[str, dict] | None:
hsv_item = self.tuya_device.function.get(self.dp_code_colour)
if not hsv_item:
return None
hsv_data = json.loads(hsv_item.values)
if hsv_data:
return hsv_data
colour_json = self.tuya_device.status.get(self.dp_code_colour)
if not colour_json:
return None
colour_data = json.loads(colour_json)
if (
self.dp_code_colour == DPCODE_COLOUR_DATA_V2
or colour_data.get("v", 0) > 255
or colour_data.get("s", 0) > 255
):
return DEFAULT_HSV_V2
return DEFAULT_HSV
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_color_modes(self) -> set[str] | None:
"""Flag supported color modes."""
color_modes = [COLOR_MODE_ONOFF]
if self.dp_code_bright in self.tuya_device.status:
color_modes.append(COLOR_MODE_BRIGHTNESS)
if self.dp_code_temp in self.tuya_device.status:
color_modes.append(COLOR_MODE_COLOR_TEMP)
if (
self.dp_code_colour in self.tuya_device.status
and len(self.tuya_device.status[self.dp_code_colour]) > 0
):
color_modes.append(COLOR_MODE_HS)
return set(color_modes)

View File

@ -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.1"
],
"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"
}

View File

@ -1,61 +1,74 @@
"""Support for the Tuya scenes."""
"""Support for Tuya scenes."""
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 . import TuyaDevice
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
from homeassistant.components.scene import Scene
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"
from .const import DOMAIN, TUYA_HOME_MANAGER
_LOGGER = logging.getLogger(__name__)
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_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)
async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
)
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN)
await async_discover_sensor(devices_ids)
def _setup_entities(hass, dev_ids, platform):
"""Set up Tuya Scene."""
tuya = hass.data[DOMAIN][TUYA_DATA]
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up tuya scenes."""
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
home_manager = hass.data[DOMAIN][entry.entry_id][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_add_entities(entities)
class TuyaScene(TuyaDevice, Scene):
"""Tuya Scene."""
class TuyaHAScene(Scene):
"""Tuya Scene Remote."""
def __init__(self, tuya, platform):
"""Init Tuya scene."""
super().__init__(tuya, platform)
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
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."""
return {
"identifiers": {(DOMAIN, f"{self.unique_id}")},
"manufacturer": "tuya",
"name": self.scene.name,
"model": "Tuya Scene",
}
@property
def available(self) -> bool:
"""Return if the scene is enabled."""
return self.scene.enabled
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)

View File

@ -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.

View File

@ -1,64 +1,29 @@
{
"config": {
"flow_title": "Tuya configuration",
"step": {
"user": {
"user":{
"title":"Tuya Integration",
"data":{
"tuya_project_type": "Tuya cloud project type"
}
},
"login": {
"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"
}
}
},
"abort": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"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"
}
}
}
}

View File

@ -1,74 +1,177 @@
#!/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
import logging
from typing import Any
from tuya_iot import TuyaDevice, TuyaDeviceManager
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 homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TuyaDevice
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
from .base import TuyaHaEntity
from .const import (
DOMAIN,
TUYA_DEVICE_MANAGER,
TUYA_DISCOVERY_NEW,
TUYA_HA_DEVICES,
TUYA_HA_TUYA_MAP,
)
SCAN_INTERVAL = timedelta(seconds=15)
_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
async def async_setup_entry(hass, config_entry, async_add_entities):
DPCODE_START = "start"
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up tuya sensors dynamically through tuya discovery."""
_LOGGER.debug("switch init")
platform = config_entry.data[CONF_PLATFORM]
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
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.debug("switch add-> %s", dev_ids)
if not dev_ids:
return
entities = await hass.async_add_executor_job(
_setup_entities,
hass,
dev_ids,
platform,
)
entities = _setup_entities(hass, entry, dev_ids)
async_add_entities(entities)
async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
entry.async_on_unload(
async_dispatcher_connect(
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][entry.entry_id][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, entry: ConfigEntry, device_ids: list[str]) -> list[Entity]:
"""Set up Tuya Switch device."""
tuya = hass.data[DOMAIN][TUYA_DATA]
entities = []
for dev_id in dev_ids:
device = tuya.get_device_by_id(dev_id)
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
entities: list[Entity] = []
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:
tuya_ha_switch = None
if device.category == "kj":
if function in [
DPCODE_ANION,
DPCODE_FRESET,
DPCODE_LIGHT,
DPCODE_LOCK,
DPCODE_UV,
DPCODE_WET,
]:
tuya_ha_switch = 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]:
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
if function.startswith(DPCODE_SWITCH):
# Main device switch
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
else:
if function.startswith(DPCODE_START):
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
if function.startswith(DPCODE_SWITCH):
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
if tuya_ha_switch is not None:
entities.append(tuya_ha_switch)
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(
tuya_ha_switch.tuya_device.id
)
return entities
class TuyaSwitch(TuyaDevice, SwitchEntity):
class TuyaHaSwitch(TuyaHaEntity, 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 is_on(self):
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 f"{self.tuya_device.name}{self.channel}"
@property
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}])

View File

@ -1,8 +0,0 @@
{
"options": {
"error": {
"dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar",
"dev_not_found": "Ger\u00e4t nicht gefunden"
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -1,65 +0,0 @@
{
"config": {
"abort": {
"cannot_connect": "Verbindung fehlgeschlagen",
"invalid_auth": "Ung\u00fcltige Authentifizierung",
"single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich."
},
"error": {
"invalid_auth": "Ung\u00fcltige Authentifizierung"
},
"flow_title": "Tuya Konfiguration",
"step": {
"user": {
"data": {
"country_code": "L\u00e4ndercode deines Kontos (z. B. 1 f\u00fcr USA oder 86 f\u00fcr China)",
"password": "Passwort",
"platform": "Die App, in der dein Konto registriert ist",
"username": "Benutzername"
},
"description": "Gib deine Tuya-Anmeldeinformationen 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\u00e4hle die zu konfigurierenden Ger\u00e4te aus oder lasse sie leer, um die Konfiguration zu speichern",
"query_device": "W\u00e4hle ein Ger\u00e4t aus, das die Abfragemethode f\u00fcr eine schnellere Statusaktualisierung verwendet.",
"query_interval": "Ger\u00e4teabrufintervall in Sekunden"
},
"description": "Stelle das Abfrageintervall nicht zu niedrig ein, sonst schlagen die Aufrufe fehl und erzeugen eine Fehlermeldung im Protokoll",
"title": "Tuya-Optionen konfigurieren"
}
}
}
}

View File

@ -1,65 +1,29 @@
{
"config": {
"abort": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"single_instance_allowed": "Already configured. Only a single configuration possible."
},
"error": {
"invalid_auth": "Invalid authentication"
},
"flow_title": "Tuya configuration",
"step": {
"user": {
"user":{
"title":"Tuya Integration",
"data":{
"tuya_project_type": "Tuya cloud project type"
}
},
"login": {
"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"
}
}
}
}
}

View File

@ -1,65 +0,0 @@
{
"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."
},
"error": {
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida"
},
"flow_title": "Configuraci\u00f3n Tuya",
"step": {
"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"
},
"description": "Introduce tu 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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -1,65 +0,0 @@
{
"config": {
"abort": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide",
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
},
"error": {
"invalid_auth": "Authentification invalide"
},
"flow_title": "Configuration Tuya",
"step": {
"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"
},
"description": "Saisissez vos informations d'identification Tuya.",
"title": "Tuya"
}
}
},
"options": {
"abort": {
"cannot_connect": "\u00c9chec de connexion"
},
"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"
}
}
}
}

View File

@ -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\u05ea \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\u05d9\u05d9\u05e9\u05d5\u05dd \u05d1\u05d5 \u05e8\u05e9\u05d5\u05dd \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05e9\u05dc\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"
}
}
}

View File

@ -1,65 +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)",
"set_temp_divided": "A h\u0151m\u00e9rs\u00e9klet be\u00e1ll\u00edt\u00e1s\u00e1hoz osztott h\u0151m\u00e9rs\u00e9kleti \u00e9rt\u00e9ket haszn\u00e1ljon",
"support_color": "Sz\u00ednt\u00e1mogat\u00e1s k\u00e9nyszer\u00edt\u00e9se",
"temp_divider": "Sz\u00ednh\u0151m\u00e9rs\u00e9klet-\u00e9rt\u00e9kek oszt\u00f3ja (0 = alap\u00e9rtelmezett)",
"temp_step_override": "C\u00e9lh\u0151m\u00e9rs\u00e9klet l\u00e9pcs\u0151",
"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"
}
}
}
}

View File

@ -1,65 +0,0 @@
{
"config": {
"abort": {
"cannot_connect": "Gagal terhubung",
"invalid_auth": "Autentikasi tidak valid",
"single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan."
},
"error": {
"invalid_auth": "Autentikasi tidak valid"
},
"flow_title": "Konfigurasi Tuya",
"step": {
"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"
},
"description": "Masukkan kredensial 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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -1,65 +0,0 @@
{
"config": {
"abort": {
"cannot_connect": "Kan geen verbinding maken",
"invalid_auth": "Ongeldige authenticatie",
"single_instance_allowed": "Al geconfigureerd. Er is maar een configuratie mogelijk."
},
"error": {
"invalid_auth": "Ongeldige authenticatie"
},
"flow_title": "Tuya-configuratie",
"step": {
"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"
},
"description": "Voer uw Tuya-inloggegevens 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 nieuwe apparaten 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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -1,25 +0,0 @@
{
"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."
},
"error": {
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
},
"step": {
"user": {
"data": {
"password": "Palavra-passe",
"username": "Nome de Utilizador"
}
}
}
},
"options": {
"abort": {
"cannot_connect": "Falha na liga\u00e7\u00e3o"
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -1,60 +1,29 @@
{
"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"
},
"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"
"user":{
"title":"Tuya插件",
"data":{
"tuya_project_type": "涂鸦云项目类型"
}
},
"init": {
"login": {
"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": "涂鸦"
}
}
}
}
}

View File

@ -1,65 +0,0 @@
{
"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"
},
"error": {
"invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548"
},
"flow_title": "Tuya \u8a2d\u5b9a",
"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"
},
"init": {
"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"
},
"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"
}
}
}
}

View File

@ -359,30 +359,6 @@ DHCP = [
"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*"

View File

@ -2332,7 +2332,7 @@ tp-connected==0.0.4
transmissionrpc==0.11
# homeassistant.components.tuya
tuyaha==0.0.10
tuya-iot-py-sdk==0.4.1
# homeassistant.components.twentemilieu
twentemilieu==0.3.0

View File

@ -1315,7 +1315,7 @@ total_connect_client==0.57
transmissionrpc==0.11
# homeassistant.components.tuya
tuyaha==0.0.10
tuya-iot-py-sdk==0.4.1
# homeassistant.components.twentemilieu
twentemilieu==0.3.0

View File

@ -1,68 +1,81 @@
"""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.config_flow import RESULT_AUTH_FAILED
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
MOCK_SMART_HOME_PROJECT_TYPE = 0
MOCK_INDUSTRY_PROJECT_TYPE = 1
from tests.common import MockConfigEntry
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"
USERNAME = "myUsername"
PASSWORD = "myPassword"
COUNTRY_CODE = "1"
TUYA_PLATFORM = "tuya"
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_USER_DATA = {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_COUNTRYCODE: COUNTRY_CODE,
CONF_PLATFORM: TUYA_PLATFORM,
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_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_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,8 +86,8 @@ 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}
)
@ -83,192 +96,92 @@ async def test_user(hass, tuya):
assert result["step_id"] == "user"
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_FORM
assert result["step_id"] == "login"
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_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 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_if_already_setup(hass, tuya):
"""Test we abort if Tuya is already setup."""
MockConfigEntry(domain=DOMAIN, data=TUYA_USER_DATA).add_to_hass(hass)
# Should fail, config exist (import)
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
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == RESULT_SINGLE_INSTANCE
async def test_abort_on_invalid_credentials(hass, tuya):
"""Test when we have invalid credentials."""
tuya().init.side_effect = TuyaAPIException("Boom")
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}
assert result["step_id"] == "user"
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
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_SMART_HOME_PROJECT_DATA
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == RESULT_CONN_ERROR
async def test_options_flow(hass):
"""Test config flow options."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=TUYA_USER_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()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "login"
# 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
# Load integration and enter options
await hass.config_entries.async_setup(config_entry.entry_id)
tuya().login = MagicMock(return_value={"success": False, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA
)
await hass.async_block_till_done()
hass.data[DOMAIN] = {TUYA_DATA: MockTuya()}
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
assert result["errors"]["base"] == RESULT_AUTH_FAILED
# 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}"]},
tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_IMPORT_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}
# 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}
# 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}"]},
)
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}"]}
)
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,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
# 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,
},
)
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,
},
)
# Verify results
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
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
async def test_error_on_invalid_credentials(hass, tuya):
"""Test when we have invalid credentials."""
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
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"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_INDUSTRY_PROJECT_DATA
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "login"
tuya().login = MagicMock(return_value={"success": False, "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["errors"]["base"] == RESULT_AUTH_FAILED