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/transmission/errors.py
homeassistant/components/travisci/sensor.py homeassistant/components/travisci/sensor.py
homeassistant/components/tuya/__init__.py homeassistant/components/tuya/__init__.py
homeassistant/components/tuya/base.py
homeassistant/components/tuya/climate.py homeassistant/components/tuya/climate.py
homeassistant/components/tuya/const.py homeassistant/components/tuya/const.py
homeassistant/components/tuya/cover.py
homeassistant/components/tuya/fan.py homeassistant/components/tuya/fan.py
homeassistant/components/tuya/light.py homeassistant/components/tuya/light.py
homeassistant/components/tuya/scene.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/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins homeassistant/components/transmission/* @engrbm87 @JPHutchins
homeassistant/components/tts/* @pvizeli homeassistant/components/tts/* @pvizeli
homeassistant/components/tuya/* @ollo69 homeassistant/components/tuya/* @Tuya
homeassistant/components/twentemilieu/* @frenck homeassistant/components/twentemilieu/* @frenck
homeassistant/components/twinkly/* @dr1rrb homeassistant/components/twinkly/* @dr1rrb
homeassistant/components/ubus/* @noltari homeassistant/components/ubus/* @noltari

View File

@ -1,376 +1,208 @@
#!/usr/bin/env python3
"""Support for Tuya Smart devices.""" """Support for Tuya Smart devices."""
from datetime import timedelta
import itertools
import logging import logging
from tuyaha import TuyaApi from tuya_iot import (
from tuyaha.tuyaapi import ( ProjectType,
TuyaAPIException, TuyaDevice,
TuyaAPIRateLimitException, TuyaDeviceListener,
TuyaFrequentlyInvokeException, TuyaDeviceManager,
TuyaNetException, TuyaHomeManager,
TuyaServerException, TuyaOpenAPI,
TuyaOpenMQ,
) )
from homeassistant.config_entries import ConfigEntry 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.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry
import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send
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 .const import ( from .const import (
CONF_COUNTRYCODE, CONF_ACCESS_ID,
CONF_DISCOVERY_INTERVAL, CONF_ACCESS_SECRET,
CONF_QUERY_DEVICE, CONF_APP_TYPE,
CONF_QUERY_INTERVAL, CONF_COUNTRY_CODE,
DEFAULT_DISCOVERY_INTERVAL, CONF_ENDPOINT,
DEFAULT_QUERY_INTERVAL, CONF_PASSWORD,
CONF_PROJECT_TYPE,
CONF_USERNAME,
DOMAIN, DOMAIN,
SIGNAL_CONFIG_ENTITY, PLATFORMS,
SIGNAL_DELETE_ENTITY, TUYA_DEVICE_MANAGER,
SIGNAL_UPDATE_ENTITY,
TUYA_DATA,
TUYA_DEVICES_CONF,
TUYA_DISCOVERY_NEW, TUYA_DISCOVERY_NEW,
TUYA_PLATFORMS, TUYA_HA_DEVICES,
TUYA_TYPE_NOT_QUERY, TUYA_HA_SIGNAL_UPDATE_ENTITY,
TUYA_HA_TUYA_MAP,
TUYA_HOME_MANAGER,
TUYA_MQTT_LISTENER,
) )
_LOGGER = logging.getLogger(__name__) _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: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Tuya platform.""" """Async setup hass config entry."""
tuya = TuyaApi() _LOGGER.debug("tuya.__init__.async_setup_entry-->%s", entry.data)
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
country_code = entry.data[CONF_COUNTRYCODE]
platform = entry.data[CONF_PLATFORM]
try: hass.data[DOMAIN] = {entry.entry_id: {TUYA_HA_TUYA_MAP: {}, TUYA_HA_DEVICES: set()}}
await hass.async_add_executor_job(
tuya.init, username, password, country_code, platform
)
except (
TuyaNetException,
TuyaServerException,
TuyaFrequentlyInvokeException,
) as exc:
raise ConfigEntryNotReady() from exc
except TuyaAPIRateLimitException as exc: success = await _init_tuya_sdk(hass, entry)
raise ConfigEntryNotReady("Tuya login rate limited") from exc if not success:
except TuyaAPIException as exc:
_LOGGER.error(
"Connection error during integration setup. Error: %s",
exc,
)
return False 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 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.""" """Unloading the Tuya platforms."""
domain_data = hass.data[DOMAIN] _LOGGER.debug("integration unload")
platforms = [platform.split(".", 1)[0] for platform in domain_data[ENTRY_IS_SETUP]] unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms) if unload:
if unload_ok: device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
domain_data["listener"]() device_manager.mq.stop()
domain_data[STOP_CANCEL]() device_manager.remove_device_listener(
domain_data[TUYA_TRACKER]() hass.data[DOMAIN][entry.entry_id][TUYA_MQTT_LISTENER]
hass.services.async_remove(DOMAIN, SERVICE_FORCE_UPDATE) )
hass.services.async_remove(DOMAIN, SERVICE_PULL_DEVICES)
hass.data.pop(DOMAIN) hass.data.pop(DOMAIN)
return unload_ok return unload
async def update_listener(hass: HomeAssistant, entry: ConfigEntry): class DeviceListener(TuyaDeviceListener):
"""Update when config_entry options update.""" """Device Update Listener."""
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)
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Init DeviceListener."""
async def cleanup_device_registry(hass: HomeAssistant, device_id): self.hass = hass
"""Remove device registry entry if there are no remaining entities.""" self.entry = entry
device_registry = await hass.helpers.device_registry.async_get_registry() def update_device(self, device: TuyaDevice) -> None:
entity_registry = await hass.helpers.entity_registry.async_get_registry() """Update device status."""
if device_id and not hass.helpers.entity_registry.async_entries_for_device( if device.id in self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_DEVICES]:
entity_registry, device_id, include_disabled_entities=True
):
device_registry.async_remove_device(device_id)
class TuyaDevice(Entity):
"""Tuya base device."""
_dev_can_query_count = 0
def __init__(self, tuya, platform):
"""Init Tuya devices."""
self._tuya = tuya
self._tuya_platform = platform
def _device_can_query(self):
"""Check if device can also use query method."""
dev_type = self._tuya.device_type()
return dev_type not in TUYA_TYPE_NOT_QUERY
def _inc_device_count(self):
"""Increment static variable device count."""
if not self._device_can_query():
return
TuyaDevice._dev_can_query_count += 1
def _dec_device_count(self):
"""Decrement static variable device count."""
if not self._device_can_query():
return
TuyaDevice._dev_can_query_count -= 1
def _get_device_config(self):
"""Get updated device options."""
devices_config = self.hass.data[DOMAIN].get(TUYA_DEVICES_CONF)
if not devices_config:
return {}
dev_conf = devices_config.get(self.object_id, {})
if dev_conf:
_LOGGER.debug( _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): def add_device(self, device: TuyaDevice) -> None:
"""Call when entity is added to hass.""" """Add device added listener."""
self.hass.data[DOMAIN]["entities"][self.object_id] = self.entity_id device_add = False
self.async_on_remove(
async_dispatcher_connect( if device.category in itertools.chain(
self.hass, SIGNAL_DELETE_ENTITY, self._delete_callback *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)
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
) )
self.async_on_remove( dispatcher_send(
async_dispatcher_connect( self.hass, TUYA_DISCOVERY_NEW.format(domain), [device.id]
self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback
) )
)
self._inc_device_count()
async def async_will_remove_from_hass(self): if device_add:
"""Call when entity is removed from hass.""" device_manager = self.hass.data[DOMAIN][self.entry.entry_id][
self._dec_device_count() TUYA_DEVICE_MANAGER
]
device_manager.mq.stop()
tuya_mq = TuyaOpenMQ(device_manager.api)
tuya_mq.start()
@property device_manager.mq = tuya_mq
def object_id(self): tuya_mq.add_message_listener(device_manager.on_message)
"""Return Tuya device id."""
return self._tuya.object_id()
@property def remove_device(self, device_id: str) -> None:
def unique_id(self): """Add device removed listener."""
"""Return a unique ID.""" _LOGGER.debug("tuya remove device:%s", device_id)
return f"tuya.{self._tuya.object_id()}" self.hass.add_job(async_remove_hass_device, self.hass, device_id)
@property
def name(self):
"""Return Tuya device name."""
return self._tuya.name()
@property
def available(self):
"""Return if the device is available."""
return self._tuya.available()
@property
def device_info(self):
"""Return a device description for device registry."""
_device_info = {
"identifiers": {(DOMAIN, f"{self.unique_id}")},
"manufacturer": TUYA_PLATFORMS.get(
self._tuya_platform, self._tuya_platform
),
"name": self.name,
"model": self._tuya.object_type(),
}
return _device_info
def update(self):
"""Refresh Tuya device data."""
query_dev = self.hass.data[DOMAIN][TUYA_DEVICES_CONF].get(CONF_QUERY_DEVICE, "")
use_discovery = (
TuyaDevice._dev_can_query_count > 1 and self.object_id != query_dev
)
try:
self._tuya.update(use_discovery=use_discovery)
except TuyaFrequentlyInvokeException as exc:
_LOGGER.error(exc)
async def _delete_callback(self, dev_id):
"""Remove this entity."""
if dev_id == self.object_id:
entity_registry = (
await self.hass.helpers.entity_registry.async_get_registry()
)
if entity_registry.async_is_registered(self.entity_id):
entity_entry = entity_registry.async_get(self.entity_id)
entity_registry.async_remove(self.entity_id)
await cleanup_device_registry(self.hass, entity_entry.device_id)
else:
await self.async_remove(force_remove=True)
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state(True)

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.""" """Support for Tuya Climate."""
from datetime import timedelta
from homeassistant.components.climate import ( from __future__ import annotations
DOMAIN as SENSOR_DOMAIN,
ENTITY_ID_FORMAT, import json
ClimateEntity, 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 ( from homeassistant.components.climate.const import (
FAN_HIGH,
FAN_LOW,
FAN_MEDIUM,
HVAC_MODE_AUTO, HVAC_MODE_AUTO,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY, HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_OFF, HVAC_MODE_OFF,
SUPPORT_FAN_MODE, SUPPORT_FAN_MODE,
SUPPORT_SWING_MODE,
SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
) )
from homeassistant.const import ( from homeassistant.config_entries import ConfigEntry
ATTR_TEMPERATURE, from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
CONF_PLATFORM, from homeassistant.core import HomeAssistant, callback
CONF_UNIT_OF_MEASUREMENT,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect 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 ( 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, DOMAIN,
SIGNAL_CONFIG_ENTITY, TUYA_DEVICE_MANAGER,
TUYA_DATA,
TUYA_DISCOVERY_NEW, 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 = { # Air Conditioner
HVAC_MODE_AUTO: "auto", # https://developer.tuya.com/en/docs/iot/f?id=K9gf46qujdmwb
HVAC_MODE_COOL: "cold", DPCODE_SWITCH = "switch"
HVAC_MODE_FAN_ONLY: "wind", DPCODE_TEMP_SET = "temp_set"
HVAC_MODE_HEAT: "hot", 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()} TUYA_SUPPORT_TYPE = {
"kt", # Air conditioner
FAN_MODES = {FAN_LOW, FAN_MEDIUM, FAN_HIGH} "qn", # Heater
"wk", # Thermostat
}
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
"""Set up tuya sensors dynamically through tuya discovery.""" 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): @callback
"""Discover and add a discovered tuya sensor.""" 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: if not dev_ids:
return return
entities = await hass.async_add_executor_job( entities = _setup_entities(hass, entry, dev_ids)
_setup_entities,
hass,
dev_ids,
platform,
)
async_add_entities(entities) async_add_entities(entities)
entry.async_on_unload(
async_dispatcher_connect( async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
)
) )
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
await async_discover_sensor(devices_ids) 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(
"""Set up Tuya Climate device.""" hass: HomeAssistant, entry: ConfigEntry, device_ids: list[str]
tuya = hass.data[DOMAIN][TUYA_DATA] ) -> list[Entity]:
entities = [] """Set up Tuya Climate."""
for dev_id in dev_ids: device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
device = tuya.get_device_by_id(dev_id) entities: list[Entity] = []
for device_id in device_ids:
device = device_manager.device_map[device_id]
if device is None: if device is None:
continue 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 return entities
class TuyaClimateEntity(TuyaDevice, ClimateEntity): class TuyaHaClimate(TuyaHaEntity, ClimateEntity):
"""Tuya climate devices,include air conditioner,heater.""" """Tuya Switch Device."""
def __init__(self, tuya, platform): def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
"""Init climate device.""" """Init Tuya Ha Climate."""
super().__init__(tuya, platform) super().__init__(device, device_manager)
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) if DPCODE_C_F in self.tuya_device.status:
self.operations = [HVAC_MODE_OFF] self.dp_temp_unit = DPCODE_C_F
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
else: else:
self._min_temp = min_temp self.dp_temp_unit = DPCODE_TEMP_UNIT_CONVERT
self._max_temp = max_temp
async def async_added_to_hass(self): def get_temp_set_scale(self) -> int | None:
"""Create operation list when add to hass.""" """Get temperature set scale."""
await super().async_added_to_hass() dp_temp_set = DPCODE_TEMP_SET if self.is_celsius() else DPCODE_TEMP_SET_F
self._process_config() temp_set_value_range_item = self.tuya_device.status_range.get(dp_temp_set)
self.async_on_remove( if not temp_set_value_range_item:
async_dispatcher_connect( return None
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
)
)
modes = self._tuya.operation_list() temp_set_value_range = json.loads(temp_set_value_range_item.values)
if modes is None: return temp_set_value_range.get("scale")
if self._def_hvac_mode not in self.operations:
self.operations.append(self._def_hvac_mode) 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
temp_current_value_range = json.loads(temp_current_value_range_item.values)
return temp_current_value_range.get("scale")
# Functions
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
commands = []
if hvac_mode == HVAC_MODE_OFF:
commands.append({"code": DPCODE_SWITCH, "value": False})
else:
commands.append({"code": DPCODE_SWITCH, "value": True})
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 return
for mode in modes: self._send_command(
if mode not in TUYA_STATE_TO_HA: [
continue {
ha_mode = TUYA_STATE_TO_HA[mode] "code": code,
if ha_mode not in self.operations: "value": int(kwargs["temperature"] * (10 ** temp_set_scale)),
self.operations.append(ha_mode) }
self._has_operation = True ]
)
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 @property
def temperature_unit(self): def temperature_unit(self) -> str:
"""Return the unit of measurement used by the platform.""" """Return true if fan is on."""
unit = self._tuya.temperature_unit() if self.is_celsius():
if unit == "FAHRENHEIT":
return TEMP_FAHRENHEIT
return TEMP_CELSIUS return TEMP_CELSIUS
return TEMP_FAHRENHEIT
@property @property
def hvac_mode(self): def current_temperature(self) -> float | None:
"""Return current operation ie. heat, cool, idle.""" """Return the current temperature."""
if not self._tuya.state(): 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."""
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
)
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 return HVAC_MODE_OFF
if not self._has_operation: @property
return self._def_hvac_mode 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"
)
mode = self._tuya.current_operation() hvac_modes = [HVAC_MODE_OFF]
if mode is None: for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items():
return None if tuya_mode in modes:
return TUYA_STATE_TO_HA.get(mode) hvac_modes.append(ha_mode)
return hvac_modes
@property @property
def hvac_modes(self): def fan_mode(self) -> str | None:
"""Return the list of available operation modes.""" """Return fan mode."""
return self.operations return self.tuya_device.status.get(DPCODE_FAN_SPEED_ENUM)
@property @property
def current_temperature(self): def fan_modes(self) -> list[str]:
"""Return the current temperature.""" """Return fan modes for select."""
return self._tuya.current_temperature() data = json.loads(
self.tuya_device.function.get(DPCODE_FAN_SPEED_ENUM, {}).values
).get("range")
return data
@property @property
def target_temperature(self): def swing_mode(self) -> str:
"""Return the temperature we try to reach.""" """Return swing mode."""
return self._tuya.target_temperature() 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 @property
def target_temperature_step(self): def swing_modes(self) -> list[str]:
"""Return the supported step of target temperature.""" """Return swing mode for select."""
if self._temp_step_override: return [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH]
return self._temp_step_override
return self._tuya.target_temperature_step()
@property @property
def fan_mode(self): def supported_features(self) -> int:
"""Return the fan setting.""" """Flag supported features."""
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."""
if hvac_mode == HVAC_MODE_OFF:
self._tuya.turn_off()
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 supports = 0
if self._tuya.support_target_temperature(): if (
supports = supports | SUPPORT_TARGET_TEMPERATURE DPCODE_TEMP_SET in self.tuya_device.status
if self._tuya.support_wind_speed(): or DPCODE_TEMP_SET_F in self.tuya_device.status
supports = supports | SUPPORT_FAN_MODE ):
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 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()
)
if min_temp is not None:
return min_temp
return super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
max_temp = (
self._max_temp if self._max_temp is not None else self._tuya.max_temp()
)
if max_temp is not None:
return max_temp
return super().max_temp

View File

@ -1,409 +1,140 @@
#!/usr/bin/env python3
"""Config flow for Tuya.""" """Config flow for Tuya."""
from __future__ import annotations
import logging import logging
from typing import Any
from tuyaha import TuyaApi from tuya_iot import ProjectType, TuyaOpenAPI
from tuyaha.tuyaapi import (
TuyaAPIException,
TuyaAPIRateLimitException,
TuyaNetException,
TuyaServerException,
)
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries 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 ( from .const import (
CONF_BRIGHTNESS_RANGE_MODE, CONF_ACCESS_ID,
CONF_COUNTRYCODE, CONF_ACCESS_SECRET,
CONF_CURR_TEMP_DIVIDER, CONF_APP_TYPE,
CONF_DISCOVERY_INTERVAL, CONF_COUNTRY_CODE,
CONF_MAX_KELVIN, CONF_ENDPOINT,
CONF_MAX_TEMP, CONF_PASSWORD,
CONF_MIN_KELVIN, CONF_PROJECT_TYPE,
CONF_MIN_TEMP, CONF_USERNAME,
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,
DOMAIN, DOMAIN,
TUYA_DATA, TUYA_APP_TYPE,
TUYA_PLATFORMS, TUYA_ENDPOINT,
TUYA_TYPE_NOT_QUERY, 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__) _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_USERNAME): str,
vol.Required(CONF_PASSWORD): 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" # SMART_HOME Schema
ERROR_DEV_NOT_CONFIG = "dev_not_config" DATA_SCHEMA_SMART_HOME = vol.Schema(
ERROR_DEV_NOT_FOUND = "dev_not_found" {
vol.Required(CONF_ACCESS_ID): str,
RESULT_AUTH_FAILED = "invalid_auth" vol.Required(CONF_ACCESS_SECRET): str,
RESULT_CONN_ERROR = "cannot_connect" vol.Required(CONF_APP_TYPE): vol.In(TUYA_APP_TYPE),
RESULT_SINGLE_INSTANCE = "single_instance_allowed" vol.Required(CONF_COUNTRY_CODE): str,
RESULT_SUCCESS = "success" vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
RESULT_LOG_MESSAGE = { }
RESULT_AUTH_FAILED: "Invalid credential", )
RESULT_CONN_ERROR: "Connection error",
}
TUYA_TYPE_CONFIG = ["climate", "light"]
class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a tuya config flow.""" """Tuya Config Flow."""
VERSION = 1
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize flow.""" """Init tuya config flow."""
self._country_code = None super().__init__()
self._password = None self.conf_project_type = None
self._platform = None
self._username = None
def _save_entry(self): @staticmethod
return self.async_create_entry( def _try_login(user_input):
title=self._username, project_type = ProjectType(user_input[CONF_PROJECT_TYPE])
data={ api = TuyaOpenAPI(
CONF_COUNTRYCODE: self._country_code, user_input[CONF_ENDPOINT]
CONF_PASSWORD: self._password, if project_type == ProjectType.INDUSTY_SOLUTIONS
CONF_PLATFORM: self._platform, else "",
CONF_USERNAME: self._username, user_input[CONF_ACCESS_ID],
}, user_input[CONF_ACCESS_SECRET],
project_type,
) )
api.set_dev_channel("hass")
def _try_connect(self): if project_type == ProjectType.INDUSTY_SOLUTIONS:
"""Try to connect and check auth.""" response = api.login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
tuya = TuyaApi() else:
try: api.endpoint = TUYA_ENDPOINT_BASE
tuya.init( response = api.login(
self._username, self._password, self._country_code, self._platform user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
user_input[CONF_COUNTRY_CODE],
user_input[CONF_APP_TYPE],
) )
except (TuyaAPIRateLimitException, TuyaNetException, TuyaServerException): if response.get("success", False) and isinstance(
return RESULT_CONN_ERROR api.token_info.platform_url, str
except TuyaAPIException: ):
return RESULT_AUTH_FAILED 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): async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user.""" """Step user."""
if self._async_current_entries(): if user_input is None:
return self.async_abort(reason=RESULT_SINGLE_INSTANCE) 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 = {} errors = {}
if user_input is not None: 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]) response = await self.hass.async_add_executor_job(
self._password = user_input[CONF_PASSWORD] self._try_login, user_input
self._platform = user_input[CONF_PLATFORM] )
self._username = user_input[CONF_USERNAME]
result = await self.hass.async_add_executor_job(self._try_connect) if response.get("success", False):
_LOGGER.debug("TuyaConfigFlow.async_step_user login success")
if result == RESULT_SUCCESS: return self.async_create_entry(
return self._save_entry() title=user_input[CONF_USERNAME],
if result != RESULT_AUTH_FAILED: data=user_input,
return self.async_abort(reason=result) )
errors["base"] = result errors["base"] = RESULT_AUTH_FAILED
if ProjectType(self.conf_project_type) == ProjectType.SMART_HOME:
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors step_id="login", data_schema=DATA_SCHEMA_SMART_HOME, 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( return self.async_show_form(
step_id="device", step_id="login",
data_schema=config_schema, data_schema=DATA_SCHEMA_INDUSTRY_SOLUTIONS,
description_placeholders={ errors=errors,
"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),
}
)
config_devices = self._get_tuya_devices_filtered(TUYA_TYPE_CONFIG, False, True)
if config_devices:
data_schema = data_schema.extend(
{vol.Optional(CONF_LIST_DEVICES): cv.multi_select(config_devices)}
)
return self.async_show_form(
step_id="init",
data_schema=data_schema,
errors=self._get_form_error(),
)
async def async_step_device(self, user_input=None, dev_ids=None):
"""Handle options flow for device."""
if dev_ids is not None:
return await self._async_device_form(dev_ids)
if user_input is not None:
for device_id in self._conf_devs_id:
self._conf_devs_option[device_id] = user_input
return await self.async_step_init()
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.""" """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" DOMAIN = "tuya"
SIGNAL_CONFIG_ENTITY = "tuya_config" CONF_PROJECT_TYPE = "tuya_project_type"
SIGNAL_DELETE_ENTITY = "tuya_delete" CONF_ENDPOINT = "endpoint"
SIGNAL_UPDATE_ENTITY = "tuya_update" 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_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_HA_SIGNAL_UPDATE_ENTITY = "tuya_entry_update"
"tuya": "Tuya",
"smart_life": "Smart Life", TUYA_ENDPOINT = {
"jinvoo_smart": "Jinvoo Smart", "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 __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 ( from homeassistant.components.fan import (
DOMAIN as SENSOR_DOMAIN, DIRECTION_FORWARD,
ENTITY_ID_FORMAT, DIRECTION_REVERSE,
DOMAIN as DEVICE_DOMAIN,
SUPPORT_DIRECTION,
SUPPORT_OSCILLATE, SUPPORT_OSCILLATE,
SUPPORT_PRESET_MODE,
SUPPORT_SET_SPEED, SUPPORT_SET_SPEED,
FanEntity, 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.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
ordered_list_item_to_percentage, ordered_list_item_to_percentage,
percentage_to_ordered_list_item, percentage_to_ordered_list_item,
) )
from . import TuyaDevice from .base import TuyaHaEntity
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW 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): # Fan
"""Set up tuya sensors dynamically through tuya discovery.""" # 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): TUYA_SUPPORT_TYPE = {
"""Discover and add a discovered tuya sensor.""" "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: if not dev_ids:
return return
entities = await hass.async_add_executor_job( entities = _setup_entities(hass, entry, dev_ids)
_setup_entities,
hass,
dev_ids,
platform,
)
async_add_entities(entities) async_add_entities(entities)
entry.async_on_unload(
async_dispatcher_connect( async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
)
) )
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
await async_discover_sensor(devices_ids) 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(
"""Set up Tuya Fan device.""" hass: HomeAssistant, entry: ConfigEntry, device_ids: list[str]
tuya = hass.data[DOMAIN][TUYA_DATA] ) -> list[TuyaHaFan]:
"""Set up Tuya Fan."""
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
entities = [] entities = []
for dev_id in dev_ids: for device_id in device_ids:
device = tuya.get_device_by_id(dev_id) device = device_manager.device_map[device_id]
if device is None: if device is None:
continue 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 return entities
class TuyaFanDevice(TuyaDevice, FanEntity): class TuyaHaFan(TuyaHaEntity, FanEntity):
"""Tuya fan devices.""" """Tuya Fan Device."""
def __init__(self, tuya, platform): def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
"""Init Tuya fan device.""" """Init Tuya Fan Device."""
super().__init__(tuya, platform) super().__init__(device, device_manager)
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
self.speeds = []
async def async_added_to_hass(self): self.ha_preset_modes = []
"""Create fan list when add to hass.""" if DPCODE_MODE in self.tuya_device.function:
await super().async_added_to_hass() self.ha_preset_modes = json.loads(
self.speeds.extend(self._tuya.speed_list()) 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: def set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan.""" """Set the speed of the fan, as a percentage."""
if percentage == 0: if self.tuya_device.category == "kj":
self.turn_off() 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: else:
tuya_speed = percentage_to_ordered_list_item(self.speeds, percentage) self._send_command([{"code": DPCODE_FAN_SPEED, "value": percentage}])
self._tuya.set_speed(tuya_speed)
def turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
self._send_command([{"code": DPCODE_SWITCH, "value": False}])
def turn_on( def turn_on(
self, self,
speed: str = None, speed: str = None,
percentage: int = None, percentage: int = None,
preset_mode: str = None, preset_mode: str = None,
**kwargs, **kwargs: Any,
) -> None: ) -> None:
"""Turn on the fan.""" """Turn on the fan."""
if percentage is not None: self._send_command([{"code": DPCODE_SWITCH, "value": True}])
self.set_percentage(percentage)
else:
self._tuya.turn_on()
def turn_off(self, **kwargs) -> None: def oscillate(self, oscillating: bool) -> None:
"""Turn the entity off."""
self._tuya.turn_off()
def oscillate(self, oscillating) -> None:
"""Oscillate the fan.""" """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 @property
def speed_count(self) -> int: def speed_count(self) -> int:
"""Return the number of speeds the fan supports.""" """Return the number of speeds the fan supports."""
if self.speeds is None: if self.tuya_device.category == "kj":
return self.air_purifier_speed_range_len
return super().speed_count return super().speed_count
return len(self.speeds)
@property @property
def oscillating(self): def supported_features(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:
"""Flag supported features.""" """Flag supported features."""
if self._tuya.support_oscillate(): supports = 0
return SUPPORT_SET_SPEED | SUPPORT_OSCILLATE if DPCODE_MODE in self.tuya_device.status:
return SUPPORT_SET_SPEED 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.""" """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 ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP, ATTR_COLOR_TEMP,
ATTR_HS_COLOR, ATTR_HS_COLOR,
DOMAIN as SENSOR_DOMAIN, COLOR_MODE_BRIGHTNESS,
ENTITY_ID_FORMAT, COLOR_MODE_COLOR_TEMP,
SUPPORT_BRIGHTNESS, COLOR_MODE_HS,
SUPPORT_COLOR, COLOR_MODE_ONOFF,
SUPPORT_COLOR_TEMP, DOMAIN as DEVICE_DOMAIN,
LightEntity, LightEntity,
) )
from homeassistant.const import CONF_PLATFORM from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect 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 ( 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, DOMAIN,
SIGNAL_CONFIG_ENTITY, TUYA_DEVICE_MANAGER,
TUYA_DATA,
TUYA_DISCOVERY_NEW, 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 = { # Light(dj)
0: TUYA_BRIGHTNESS_RANGE0, # https://developer.tuya.com/en/docs/iot/f?id=K9i5ql3v98hn3
1: TUYA_BRIGHTNESS_RANGE1, 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): async def async_setup_entry(
"""Set up tuya sensors dynamically through tuya discovery.""" 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): @callback
"""Discover and add a discovered tuya sensor.""" 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: if not dev_ids:
return return
entities = await hass.async_add_executor_job( entities = _setup_entities(hass, entry, dev_ids)
_setup_entities,
hass,
dev_ids,
platform,
)
async_add_entities(entities) async_add_entities(entities)
entry.async_on_unload(
async_dispatcher_connect( async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
)
) )
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
await async_discover_sensor(devices_ids) 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.""" """Set up Tuya Light device."""
tuya = hass.data[DOMAIN][TUYA_DATA] device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
entities = [] entities = []
for dev_id in dev_ids: for device_id in device_ids:
device = tuya.get_device_by_id(dev_id) device = device_manager.device_map[device_id]
if device is None: if device is None:
continue 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 return entities
class TuyaLight(TuyaDevice, LightEntity): class TuyaHaLight(TuyaHaEntity, LightEntity):
"""Tuya light device.""" """Tuya light device."""
def __init__(self, tuya, platform): def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
"""Init Tuya light device.""" """Init TuyaHaLight."""
super().__init__(tuya, platform) self.dp_code_bright = DPCODE_BRIGHT_VALUE
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) self.dp_code_temp = DPCODE_TEMP_VALUE
self._min_kelvin = tuya.max_color_temp() self.dp_code_colour = DPCODE_COLOUR_DATA
self._max_kelvin = tuya.min_color_temp()
@callback for key in device.function:
def _process_config(self): if key.startswith(DPCODE_BRIGHT_VALUE):
"""Set device config parameter.""" self.dp_code_bright = key
config = self._get_device_config() elif key.startswith(DPCODE_TEMP_VALUE):
if not config: self.dp_code_temp = key
return elif key.startswith(DPCODE_COLOUR_DATA):
self.dp_code_colour = key
# support color config super().__init__(device, device_manager)
supp_color = config.get(CONF_SUPPORT_COLOR, False)
if supp_color:
self._tuya.force_support_color()
# brightness range config
self._tuya.brightness_white_range = BRIGHTNESS_MODES.get(
config.get(CONF_BRIGHTNESS_RANGE_MODE, 0),
TUYA_BRIGHTNESS_RANGE0,
)
# color set temp range
min_tuya = self._tuya.max_color_temp()
min_kelvin = config.get(CONF_MIN_KELVIN, min_tuya)
max_tuya = self._tuya.min_color_temp()
max_kelvin = config.get(CONF_MAX_KELVIN, max_tuya)
self._min_kelvin = min(max(min_kelvin, min_tuya), max_tuya)
self._max_kelvin = min(max(max_kelvin, self._min_kelvin), max_tuya)
# color shown temp range
max_color_temp = max(
config.get(CONF_TUYA_MAX_COLTEMP, DEFAULT_TUYA_MAX_COLTEMP),
DEFAULT_TUYA_MAX_COLTEMP,
)
self._tuya.color_temp_range = (1000, max_color_temp)
async def async_added_to_hass(self):
"""Set config parameter when add to hass."""
await super().async_added_to_hass()
self._process_config()
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
)
)
return
@property @property
def brightness(self): def is_on(self) -> bool:
"""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):
"""Return true if light is on.""" """Return true if light is on."""
return self._tuya.state() return self.tuya_device.status.get(DPCODE_SWITCH, False)
@property def turn_on(self, **kwargs: Any) -> None:
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):
"""Turn on or control the light.""" """Turn on or control the light."""
if ( commands = []
ATTR_BRIGHTNESS not in kwargs _LOGGER.debug("light kwargs-> %s", 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)
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.""" """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 @property
def supported_features(self): def brightness(self) -> int | None:
"""Flag supported features.""" """Return the brightness of the light."""
supports = SUPPORT_BRIGHTNESS old_range = self._tuya_brightness_range()
if self._tuya.support_color(): brightness = self.tuya_device.status.get(self.dp_code_bright, 0)
supports = supports | SUPPORT_COLOR
if self._tuya.support_color_temp(): if self._work_mode().startswith(WORK_MODE_COLOUR):
supports = supports | SUPPORT_COLOR_TEMP colour_json = self.tuya_device.status.get(self.dp_code_colour)
return supports 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", "domain": "tuya",
"name": "Tuya", "name": "Tuya",
"documentation": "https://www.home-assistant.io/integrations/tuya", "documentation": "https://github.com/tuya/tuya-home-assistant",
"requirements": ["tuyaha==0.0.10"], "requirements": [
"codeowners": ["@ollo69"], "tuya-iot-py-sdk==0.4.1"
],
"codeowners": [
"@Tuya"
],
"config_flow": true, "config_flow": true,
"iot_class": "cloud_polling", "iot_class": "cloud_push"
"dhcp": [
{"macaddress": "508A06*"},
{"macaddress": "7CF666*"},
{"macaddress": "10D561*"},
{"macaddress": "D4A651*"},
{"macaddress": "68572D*"},
{"macaddress": "1869D8*"}
]
} }

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 typing import Any
from homeassistant.components.scene import DOMAIN as SENSOR_DOMAIN, Scene from tuya_iot import TuyaHomeManager, TuyaScene
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import TuyaDevice from homeassistant.components.scene import Scene
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW 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): async def async_setup_entry(
"""Set up tuya sensors dynamically through tuya discovery.""" hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up tuya scenes."""
entities = []
platform = config_entry.data[CONF_PLATFORM] 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 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_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) class TuyaHAScene(Scene):
await async_discover_sensor(devices_ids) """Tuya Scene Remote."""
def __init__(self, home_manager: TuyaHomeManager, scene: TuyaScene) -> None:
"""Init Tuya Scene."""
super().__init__()
self.home_manager = home_manager
self.scene = scene
def _setup_entities(hass, dev_ids, platform): @property
"""Set up Tuya Scene.""" def should_poll(self) -> bool:
tuya = hass.data[DOMAIN][TUYA_DATA] """Hass should not poll."""
entities = [] return False
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
@property
def unique_id(self) -> str | None:
"""Return a unique ID."""
return f"tys{self.scene.scene_id}"
class TuyaScene(TuyaDevice, Scene): @property
"""Tuya Scene.""" def name(self) -> str | None:
"""Return Tuya scene name."""
return self.scene.name
def __init__(self, tuya, platform): @property
"""Init Tuya scene.""" def device_info(self):
super().__init__(tuya, platform) """Return a device description for device registry."""
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) 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: def activate(self, **kwargs: Any) -> None:
"""Activate the scene.""" """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": { "config": {
"flow_title": "Tuya configuration",
"step": { "step": {
"user": { "user":{
"title": "Tuya", "title":"Tuya Integration",
"description": "Enter your Tuya credentials.", "data":{
"data": { "tuya_project_type": "Tuya cloud project type"
"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%]"
}
} }
}, },
"abort": { "login": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "title": "Tuya",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "description": "Enter your Tuya credential",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" "data": {
"endpoint": "Availability Zone",
"access_id": "Access ID",
"access_secret": "Access Secret",
"tuya_app_type": "Mobile App",
"country_code": "Country Code",
"username": "Account",
"password": "Password"
}
}
}, },
"error": { "error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" "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.""" """Support for Tuya switches."""
from datetime import timedelta from __future__ import annotations
from homeassistant.components.switch import ( import logging
DOMAIN as SENSOR_DOMAIN, from typing import Any
ENTITY_ID_FORMAT,
SwitchEntity, from tuya_iot import TuyaDevice, TuyaDeviceManager
)
from homeassistant.const import CONF_PLATFORM 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.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 DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW 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.""" """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.""" """Discover and add a discovered tuya sensor."""
_LOGGER.debug("switch add-> %s", dev_ids)
if not dev_ids: if not dev_ids:
return return
entities = await hass.async_add_executor_job( entities = _setup_entities(hass, entry, dev_ids)
_setup_entities,
hass,
dev_ids,
platform,
)
async_add_entities(entities) async_add_entities(entities)
entry.async_on_unload(
async_dispatcher_connect( async_dispatcher_connect(
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
)
) )
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN) device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
await async_discover_sensor(devices_ids) 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.""" """Set up Tuya Switch device."""
tuya = hass.data[DOMAIN][TUYA_DATA] device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
entities = [] entities: list[Entity] = []
for dev_id in dev_ids: for device_id in device_ids:
device = tuya.get_device_by_id(dev_id) device = device_manager.device_map[device_id]
if device is None: if device is None:
continue 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 return entities
class TuyaSwitch(TuyaDevice, SwitchEntity): class TuyaHaSwitch(TuyaHaEntity, SwitchEntity):
"""Tuya Switch Device.""" """Tuya Switch Device."""
def __init__(self, tuya, platform): dp_code_switch = DPCODE_SWITCH
"""Init Tuya switch device.""" dp_code_start = DPCODE_START
super().__init__(tuya, platform)
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) 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 @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 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.""" """Turn the switch on."""
self._tuya.turn_on() self._send_command([{"code": self.dp_code, "value": True}])
def turn_off(self, **kwargs): def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off.""" """Turn the switch off."""
self._tuya.turn_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": { "config": {
"abort": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"single_instance_allowed": "Already configured. Only a single configuration possible."
},
"error": { "error": {
"invalid_auth": "Invalid authentication" "invalid_auth": "Invalid authentication"
}, },
"flow_title": "Tuya configuration", "flow_title": "Tuya configuration",
"step": { "step": {
"user": { "user":{
"data": { "title":"Tuya Integration",
"country_code": "Your account country code (e.g., 1 for USA or 86 for China)", "data":{
"password": "Password", "tuya_project_type": "Tuya cloud project type"
"platform": "The app where your account is registered", }
"username": "Username"
}, },
"description": "Enter your Tuya credentials.", "login": {
"data": {
"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 credential.",
"title": "Tuya" "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,59 +1,28 @@
{ {
"config": { "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": { "error": {
"invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548" "invalid_auth": "身份认证无效"
}, },
"flow_title": "\u6d82\u9e26\u914d\u7f6e", "flow_title": "涂鸦配置",
"step": { "step": {
"user": { "user":{
"data": { "title":"Tuya插件",
"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", "data":{
"password": "\u5bc6\u7801", "tuya_project_type": "涂鸦云项目类型"
"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": { "login": {
"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": { "data": {
"brightness_range_mode": "\u8bbe\u5907\u4f7f\u7528\u7684\u4eae\u5ea6\u8303\u56f4", "endpoint": "可用区域",
"max_kelvin": "\u6700\u9ad8\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09", "access_id": "Access ID",
"max_temp": "\u6700\u9ad8\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09", "access_secret": "Access Secret",
"min_kelvin": "\u6700\u4f4e\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09", "tuya_app_type": "移动应用",
"min_temp": "\u6700\u4f4e\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09", "country_code": "国家码",
"support_color": "\u5f3a\u5236\u652f\u6301\u8c03\u8272", "username": "账号",
"tuya_max_coltemp": "\u8bbe\u5907\u62a5\u544a\u7684\u6700\u9ad8\u8272\u6e29", "password": "密码"
"unit_of_measurement": "\u8bbe\u5907\u4f7f\u7528\u7684\u6e29\u5ea6\u5355\u4f4d"
}, },
"title": "\u914d\u7f6e\u6d82\u9e26\u8bbe\u5907" "description": "请输入涂鸦账户信息。",
}, "title": "涂鸦"
"init": {
"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"
},
"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"
} }
} }
} }

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*", "hostname": "lb*",
"macaddress": "B09575*" "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", "domain": "verisure",
"macaddress": "0023C1*" "macaddress": "0023C1*"

View File

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

View File

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

View File

@ -1,68 +1,81 @@
"""Tests for the Tuya config flow.""" """Tests for the Tuya config flow."""
from unittest.mock import Mock, patch from unittest.mock import MagicMock, Mock, patch
import pytest 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 import config_entries, data_entry_flow
from homeassistant.components.tuya.config_flow import ( from homeassistant.components.tuya.config_flow import RESULT_AUTH_FAILED
CONF_LIST_DEVICES,
ERROR_DEV_MULTI_TYPE,
ERROR_DEV_NOT_CONFIG,
ERROR_DEV_NOT_FOUND,
RESULT_AUTH_FAILED,
RESULT_CONN_ERROR,
RESULT_SINGLE_INSTANCE,
)
from homeassistant.components.tuya.const import ( from homeassistant.components.tuya.const import (
CONF_BRIGHTNESS_RANGE_MODE, CONF_ACCESS_ID,
CONF_COUNTRYCODE, CONF_ACCESS_SECRET,
CONF_CURR_TEMP_DIVIDER, CONF_APP_TYPE,
CONF_DISCOVERY_INTERVAL, CONF_COUNTRY_CODE,
CONF_MAX_KELVIN, CONF_ENDPOINT,
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_PASSWORD, CONF_PASSWORD,
CONF_PLATFORM, CONF_PROJECT_TYPE,
CONF_UNIT_OF_MEASUREMENT,
CONF_USERNAME, 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" TUYA_SMART_HOME_PROJECT_DATA = {
PASSWORD = "myPassword" CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE,
COUNTRY_CODE = "1" }
TUYA_PLATFORM = "tuya" TUYA_INDUSTRY_PROJECT_DATA = {
CONF_PROJECT_TYPE: MOCK_INDUSTRY_PROJECT_TYPE,
}
TUYA_USER_DATA = { TUYA_INPUT_SMART_HOME_DATA = {
CONF_USERNAME: USERNAME, CONF_ACCESS_ID: MOCK_ACCESS_ID,
CONF_PASSWORD: PASSWORD, CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
CONF_COUNTRYCODE: COUNTRY_CODE, CONF_USERNAME: MOCK_USERNAME,
CONF_PLATFORM: TUYA_PLATFORM, 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") @pytest.fixture(name="tuya")
def tuya_fixture() -> Mock: def tuya_fixture() -> Mock:
"""Patch libraries.""" """Patch libraries."""
with patch("homeassistant.components.tuya.config_flow.TuyaApi") as tuya: with patch("homeassistant.components.tuya.config_flow.TuyaOpenAPI") as tuya:
yield tuya yield tuya
@ -73,8 +86,8 @@ def tuya_setup_fixture():
yield yield
async def test_user(hass, tuya): async def test_industry_user(hass, tuya):
"""Test user config.""" """Test industry user config."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
@ -83,192 +96,92 @@ async def test_user(hass, tuya):
assert result["step_id"] == "user" assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure( 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() await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == USERNAME assert result["title"] == MOCK_USERNAME
assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_ACCESS_ID] == MOCK_ACCESS_ID
assert result["data"][CONF_PASSWORD] == PASSWORD assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET
assert result["data"][CONF_COUNTRYCODE] == COUNTRY_CODE assert result["data"][CONF_USERNAME] == MOCK_USERNAME
assert result["data"][CONF_PLATFORM] == TUYA_PLATFORM assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
assert not result["result"].unique_id assert not result["result"].unique_id
async def test_abort_if_already_setup(hass, tuya): async def test_smart_home_user(hass, tuya):
"""Test we abort if Tuya is already setup.""" """Test smart home user config."""
MockConfigEntry(domain=DOMAIN, data=TUYA_USER_DATA).add_to_hass(hass)
# Should fail, config exist (import)
result = await hass.config_entries.flow.async_init( 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_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
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {"base": RESULT_AUTH_FAILED} assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
async def test_abort_on_connection_error(hass, tuya): result["flow_id"], user_input=TUYA_SMART_HOME_PROJECT_DATA
"""Test when we have a network error."""
tuya().init.side_effect = TuyaNetException("Boom")
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=TUYA_USER_DATA
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == RESULT_CONN_ERROR
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() await hass.async_block_till_done()
# Unload the integration to prepare for the test. assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
with patch("homeassistant.components.tuya.async_unload_entry", return_value=True): assert result["step_id"] == "login"
assert await hass.config_entries.async_unload(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() await hass.async_block_till_done()
# Test check for integration not loaded assert result["errors"]["base"] == RESULT_AUTH_FAILED
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 tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024})
await hass.config_entries.async_setup(config_entry.entry_id) result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA
)
await hass.async_block_till_done() 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"
# 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}"]},
)
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["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] async def test_error_on_invalid_credentials(hass, tuya):
assert light_options[CONF_SUPPORT_COLOR] is True """Test when we have invalid credentials."""
assert light_options[CONF_BRIGHTNESS_RANGE_MODE] == 1
assert light_options[CONF_MIN_KELVIN] == 4000
assert light_options[CONF_MAX_KELVIN] == 5000
assert light_options[CONF_TUYA_MAX_COLTEMP] == 12000
assert config_entry.options[CONF_DISCOVERY_INTERVAL] == 100 result = await hass.config_entries.flow.async_init(
assert config_entry.options[CONF_QUERY_INTERVAL] == 50 DOMAIN, context={"source": config_entries.SOURCE_USER}
assert config_entry.options[CONF_QUERY_DEVICE] == LIGHT_ID )
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