mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
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:
parent
26042bdad7
commit
d3b1ccb668
@ -1115,9 +1115,9 @@ omit =
|
||||
homeassistant/components/transmission/errors.py
|
||||
homeassistant/components/travisci/sensor.py
|
||||
homeassistant/components/tuya/__init__.py
|
||||
homeassistant/components/tuya/base.py
|
||||
homeassistant/components/tuya/climate.py
|
||||
homeassistant/components/tuya/const.py
|
||||
homeassistant/components/tuya/cover.py
|
||||
homeassistant/components/tuya/fan.py
|
||||
homeassistant/components/tuya/light.py
|
||||
homeassistant/components/tuya/scene.py
|
||||
|
@ -544,7 +544,7 @@ homeassistant/components/trafikverket_train/* @endor-force
|
||||
homeassistant/components/trafikverket_weatherstation/* @endor-force
|
||||
homeassistant/components/transmission/* @engrbm87 @JPHutchins
|
||||
homeassistant/components/tts/* @pvizeli
|
||||
homeassistant/components/tuya/* @ollo69
|
||||
homeassistant/components/tuya/* @Tuya
|
||||
homeassistant/components/twentemilieu/* @frenck
|
||||
homeassistant/components/twinkly/* @dr1rrb
|
||||
homeassistant/components/ubus/* @noltari
|
||||
|
@ -1,376 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Support for Tuya Smart devices."""
|
||||
from datetime import timedelta
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
from tuyaha import TuyaApi
|
||||
from tuyaha.tuyaapi import (
|
||||
TuyaAPIException,
|
||||
TuyaAPIRateLimitException,
|
||||
TuyaFrequentlyInvokeException,
|
||||
TuyaNetException,
|
||||
TuyaServerException,
|
||||
from tuya_iot import (
|
||||
ProjectType,
|
||||
TuyaDevice,
|
||||
TuyaDeviceListener,
|
||||
TuyaDeviceManager,
|
||||
TuyaHomeManager,
|
||||
TuyaOpenAPI,
|
||||
TuyaOpenMQ,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers import device_registry
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
from .const import (
|
||||
CONF_COUNTRYCODE,
|
||||
CONF_DISCOVERY_INTERVAL,
|
||||
CONF_QUERY_DEVICE,
|
||||
CONF_QUERY_INTERVAL,
|
||||
DEFAULT_DISCOVERY_INTERVAL,
|
||||
DEFAULT_QUERY_INTERVAL,
|
||||
CONF_ACCESS_ID,
|
||||
CONF_ACCESS_SECRET,
|
||||
CONF_APP_TYPE,
|
||||
CONF_COUNTRY_CODE,
|
||||
CONF_ENDPOINT,
|
||||
CONF_PASSWORD,
|
||||
CONF_PROJECT_TYPE,
|
||||
CONF_USERNAME,
|
||||
DOMAIN,
|
||||
SIGNAL_CONFIG_ENTITY,
|
||||
SIGNAL_DELETE_ENTITY,
|
||||
SIGNAL_UPDATE_ENTITY,
|
||||
TUYA_DATA,
|
||||
TUYA_DEVICES_CONF,
|
||||
PLATFORMS,
|
||||
TUYA_DEVICE_MANAGER,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
TUYA_PLATFORMS,
|
||||
TUYA_TYPE_NOT_QUERY,
|
||||
TUYA_HA_DEVICES,
|
||||
TUYA_HA_SIGNAL_UPDATE_ENTITY,
|
||||
TUYA_HA_TUYA_MAP,
|
||||
TUYA_HOME_MANAGER,
|
||||
TUYA_MQTT_LISTENER,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_TUYA_DEV_ID = "tuya_device_id"
|
||||
ENTRY_IS_SETUP = "tuya_entry_is_setup"
|
||||
|
||||
SERVICE_FORCE_UPDATE = "force_update"
|
||||
SERVICE_PULL_DEVICES = "pull_devices"
|
||||
|
||||
TUYA_TYPE_TO_HA = {
|
||||
"climate": "climate",
|
||||
"cover": "cover",
|
||||
"fan": "fan",
|
||||
"light": "light",
|
||||
"scene": "scene",
|
||||
"switch": "switch",
|
||||
}
|
||||
|
||||
TUYA_TRACKER = "tuya_tracker"
|
||||
STOP_CANCEL = "stop_event_cancel"
|
||||
|
||||
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
|
||||
|
||||
|
||||
def _update_discovery_interval(hass, interval):
|
||||
tuya = hass.data[DOMAIN].get(TUYA_DATA)
|
||||
if not tuya:
|
||||
return
|
||||
|
||||
try:
|
||||
tuya.discovery_interval = interval
|
||||
_LOGGER.info("Tuya discovery device poll interval set to %s seconds", interval)
|
||||
except ValueError as ex:
|
||||
_LOGGER.warning(ex)
|
||||
|
||||
|
||||
def _update_query_interval(hass, interval):
|
||||
tuya = hass.data[DOMAIN].get(TUYA_DATA)
|
||||
if not tuya:
|
||||
return
|
||||
|
||||
try:
|
||||
tuya.query_interval = interval
|
||||
_LOGGER.info("Tuya query device poll interval set to %s seconds", interval)
|
||||
except ValueError as ex:
|
||||
_LOGGER.warning(ex)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Tuya platform."""
|
||||
"""Async setup hass config entry."""
|
||||
|
||||
tuya = TuyaApi()
|
||||
username = entry.data[CONF_USERNAME]
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
country_code = entry.data[CONF_COUNTRYCODE]
|
||||
platform = entry.data[CONF_PLATFORM]
|
||||
_LOGGER.debug("tuya.__init__.async_setup_entry-->%s", entry.data)
|
||||
|
||||
try:
|
||||
await hass.async_add_executor_job(
|
||||
tuya.init, username, password, country_code, platform
|
||||
)
|
||||
except (
|
||||
TuyaNetException,
|
||||
TuyaServerException,
|
||||
TuyaFrequentlyInvokeException,
|
||||
) as exc:
|
||||
raise ConfigEntryNotReady() from exc
|
||||
hass.data[DOMAIN] = {entry.entry_id: {TUYA_HA_TUYA_MAP: {}, TUYA_HA_DEVICES: set()}}
|
||||
|
||||
except TuyaAPIRateLimitException as exc:
|
||||
raise ConfigEntryNotReady("Tuya login rate limited") from exc
|
||||
|
||||
except TuyaAPIException as exc:
|
||||
_LOGGER.error(
|
||||
"Connection error during integration setup. Error: %s",
|
||||
exc,
|
||||
)
|
||||
success = await _init_tuya_sdk(hass, entry)
|
||||
if not success:
|
||||
return False
|
||||
|
||||
domain_data = hass.data[DOMAIN] = {
|
||||
TUYA_DATA: tuya,
|
||||
TUYA_DEVICES_CONF: entry.options.copy(),
|
||||
TUYA_TRACKER: None,
|
||||
ENTRY_IS_SETUP: set(),
|
||||
"entities": {},
|
||||
"pending": {},
|
||||
"listener": entry.add_update_listener(update_listener),
|
||||
}
|
||||
|
||||
_update_discovery_interval(
|
||||
hass, entry.options.get(CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL)
|
||||
)
|
||||
|
||||
_update_query_interval(
|
||||
hass, entry.options.get(CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL)
|
||||
)
|
||||
|
||||
async def async_load_devices(device_list):
|
||||
"""Load new devices by device_list."""
|
||||
device_type_list = {}
|
||||
for device in device_list:
|
||||
dev_type = device.device_type()
|
||||
if (
|
||||
dev_type in TUYA_TYPE_TO_HA
|
||||
and device.object_id() not in domain_data["entities"]
|
||||
):
|
||||
ha_type = TUYA_TYPE_TO_HA[dev_type]
|
||||
if ha_type not in device_type_list:
|
||||
device_type_list[ha_type] = []
|
||||
device_type_list[ha_type].append(device.object_id())
|
||||
domain_data["entities"][device.object_id()] = None
|
||||
|
||||
for ha_type, dev_ids in device_type_list.items():
|
||||
config_entries_key = f"{ha_type}.tuya"
|
||||
if config_entries_key not in domain_data[ENTRY_IS_SETUP]:
|
||||
domain_data["pending"][ha_type] = dev_ids
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, ha_type)
|
||||
)
|
||||
domain_data[ENTRY_IS_SETUP].add(config_entries_key)
|
||||
else:
|
||||
async_dispatcher_send(hass, TUYA_DISCOVERY_NEW.format(ha_type), dev_ids)
|
||||
|
||||
await async_load_devices(tuya.get_all_devices())
|
||||
|
||||
def _get_updated_devices():
|
||||
try:
|
||||
tuya.poll_devices_update()
|
||||
except TuyaFrequentlyInvokeException as exc:
|
||||
_LOGGER.error(exc)
|
||||
return tuya.get_all_devices()
|
||||
|
||||
async def async_poll_devices_update(event_time):
|
||||
"""Check if accesstoken is expired and pull device list from server."""
|
||||
_LOGGER.debug("Pull devices from Tuya")
|
||||
# Add new discover device.
|
||||
device_list = await hass.async_add_executor_job(_get_updated_devices)
|
||||
await async_load_devices(device_list)
|
||||
# Delete not exist device.
|
||||
newlist_ids = []
|
||||
for device in device_list:
|
||||
newlist_ids.append(device.object_id())
|
||||
for dev_id in list(domain_data["entities"]):
|
||||
if dev_id not in newlist_ids:
|
||||
async_dispatcher_send(hass, SIGNAL_DELETE_ENTITY, dev_id)
|
||||
domain_data["entities"].pop(dev_id)
|
||||
|
||||
domain_data[TUYA_TRACKER] = async_track_time_interval(
|
||||
hass, async_poll_devices_update, timedelta(minutes=2)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_cancel_tuya_tracker(event):
|
||||
domain_data[TUYA_TRACKER]() # pylint: disable=not-callable
|
||||
|
||||
domain_data[STOP_CANCEL] = hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, _async_cancel_tuya_tracker
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_PULL_DEVICES, async_poll_devices_update
|
||||
)
|
||||
|
||||
async def async_force_update(call):
|
||||
"""Force all devices to pull data."""
|
||||
async_dispatcher_send(hass, SIGNAL_UPDATE_ENTITY)
|
||||
|
||||
hass.services.async_register(DOMAIN, SERVICE_FORCE_UPDATE, async_force_update)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
async def _init_tuya_sdk(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
project_type = ProjectType(entry.data[CONF_PROJECT_TYPE])
|
||||
api = TuyaOpenAPI(
|
||||
entry.data[CONF_ENDPOINT],
|
||||
entry.data[CONF_ACCESS_ID],
|
||||
entry.data[CONF_ACCESS_SECRET],
|
||||
project_type,
|
||||
)
|
||||
|
||||
api.set_dev_channel("hass")
|
||||
|
||||
if project_type == ProjectType.INDUSTY_SOLUTIONS:
|
||||
response = await hass.async_add_executor_job(
|
||||
api.login, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]
|
||||
)
|
||||
else:
|
||||
response = await hass.async_add_executor_job(
|
||||
api.login,
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
entry.data[CONF_COUNTRY_CODE],
|
||||
entry.data[CONF_APP_TYPE],
|
||||
)
|
||||
|
||||
if response.get("success", False) is False:
|
||||
_LOGGER.error("Tuya login error response: %s", response)
|
||||
return False
|
||||
|
||||
tuya_mq = TuyaOpenMQ(api)
|
||||
tuya_mq.start()
|
||||
|
||||
device_manager = TuyaDeviceManager(api, tuya_mq)
|
||||
|
||||
# Get device list
|
||||
home_manager = TuyaHomeManager(api, tuya_mq, device_manager)
|
||||
await hass.async_add_executor_job(home_manager.update_device_cache)
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HOME_MANAGER] = home_manager
|
||||
|
||||
listener = DeviceListener(hass, entry)
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_MQTT_LISTENER] = listener
|
||||
device_manager.add_device_listener(listener)
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER] = device_manager
|
||||
|
||||
# Clean up device entities
|
||||
await cleanup_device_registry(hass, entry)
|
||||
|
||||
_LOGGER.debug("init support type->%s", PLATFORMS)
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def cleanup_device_registry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Remove deleted device registry entry if there are no remaining entities."""
|
||||
|
||||
device_registry_object = device_registry.async_get(hass)
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
|
||||
for dev_id, device_entry in list(device_registry_object.devices.items()):
|
||||
for item in device_entry.identifiers:
|
||||
if DOMAIN == item[0] and item[1] not in device_manager.device_map:
|
||||
device_registry_object.async_remove_device(dev_id)
|
||||
break
|
||||
|
||||
|
||||
@callback
|
||||
def async_remove_hass_device(hass: HomeAssistant, device_id: str) -> None:
|
||||
"""Remove device from hass cache."""
|
||||
device_registry_object = device_registry.async_get(hass)
|
||||
for device_entry in list(device_registry_object.devices.values()):
|
||||
if device_id in list(device_entry.identifiers)[0]:
|
||||
device_registry_object.async_remove_device(device_entry.id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unloading the Tuya platforms."""
|
||||
domain_data = hass.data[DOMAIN]
|
||||
platforms = [platform.split(".", 1)[0] for platform in domain_data[ENTRY_IS_SETUP]]
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms)
|
||||
if unload_ok:
|
||||
domain_data["listener"]()
|
||||
domain_data[STOP_CANCEL]()
|
||||
domain_data[TUYA_TRACKER]()
|
||||
hass.services.async_remove(DOMAIN, SERVICE_FORCE_UPDATE)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_PULL_DEVICES)
|
||||
_LOGGER.debug("integration unload")
|
||||
unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload:
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
device_manager.mq.stop()
|
||||
device_manager.remove_device_listener(
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_MQTT_LISTENER]
|
||||
)
|
||||
|
||||
hass.data.pop(DOMAIN)
|
||||
|
||||
return unload_ok
|
||||
return unload
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Update when config_entry options update."""
|
||||
hass.data[DOMAIN][TUYA_DEVICES_CONF] = entry.options.copy()
|
||||
_update_discovery_interval(
|
||||
hass, entry.options.get(CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL)
|
||||
)
|
||||
_update_query_interval(
|
||||
hass, entry.options.get(CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL)
|
||||
)
|
||||
async_dispatcher_send(hass, SIGNAL_CONFIG_ENTITY)
|
||||
class DeviceListener(TuyaDeviceListener):
|
||||
"""Device Update Listener."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Init DeviceListener."""
|
||||
|
||||
async def cleanup_device_registry(hass: HomeAssistant, device_id):
|
||||
"""Remove device registry entry if there are no remaining entities."""
|
||||
self.hass = hass
|
||||
self.entry = entry
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
if device_id and not hass.helpers.entity_registry.async_entries_for_device(
|
||||
entity_registry, device_id, include_disabled_entities=True
|
||||
):
|
||||
device_registry.async_remove_device(device_id)
|
||||
|
||||
|
||||
class TuyaDevice(Entity):
|
||||
"""Tuya base device."""
|
||||
|
||||
_dev_can_query_count = 0
|
||||
|
||||
def __init__(self, tuya, platform):
|
||||
"""Init Tuya devices."""
|
||||
self._tuya = tuya
|
||||
self._tuya_platform = platform
|
||||
|
||||
def _device_can_query(self):
|
||||
"""Check if device can also use query method."""
|
||||
dev_type = self._tuya.device_type()
|
||||
return dev_type not in TUYA_TYPE_NOT_QUERY
|
||||
|
||||
def _inc_device_count(self):
|
||||
"""Increment static variable device count."""
|
||||
if not self._device_can_query():
|
||||
return
|
||||
TuyaDevice._dev_can_query_count += 1
|
||||
|
||||
def _dec_device_count(self):
|
||||
"""Decrement static variable device count."""
|
||||
if not self._device_can_query():
|
||||
return
|
||||
TuyaDevice._dev_can_query_count -= 1
|
||||
|
||||
def _get_device_config(self):
|
||||
"""Get updated device options."""
|
||||
devices_config = self.hass.data[DOMAIN].get(TUYA_DEVICES_CONF)
|
||||
if not devices_config:
|
||||
return {}
|
||||
dev_conf = devices_config.get(self.object_id, {})
|
||||
if dev_conf:
|
||||
def update_device(self, device: TuyaDevice) -> None:
|
||||
"""Update device status."""
|
||||
if device.id in self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_DEVICES]:
|
||||
_LOGGER.debug(
|
||||
"Configuration for deviceID %s: %s", self.object_id, str(dev_conf)
|
||||
"_update-->%s;->>%s",
|
||||
self,
|
||||
device.id,
|
||||
)
|
||||
return dev_conf
|
||||
dispatcher_send(self.hass, f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{device.id}")
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.data[DOMAIN]["entities"][self.object_id] = self.entity_id
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_DELETE_ENTITY, self._delete_callback
|
||||
)
|
||||
)
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback
|
||||
)
|
||||
)
|
||||
self._inc_device_count()
|
||||
def add_device(self, device: TuyaDevice) -> None:
|
||||
"""Add device added listener."""
|
||||
device_add = False
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Call when entity is removed from hass."""
|
||||
self._dec_device_count()
|
||||
if device.category in itertools.chain(
|
||||
*self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_TUYA_MAP].values()
|
||||
):
|
||||
ha_tuya_map = self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_TUYA_MAP]
|
||||
self.hass.add_job(async_remove_hass_device, self.hass, device.id)
|
||||
|
||||
@property
|
||||
def object_id(self):
|
||||
"""Return Tuya device id."""
|
||||
return self._tuya.object_id()
|
||||
for domain, tuya_list in ha_tuya_map.items():
|
||||
if device.category in tuya_list:
|
||||
device_add = True
|
||||
_LOGGER.debug(
|
||||
"Add device category->%s; domain-> %s",
|
||||
device.category,
|
||||
domain,
|
||||
)
|
||||
self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_DEVICES].add(
|
||||
device.id
|
||||
)
|
||||
dispatcher_send(
|
||||
self.hass, TUYA_DISCOVERY_NEW.format(domain), [device.id]
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return f"tuya.{self._tuya.object_id()}"
|
||||
if device_add:
|
||||
device_manager = self.hass.data[DOMAIN][self.entry.entry_id][
|
||||
TUYA_DEVICE_MANAGER
|
||||
]
|
||||
device_manager.mq.stop()
|
||||
tuya_mq = TuyaOpenMQ(device_manager.api)
|
||||
tuya_mq.start()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return Tuya device name."""
|
||||
return self._tuya.name()
|
||||
device_manager.mq = tuya_mq
|
||||
tuya_mq.add_message_listener(device_manager.on_message)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if the device is available."""
|
||||
return self._tuya.available()
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
_device_info = {
|
||||
"identifiers": {(DOMAIN, f"{self.unique_id}")},
|
||||
"manufacturer": TUYA_PLATFORMS.get(
|
||||
self._tuya_platform, self._tuya_platform
|
||||
),
|
||||
"name": self.name,
|
||||
"model": self._tuya.object_type(),
|
||||
}
|
||||
return _device_info
|
||||
|
||||
def update(self):
|
||||
"""Refresh Tuya device data."""
|
||||
query_dev = self.hass.data[DOMAIN][TUYA_DEVICES_CONF].get(CONF_QUERY_DEVICE, "")
|
||||
use_discovery = (
|
||||
TuyaDevice._dev_can_query_count > 1 and self.object_id != query_dev
|
||||
)
|
||||
try:
|
||||
self._tuya.update(use_discovery=use_discovery)
|
||||
except TuyaFrequentlyInvokeException as exc:
|
||||
_LOGGER.error(exc)
|
||||
|
||||
async def _delete_callback(self, dev_id):
|
||||
"""Remove this entity."""
|
||||
if dev_id == self.object_id:
|
||||
entity_registry = (
|
||||
await self.hass.helpers.entity_registry.async_get_registry()
|
||||
)
|
||||
if entity_registry.async_is_registered(self.entity_id):
|
||||
entity_entry = entity_registry.async_get(self.entity_id)
|
||||
entity_registry.async_remove(self.entity_id)
|
||||
await cleanup_device_registry(self.hass, entity_entry.device_id)
|
||||
else:
|
||||
await self.async_remove(force_remove=True)
|
||||
|
||||
@callback
|
||||
def _update_callback(self):
|
||||
"""Call update method."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
def remove_device(self, device_id: str) -> None:
|
||||
"""Add device removed listener."""
|
||||
_LOGGER.debug("tuya remove device:%s", device_id)
|
||||
self.hass.add_job(async_remove_hass_device, self.hass, device_id)
|
||||
|
76
homeassistant/components/tuya/base.py
Normal file
76
homeassistant/components/tuya/base.py
Normal 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)
|
@ -1,261 +1,484 @@
|
||||
"""Support for the Tuya climate devices."""
|
||||
from datetime import timedelta
|
||||
"""Support for Tuya Climate."""
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
ClimateEntity,
|
||||
)
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from tuya_iot import TuyaDevice, TuyaDeviceManager
|
||||
|
||||
from homeassistant.components.climate import DOMAIN as DEVICE_DOMAIN, ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_SWING_MODE,
|
||||
SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_PLATFORM,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TuyaDevice
|
||||
from .base import TuyaHaEntity
|
||||
from .const import (
|
||||
CONF_CURR_TEMP_DIVIDER,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_SET_TEMP_DIVIDED,
|
||||
CONF_TEMP_DIVIDER,
|
||||
CONF_TEMP_STEP_OVERRIDE,
|
||||
DOMAIN,
|
||||
SIGNAL_CONFIG_ENTITY,
|
||||
TUYA_DATA,
|
||||
TUYA_DEVICE_MANAGER,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
TUYA_HA_DEVICES,
|
||||
TUYA_HA_TUYA_MAP,
|
||||
)
|
||||
|
||||
DEVICE_TYPE = "climate"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
HA_STATE_TO_TUYA = {
|
||||
HVAC_MODE_AUTO: "auto",
|
||||
HVAC_MODE_COOL: "cold",
|
||||
HVAC_MODE_FAN_ONLY: "wind",
|
||||
HVAC_MODE_HEAT: "hot",
|
||||
# Air Conditioner
|
||||
# https://developer.tuya.com/en/docs/iot/f?id=K9gf46qujdmwb
|
||||
DPCODE_SWITCH = "switch"
|
||||
DPCODE_TEMP_SET = "temp_set"
|
||||
DPCODE_TEMP_SET_F = "temp_set_f"
|
||||
DPCODE_MODE = "mode"
|
||||
DPCODE_HUMIDITY_SET = "humidity_set"
|
||||
DPCODE_FAN_SPEED_ENUM = "fan_speed_enum"
|
||||
|
||||
# Temperature unit
|
||||
DPCODE_TEMP_UNIT_CONVERT = "temp_unit_convert"
|
||||
DPCODE_C_F = "c_f"
|
||||
|
||||
# swing flap switch
|
||||
DPCODE_SWITCH_HORIZONTAL = "switch_horizontal"
|
||||
DPCODE_SWITCH_VERTICAL = "switch_vertical"
|
||||
|
||||
# status
|
||||
DPCODE_TEMP_CURRENT = "temp_current"
|
||||
DPCODE_TEMP_CURRENT_F = "temp_current_f"
|
||||
DPCODE_HUMIDITY_CURRENT = "humidity_current"
|
||||
|
||||
SWING_OFF = "swing_off"
|
||||
SWING_VERTICAL = "swing_vertical"
|
||||
SWING_HORIZONTAL = "swing_horizontal"
|
||||
SWING_BOTH = "swing_both"
|
||||
|
||||
DEFAULT_MIN_TEMP = 7
|
||||
DEFAULT_MAX_TEMP = 35
|
||||
|
||||
TUYA_HVAC_TO_HA = {
|
||||
"hot": HVAC_MODE_HEAT,
|
||||
"cold": HVAC_MODE_COOL,
|
||||
"wet": HVAC_MODE_DRY,
|
||||
"wind": HVAC_MODE_FAN_ONLY,
|
||||
"auto": HVAC_MODE_AUTO,
|
||||
}
|
||||
|
||||
TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()}
|
||||
|
||||
FAN_MODES = {FAN_LOW, FAN_MEDIUM, FAN_HIGH}
|
||||
TUYA_SUPPORT_TYPE = {
|
||||
"kt", # Air conditioner
|
||||
"qn", # Heater
|
||||
"wk", # Thermostat
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up tuya sensors dynamically through tuya discovery."""
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up tuya climate dynamically through tuya discovery."""
|
||||
_LOGGER.debug("climate init")
|
||||
|
||||
platform = config_entry.data[CONF_PLATFORM]
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
|
||||
DEVICE_DOMAIN
|
||||
] = TUYA_SUPPORT_TYPE
|
||||
|
||||
async def async_discover_sensor(dev_ids):
|
||||
"""Discover and add a discovered tuya sensor."""
|
||||
@callback
|
||||
def async_discover_device(dev_ids: list[str]) -> None:
|
||||
"""Discover and add a discovered tuya climate."""
|
||||
_LOGGER.debug("climate add-> %s", dev_ids)
|
||||
if not dev_ids:
|
||||
return
|
||||
entities = await hass.async_add_executor_job(
|
||||
_setup_entities,
|
||||
hass,
|
||||
dev_ids,
|
||||
platform,
|
||||
)
|
||||
entities = _setup_entities(hass, entry, dev_ids)
|
||||
async_add_entities(entities)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
|
||||
entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
|
||||
)
|
||||
)
|
||||
|
||||
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN)
|
||||
await async_discover_sensor(devices_ids)
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
device_ids = []
|
||||
for (device_id, device) in device_manager.device_map.items():
|
||||
if device.category in TUYA_SUPPORT_TYPE:
|
||||
device_ids.append(device_id)
|
||||
async_discover_device(device_ids)
|
||||
|
||||
|
||||
def _setup_entities(hass, dev_ids, platform):
|
||||
"""Set up Tuya Climate device."""
|
||||
tuya = hass.data[DOMAIN][TUYA_DATA]
|
||||
entities = []
|
||||
for dev_id in dev_ids:
|
||||
device = tuya.get_device_by_id(dev_id)
|
||||
def _setup_entities(
|
||||
hass: HomeAssistant, entry: ConfigEntry, device_ids: list[str]
|
||||
) -> list[Entity]:
|
||||
"""Set up Tuya Climate."""
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
entities: list[Entity] = []
|
||||
for device_id in device_ids:
|
||||
device = device_manager.device_map[device_id]
|
||||
if device is None:
|
||||
continue
|
||||
entities.append(TuyaClimateEntity(device, platform))
|
||||
entities.append(TuyaHaClimate(device, device_manager))
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(device_id)
|
||||
return entities
|
||||
|
||||
|
||||
class TuyaClimateEntity(TuyaDevice, ClimateEntity):
|
||||
"""Tuya climate devices,include air conditioner,heater."""
|
||||
class TuyaHaClimate(TuyaHaEntity, ClimateEntity):
|
||||
"""Tuya Switch Device."""
|
||||
|
||||
def __init__(self, tuya, platform):
|
||||
"""Init climate device."""
|
||||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
self.operations = [HVAC_MODE_OFF]
|
||||
self._has_operation = False
|
||||
self._def_hvac_mode = HVAC_MODE_AUTO
|
||||
self._set_temp_divided = True
|
||||
self._temp_step_override = None
|
||||
self._min_temp = None
|
||||
self._max_temp = None
|
||||
|
||||
@callback
|
||||
def _process_config(self):
|
||||
"""Set device config parameter."""
|
||||
config = self._get_device_config()
|
||||
if not config:
|
||||
return
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
if unit:
|
||||
self._tuya.set_unit("FAHRENHEIT" if unit == TEMP_FAHRENHEIT else "CELSIUS")
|
||||
self._tuya.temp_divider = config.get(CONF_TEMP_DIVIDER, 0)
|
||||
self._tuya.curr_temp_divider = config.get(CONF_CURR_TEMP_DIVIDER, 0)
|
||||
self._set_temp_divided = config.get(CONF_SET_TEMP_DIVIDED, True)
|
||||
self._temp_step_override = config.get(CONF_TEMP_STEP_OVERRIDE)
|
||||
min_temp = config.get(CONF_MIN_TEMP, 0)
|
||||
max_temp = config.get(CONF_MAX_TEMP, 0)
|
||||
if min_temp >= max_temp:
|
||||
self._min_temp = self._max_temp = None
|
||||
def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
|
||||
"""Init Tuya Ha Climate."""
|
||||
super().__init__(device, device_manager)
|
||||
if DPCODE_C_F in self.tuya_device.status:
|
||||
self.dp_temp_unit = DPCODE_C_F
|
||||
else:
|
||||
self._min_temp = min_temp
|
||||
self._max_temp = max_temp
|
||||
self.dp_temp_unit = DPCODE_TEMP_UNIT_CONVERT
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Create operation list when add to hass."""
|
||||
await super().async_added_to_hass()
|
||||
self._process_config()
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
|
||||
)
|
||||
)
|
||||
|
||||
modes = self._tuya.operation_list()
|
||||
if modes is None:
|
||||
if self._def_hvac_mode not in self.operations:
|
||||
self.operations.append(self._def_hvac_mode)
|
||||
return
|
||||
|
||||
for mode in modes:
|
||||
if mode not in TUYA_STATE_TO_HA:
|
||||
continue
|
||||
ha_mode = TUYA_STATE_TO_HA[mode]
|
||||
if ha_mode not in self.operations:
|
||||
self.operations.append(ha_mode)
|
||||
self._has_operation = True
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement used by the platform."""
|
||||
unit = self._tuya.temperature_unit()
|
||||
if unit == "FAHRENHEIT":
|
||||
return TEMP_FAHRENHEIT
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if not self._tuya.state():
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
if not self._has_operation:
|
||||
return self._def_hvac_mode
|
||||
|
||||
mode = self._tuya.current_operation()
|
||||
if mode is None:
|
||||
def get_temp_set_scale(self) -> int | None:
|
||||
"""Get temperature set scale."""
|
||||
dp_temp_set = DPCODE_TEMP_SET if self.is_celsius() else DPCODE_TEMP_SET_F
|
||||
temp_set_value_range_item = self.tuya_device.status_range.get(dp_temp_set)
|
||||
if not temp_set_value_range_item:
|
||||
return None
|
||||
return TUYA_STATE_TO_HA.get(mode)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self.operations
|
||||
temp_set_value_range = json.loads(temp_set_value_range_item.values)
|
||||
return temp_set_value_range.get("scale")
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._tuya.current_temperature()
|
||||
def get_temp_current_scale(self) -> int | None:
|
||||
"""Get temperature current scale."""
|
||||
dp_temp_current = (
|
||||
DPCODE_TEMP_CURRENT if self.is_celsius() else DPCODE_TEMP_CURRENT_F
|
||||
)
|
||||
temp_current_value_range_item = self.tuya_device.status_range.get(
|
||||
dp_temp_current
|
||||
)
|
||||
if not temp_current_value_range_item:
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._tuya.target_temperature()
|
||||
temp_current_value_range = json.loads(temp_current_value_range_item.values)
|
||||
return temp_current_value_range.get("scale")
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Return the supported step of target temperature."""
|
||||
if self._temp_step_override:
|
||||
return self._temp_step_override
|
||||
return self._tuya.target_temperature_step()
|
||||
# Functions
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._tuya.current_fan_mode()
|
||||
|
||||
@property
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self._tuya.fan_list()
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
self._tuya.set_temperature(kwargs[ATTR_TEMPERATURE], self._set_temp_divided)
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
self._tuya.set_fan_mode(fan_mode)
|
||||
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
commands = []
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self._tuya.turn_off()
|
||||
commands.append({"code": DPCODE_SWITCH, "value": False})
|
||||
else:
|
||||
commands.append({"code": DPCODE_SWITCH, "value": True})
|
||||
|
||||
for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items():
|
||||
if ha_mode == hvac_mode:
|
||||
commands.append({"code": DPCODE_MODE, "value": tuya_mode})
|
||||
break
|
||||
|
||||
self._send_command(commands)
|
||||
|
||||
def set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
self._send_command([{"code": DPCODE_FAN_SPEED_ENUM, "value": fan_mode}])
|
||||
|
||||
def set_humidity(self, humidity: float) -> None:
|
||||
"""Set new target humidity."""
|
||||
self._send_command([{"code": DPCODE_HUMIDITY_SET, "value": int(humidity)}])
|
||||
|
||||
def set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set new target swing operation."""
|
||||
if swing_mode == SWING_BOTH:
|
||||
commands = [
|
||||
{"code": DPCODE_SWITCH_VERTICAL, "value": True},
|
||||
{"code": DPCODE_SWITCH_HORIZONTAL, "value": True},
|
||||
]
|
||||
elif swing_mode == SWING_HORIZONTAL:
|
||||
commands = [
|
||||
{"code": DPCODE_SWITCH_VERTICAL, "value": False},
|
||||
{"code": DPCODE_SWITCH_HORIZONTAL, "value": True},
|
||||
]
|
||||
elif swing_mode == SWING_VERTICAL:
|
||||
commands = [
|
||||
{"code": DPCODE_SWITCH_VERTICAL, "value": True},
|
||||
{"code": DPCODE_SWITCH_HORIZONTAL, "value": False},
|
||||
]
|
||||
else:
|
||||
commands = [
|
||||
{"code": DPCODE_SWITCH_VERTICAL, "value": False},
|
||||
{"code": DPCODE_SWITCH_HORIZONTAL, "value": False},
|
||||
]
|
||||
|
||||
self._send_command(commands)
|
||||
|
||||
def set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
_LOGGER.debug("climate temp-> %s", kwargs)
|
||||
code = DPCODE_TEMP_SET if self.is_celsius() else DPCODE_TEMP_SET_F
|
||||
temp_set_scale = self.get_temp_set_scale()
|
||||
if not temp_set_scale:
|
||||
return
|
||||
|
||||
if not self._tuya.state():
|
||||
self._tuya.turn_on()
|
||||
|
||||
if self._has_operation:
|
||||
self._tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode))
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
supports = 0
|
||||
if self._tuya.support_target_temperature():
|
||||
supports = supports | SUPPORT_TARGET_TEMPERATURE
|
||||
if self._tuya.support_wind_speed():
|
||||
supports = supports | SUPPORT_FAN_MODE
|
||||
return supports
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
min_temp = (
|
||||
self._min_temp if self._min_temp is not None else self._tuya.min_temp()
|
||||
self._send_command(
|
||||
[
|
||||
{
|
||||
"code": code,
|
||||
"value": int(kwargs["temperature"] * (10 ** temp_set_scale)),
|
||||
}
|
||||
]
|
||||
)
|
||||
if min_temp is not None:
|
||||
return min_temp
|
||||
return super().min_temp
|
||||
|
||||
def is_celsius(self) -> bool:
|
||||
"""Return True if device reports in Celsius."""
|
||||
if (
|
||||
self.dp_temp_unit in self.tuya_device.status
|
||||
and self.tuya_device.status.get(self.dp_temp_unit).lower() == "c"
|
||||
):
|
||||
return True
|
||||
if (
|
||||
DPCODE_TEMP_SET in self.tuya_device.status
|
||||
or DPCODE_TEMP_CURRENT in self.tuya_device.status
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return true if fan is on."""
|
||||
if self.is_celsius():
|
||||
return TEMP_CELSIUS
|
||||
return TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return the current temperature."""
|
||||
if (
|
||||
DPCODE_TEMP_CURRENT not in self.tuya_device.status
|
||||
and DPCODE_TEMP_CURRENT_F not in self.tuya_device.status
|
||||
):
|
||||
return None
|
||||
|
||||
temp_current_scale = self.get_temp_current_scale()
|
||||
if not temp_current_scale:
|
||||
return None
|
||||
|
||||
if self.is_celsius():
|
||||
temperature = self.tuya_device.status.get(DPCODE_TEMP_CURRENT)
|
||||
if not temperature:
|
||||
return None
|
||||
return temperature * 1.0 / (10 ** temp_current_scale)
|
||||
|
||||
temperature = self.tuya_device.status.get(DPCODE_TEMP_CURRENT_F)
|
||||
if not temperature:
|
||||
return None
|
||||
return temperature * 1.0 / (10 ** temp_current_scale)
|
||||
|
||||
@property
|
||||
def current_humidity(self) -> int:
|
||||
"""Return the current humidity."""
|
||||
return int(self.tuya_device.status.get(DPCODE_HUMIDITY_CURRENT, 0))
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> float | None:
|
||||
"""Return the temperature currently set to be reached."""
|
||||
temp_set_scale = self.get_temp_set_scale()
|
||||
if temp_set_scale is None:
|
||||
return None
|
||||
|
||||
dpcode_temp_set = self.tuya_device.status.get(DPCODE_TEMP_SET)
|
||||
if dpcode_temp_set is None:
|
||||
return None
|
||||
|
||||
return dpcode_temp_set * 1.0 / (10 ** temp_set_scale)
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
"""Return the maximum temperature."""
|
||||
max_temp = (
|
||||
self._max_temp if self._max_temp is not None else self._tuya.max_temp()
|
||||
scale = self.get_temp_set_scale()
|
||||
if scale is None:
|
||||
return DEFAULT_MAX_TEMP
|
||||
|
||||
if self.is_celsius():
|
||||
if DPCODE_TEMP_SET not in self.tuya_device.function:
|
||||
return DEFAULT_MAX_TEMP
|
||||
|
||||
function_item = self.tuya_device.function.get(DPCODE_TEMP_SET)
|
||||
if function_item is None:
|
||||
return DEFAULT_MAX_TEMP
|
||||
|
||||
temp_value = json.loads(function_item.values)
|
||||
|
||||
temp_max = temp_value.get("max")
|
||||
if temp_max is None:
|
||||
return DEFAULT_MAX_TEMP
|
||||
return temp_max * 1.0 / (10 ** scale)
|
||||
if DPCODE_TEMP_SET_F not in self.tuya_device.function:
|
||||
return DEFAULT_MAX_TEMP
|
||||
|
||||
function_item_f = self.tuya_device.function.get(DPCODE_TEMP_SET_F)
|
||||
if function_item_f is None:
|
||||
return DEFAULT_MAX_TEMP
|
||||
|
||||
temp_value_f = json.loads(function_item_f.values)
|
||||
|
||||
temp_max_f = temp_value_f.get("max")
|
||||
if temp_max_f is None:
|
||||
return DEFAULT_MAX_TEMP
|
||||
return temp_max_f * 1.0 / (10 ** scale)
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum temperature."""
|
||||
temp_set_scal = self.get_temp_set_scale()
|
||||
if temp_set_scal is None:
|
||||
return DEFAULT_MIN_TEMP
|
||||
|
||||
if self.is_celsius():
|
||||
if DPCODE_TEMP_SET not in self.tuya_device.function:
|
||||
return DEFAULT_MIN_TEMP
|
||||
|
||||
function_temp_item = self.tuya_device.function.get(DPCODE_TEMP_SET)
|
||||
if function_temp_item is None:
|
||||
return DEFAULT_MIN_TEMP
|
||||
temp_value = json.loads(function_temp_item.values)
|
||||
temp_min = temp_value.get("min")
|
||||
if temp_min is None:
|
||||
return DEFAULT_MIN_TEMP
|
||||
return temp_min * 1.0 / (10 ** temp_set_scal)
|
||||
|
||||
if DPCODE_TEMP_SET_F not in self.tuya_device.function:
|
||||
return DEFAULT_MIN_TEMP
|
||||
|
||||
temp_value_temp_f = self.tuya_device.function.get(DPCODE_TEMP_SET_F)
|
||||
if temp_value_temp_f is None:
|
||||
return DEFAULT_MIN_TEMP
|
||||
temp_value_f = json.loads(temp_value_temp_f.values)
|
||||
|
||||
temp_min_f = temp_value_f.get("min")
|
||||
if temp_min_f is None:
|
||||
return DEFAULT_MIN_TEMP
|
||||
|
||||
return temp_min_f * 1.0 / (10 ** temp_set_scal)
|
||||
|
||||
@property
|
||||
def target_temperature_step(self) -> float | None:
|
||||
"""Return target temperature setp."""
|
||||
if (
|
||||
DPCODE_TEMP_SET not in self.tuya_device.status_range
|
||||
and DPCODE_TEMP_SET_F not in self.tuya_device.status_range
|
||||
):
|
||||
return 1.0
|
||||
temp_set_value_range = json.loads(
|
||||
self.tuya_device.status_range.get(
|
||||
DPCODE_TEMP_SET if self.is_celsius() else DPCODE_TEMP_SET_F
|
||||
).values
|
||||
)
|
||||
if max_temp is not None:
|
||||
return max_temp
|
||||
return super().max_temp
|
||||
step = temp_set_value_range.get("step")
|
||||
if step is None:
|
||||
return None
|
||||
|
||||
temp_set_scale = self.get_temp_set_scale()
|
||||
if temp_set_scale is None:
|
||||
return None
|
||||
|
||||
return step * 1.0 / (10 ** temp_set_scale)
|
||||
|
||||
@property
|
||||
def target_humidity(self) -> int:
|
||||
"""Return target humidity."""
|
||||
return int(self.tuya_device.status.get(DPCODE_HUMIDITY_SET, 0))
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac mode."""
|
||||
if not self.tuya_device.status.get(DPCODE_SWITCH, False):
|
||||
return HVAC_MODE_OFF
|
||||
if DPCODE_MODE not in self.tuya_device.status:
|
||||
return HVAC_MODE_OFF
|
||||
if self.tuya_device.status.get(DPCODE_MODE) is not None:
|
||||
return TUYA_HVAC_TO_HA[self.tuya_device.status[DPCODE_MODE]]
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[str]:
|
||||
"""Return hvac modes for select."""
|
||||
if DPCODE_MODE not in self.tuya_device.function:
|
||||
return []
|
||||
modes = json.loads(self.tuya_device.function.get(DPCODE_MODE, {}).values).get(
|
||||
"range"
|
||||
)
|
||||
|
||||
hvac_modes = [HVAC_MODE_OFF]
|
||||
for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items():
|
||||
if tuya_mode in modes:
|
||||
hvac_modes.append(ha_mode)
|
||||
|
||||
return hvac_modes
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> str | None:
|
||||
"""Return fan mode."""
|
||||
return self.tuya_device.status.get(DPCODE_FAN_SPEED_ENUM)
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> list[str]:
|
||||
"""Return fan modes for select."""
|
||||
data = json.loads(
|
||||
self.tuya_device.function.get(DPCODE_FAN_SPEED_ENUM, {}).values
|
||||
).get("range")
|
||||
return data
|
||||
|
||||
@property
|
||||
def swing_mode(self) -> str:
|
||||
"""Return swing mode."""
|
||||
mode = 0
|
||||
if (
|
||||
DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status
|
||||
and self.tuya_device.status.get(DPCODE_SWITCH_HORIZONTAL)
|
||||
):
|
||||
mode += 1
|
||||
if (
|
||||
DPCODE_SWITCH_VERTICAL in self.tuya_device.status
|
||||
and self.tuya_device.status.get(DPCODE_SWITCH_VERTICAL)
|
||||
):
|
||||
mode += 2
|
||||
|
||||
if mode == 3:
|
||||
return SWING_BOTH
|
||||
if mode == 2:
|
||||
return SWING_VERTICAL
|
||||
if mode == 1:
|
||||
return SWING_HORIZONTAL
|
||||
return SWING_OFF
|
||||
|
||||
@property
|
||||
def swing_modes(self) -> list[str]:
|
||||
"""Return swing mode for select."""
|
||||
return [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH]
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
supports = 0
|
||||
if (
|
||||
DPCODE_TEMP_SET in self.tuya_device.status
|
||||
or DPCODE_TEMP_SET_F in self.tuya_device.status
|
||||
):
|
||||
supports |= SUPPORT_TARGET_TEMPERATURE
|
||||
if DPCODE_FAN_SPEED_ENUM in self.tuya_device.status:
|
||||
supports |= SUPPORT_FAN_MODE
|
||||
if DPCODE_HUMIDITY_SET in self.tuya_device.status:
|
||||
supports |= SUPPORT_TARGET_HUMIDITY
|
||||
if (
|
||||
DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status
|
||||
or DPCODE_SWITCH_VERTICAL in self.tuya_device.status
|
||||
):
|
||||
supports |= SUPPORT_SWING_MODE
|
||||
return supports
|
||||
|
@ -1,409 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Config flow for Tuya."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from tuyaha import TuyaApi
|
||||
from tuyaha.tuyaapi import (
|
||||
TuyaAPIException,
|
||||
TuyaAPIRateLimitException,
|
||||
TuyaNetException,
|
||||
TuyaServerException,
|
||||
)
|
||||
from tuya_iot import ProjectType, TuyaOpenAPI
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_USERNAME,
|
||||
ENTITY_MATCH_NONE,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import (
|
||||
CONF_BRIGHTNESS_RANGE_MODE,
|
||||
CONF_COUNTRYCODE,
|
||||
CONF_CURR_TEMP_DIVIDER,
|
||||
CONF_DISCOVERY_INTERVAL,
|
||||
CONF_MAX_KELVIN,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_KELVIN,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_QUERY_DEVICE,
|
||||
CONF_QUERY_INTERVAL,
|
||||
CONF_SET_TEMP_DIVIDED,
|
||||
CONF_SUPPORT_COLOR,
|
||||
CONF_TEMP_DIVIDER,
|
||||
CONF_TEMP_STEP_OVERRIDE,
|
||||
CONF_TUYA_MAX_COLTEMP,
|
||||
DEFAULT_DISCOVERY_INTERVAL,
|
||||
DEFAULT_QUERY_INTERVAL,
|
||||
DEFAULT_TUYA_MAX_COLTEMP,
|
||||
CONF_ACCESS_ID,
|
||||
CONF_ACCESS_SECRET,
|
||||
CONF_APP_TYPE,
|
||||
CONF_COUNTRY_CODE,
|
||||
CONF_ENDPOINT,
|
||||
CONF_PASSWORD,
|
||||
CONF_PROJECT_TYPE,
|
||||
CONF_USERNAME,
|
||||
DOMAIN,
|
||||
TUYA_DATA,
|
||||
TUYA_PLATFORMS,
|
||||
TUYA_TYPE_NOT_QUERY,
|
||||
TUYA_APP_TYPE,
|
||||
TUYA_ENDPOINT,
|
||||
TUYA_PROJECT_TYPE,
|
||||
)
|
||||
|
||||
RESULT_SINGLE_INSTANCE = "single_instance_allowed"
|
||||
RESULT_AUTH_FAILED = "invalid_auth"
|
||||
TUYA_ENDPOINT_BASE = "https://openapi.tuyacn.com"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_LIST_DEVICES = "list_devices"
|
||||
# Project Type
|
||||
DATA_SCHEMA_PROJECT_TYPE = vol.Schema(
|
||||
{vol.Required(CONF_PROJECT_TYPE, default=0): vol.In(TUYA_PROJECT_TYPE)}
|
||||
)
|
||||
|
||||
DATA_SCHEMA_USER = vol.Schema(
|
||||
# INDUSTY_SOLUTIONS Schema
|
||||
DATA_SCHEMA_INDUSTRY_SOLUTIONS = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ENDPOINT): vol.In(TUYA_ENDPOINT),
|
||||
vol.Required(CONF_ACCESS_ID): str,
|
||||
vol.Required(CONF_ACCESS_SECRET): str,
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_COUNTRYCODE): vol.Coerce(int),
|
||||
vol.Required(CONF_PLATFORM): vol.In(TUYA_PLATFORMS),
|
||||
}
|
||||
)
|
||||
|
||||
ERROR_DEV_MULTI_TYPE = "dev_multi_type"
|
||||
ERROR_DEV_NOT_CONFIG = "dev_not_config"
|
||||
ERROR_DEV_NOT_FOUND = "dev_not_found"
|
||||
|
||||
RESULT_AUTH_FAILED = "invalid_auth"
|
||||
RESULT_CONN_ERROR = "cannot_connect"
|
||||
RESULT_SINGLE_INSTANCE = "single_instance_allowed"
|
||||
RESULT_SUCCESS = "success"
|
||||
|
||||
RESULT_LOG_MESSAGE = {
|
||||
RESULT_AUTH_FAILED: "Invalid credential",
|
||||
RESULT_CONN_ERROR: "Connection error",
|
||||
}
|
||||
|
||||
TUYA_TYPE_CONFIG = ["climate", "light"]
|
||||
# SMART_HOME Schema
|
||||
DATA_SCHEMA_SMART_HOME = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ACCESS_ID): str,
|
||||
vol.Required(CONF_ACCESS_SECRET): str,
|
||||
vol.Required(CONF_APP_TYPE): vol.In(TUYA_APP_TYPE),
|
||||
vol.Required(CONF_COUNTRY_CODE): str,
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a tuya config flow."""
|
||||
|
||||
VERSION = 1
|
||||
"""Tuya Config Flow."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize flow."""
|
||||
self._country_code = None
|
||||
self._password = None
|
||||
self._platform = None
|
||||
self._username = None
|
||||
"""Init tuya config flow."""
|
||||
super().__init__()
|
||||
self.conf_project_type = None
|
||||
|
||||
def _save_entry(self):
|
||||
return self.async_create_entry(
|
||||
title=self._username,
|
||||
data={
|
||||
CONF_COUNTRYCODE: self._country_code,
|
||||
CONF_PASSWORD: self._password,
|
||||
CONF_PLATFORM: self._platform,
|
||||
CONF_USERNAME: self._username,
|
||||
},
|
||||
@staticmethod
|
||||
def _try_login(user_input):
|
||||
project_type = ProjectType(user_input[CONF_PROJECT_TYPE])
|
||||
api = TuyaOpenAPI(
|
||||
user_input[CONF_ENDPOINT]
|
||||
if project_type == ProjectType.INDUSTY_SOLUTIONS
|
||||
else "",
|
||||
user_input[CONF_ACCESS_ID],
|
||||
user_input[CONF_ACCESS_SECRET],
|
||||
project_type,
|
||||
)
|
||||
api.set_dev_channel("hass")
|
||||
|
||||
def _try_connect(self):
|
||||
"""Try to connect and check auth."""
|
||||
tuya = TuyaApi()
|
||||
try:
|
||||
tuya.init(
|
||||
self._username, self._password, self._country_code, self._platform
|
||||
if project_type == ProjectType.INDUSTY_SOLUTIONS:
|
||||
response = api.login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
|
||||
else:
|
||||
api.endpoint = TUYA_ENDPOINT_BASE
|
||||
response = api.login(
|
||||
user_input[CONF_USERNAME],
|
||||
user_input[CONF_PASSWORD],
|
||||
user_input[CONF_COUNTRY_CODE],
|
||||
user_input[CONF_APP_TYPE],
|
||||
)
|
||||
except (TuyaAPIRateLimitException, TuyaNetException, TuyaServerException):
|
||||
return RESULT_CONN_ERROR
|
||||
except TuyaAPIException:
|
||||
return RESULT_AUTH_FAILED
|
||||
if response.get("success", False) and isinstance(
|
||||
api.token_info.platform_url, str
|
||||
):
|
||||
api.endpoint = api.token_info.platform_url
|
||||
user_input[CONF_ENDPOINT] = api.token_info.platform_url
|
||||
|
||||
return RESULT_SUCCESS
|
||||
_LOGGER.debug("TuyaConfigFlow._try_login finish, response:, %s", response)
|
||||
return response
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason=RESULT_SINGLE_INSTANCE)
|
||||
"""Step user."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA_PROJECT_TYPE
|
||||
)
|
||||
|
||||
self.conf_project_type = user_input[CONF_PROJECT_TYPE]
|
||||
|
||||
return await self.async_step_login()
|
||||
|
||||
async def async_step_login(self, user_input=None):
|
||||
"""Step login."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
assert self.conf_project_type is not None
|
||||
user_input[CONF_PROJECT_TYPE] = self.conf_project_type
|
||||
|
||||
self._country_code = str(user_input[CONF_COUNTRYCODE])
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
self._platform = user_input[CONF_PLATFORM]
|
||||
self._username = user_input[CONF_USERNAME]
|
||||
|
||||
result = await self.hass.async_add_executor_job(self._try_connect)
|
||||
|
||||
if result == RESULT_SUCCESS:
|
||||
return self._save_entry()
|
||||
if result != RESULT_AUTH_FAILED:
|
||||
return self.async_abort(reason=result)
|
||||
errors["base"] = result
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Get the options flow for this handler."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle a option flow for Tuya."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
self._conf_devs_id = None
|
||||
self._conf_devs_option: dict[str, Any] = {}
|
||||
self._form_error = None
|
||||
|
||||
def _get_form_error(self):
|
||||
"""Set the error to be shown in the options form."""
|
||||
errors = {}
|
||||
if self._form_error:
|
||||
errors["base"] = self._form_error
|
||||
self._form_error = None
|
||||
return errors
|
||||
|
||||
def _get_tuya_devices_filtered(self, types, exclude_mode=False, type_prefix=True):
|
||||
"""Get the list of Tuya device to filtered by types."""
|
||||
config_list = {}
|
||||
types_filter = set(types)
|
||||
tuya = self.hass.data[DOMAIN][TUYA_DATA]
|
||||
devices_list = tuya.get_all_devices()
|
||||
for device in devices_list:
|
||||
dev_type = device.device_type()
|
||||
exclude = (
|
||||
dev_type in types_filter
|
||||
if exclude_mode
|
||||
else dev_type not in types_filter
|
||||
)
|
||||
if exclude:
|
||||
continue
|
||||
dev_id = device.object_id()
|
||||
if type_prefix:
|
||||
dev_id = f"{dev_type}-{dev_id}"
|
||||
config_list[dev_id] = f"{device.name()} ({dev_type})"
|
||||
|
||||
return config_list
|
||||
|
||||
def _get_device(self, dev_id):
|
||||
"""Get specific device from tuya library."""
|
||||
tuya = self.hass.data[DOMAIN][TUYA_DATA]
|
||||
return tuya.get_device_by_id(dev_id)
|
||||
|
||||
def _save_config(self, data):
|
||||
"""Save the updated options."""
|
||||
curr_conf = self.config_entry.options.copy()
|
||||
curr_conf.update(data)
|
||||
curr_conf.update(self._conf_devs_option)
|
||||
|
||||
return self.async_create_entry(title="", data=curr_conf)
|
||||
|
||||
async def _async_device_form(self, devs_id):
|
||||
"""Return configuration form for devices."""
|
||||
conf_devs_id = []
|
||||
for count, dev_id in enumerate(devs_id):
|
||||
device_info = dev_id.split("-")
|
||||
if count == 0:
|
||||
device_type = device_info[0]
|
||||
device_id = device_info[1]
|
||||
elif device_type != device_info[0]:
|
||||
self._form_error = ERROR_DEV_MULTI_TYPE
|
||||
return await self.async_step_init()
|
||||
conf_devs_id.append(device_info[1])
|
||||
|
||||
device = self._get_device(device_id)
|
||||
if not device:
|
||||
self._form_error = ERROR_DEV_NOT_FOUND
|
||||
return await self.async_step_init()
|
||||
|
||||
curr_conf = self._conf_devs_option.get(
|
||||
device_id, self.config_entry.options.get(device_id, {})
|
||||
)
|
||||
|
||||
config_schema = self._get_device_schema(device_type, curr_conf, device)
|
||||
if not config_schema:
|
||||
self._form_error = ERROR_DEV_NOT_CONFIG
|
||||
return await self.async_step_init()
|
||||
|
||||
self._conf_devs_id = conf_devs_id
|
||||
device_name = (
|
||||
"(multiple devices selected)" if len(conf_devs_id) > 1 else device.name()
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="device",
|
||||
data_schema=config_schema,
|
||||
description_placeholders={
|
||||
"device_type": device_type,
|
||||
"device_name": device_name,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Handle options flow."""
|
||||
|
||||
if self.config_entry.state is not config_entries.ConfigEntryState.LOADED:
|
||||
_LOGGER.error("Tuya integration not yet loaded")
|
||||
return self.async_abort(reason=RESULT_CONN_ERROR)
|
||||
|
||||
if user_input is not None:
|
||||
dev_ids = user_input.get(CONF_LIST_DEVICES)
|
||||
if dev_ids:
|
||||
return await self.async_step_device(None, dev_ids)
|
||||
|
||||
user_input.pop(CONF_LIST_DEVICES, [])
|
||||
return self._save_config(data=user_input)
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_DISCOVERY_INTERVAL,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL
|
||||
),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=30, max=900)),
|
||||
}
|
||||
)
|
||||
|
||||
query_devices = self._get_tuya_devices_filtered(
|
||||
TUYA_TYPE_NOT_QUERY, True, False
|
||||
)
|
||||
if query_devices:
|
||||
devices = {ENTITY_MATCH_NONE: "Default"}
|
||||
devices.update(query_devices)
|
||||
def_val = self.config_entry.options.get(CONF_QUERY_DEVICE)
|
||||
if not def_val or not query_devices.get(def_val):
|
||||
def_val = ENTITY_MATCH_NONE
|
||||
data_schema = data_schema.extend(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_QUERY_INTERVAL,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL
|
||||
),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=30, max=240)),
|
||||
vol.Optional(CONF_QUERY_DEVICE, default=def_val): vol.In(devices),
|
||||
}
|
||||
response = await self.hass.async_add_executor_job(
|
||||
self._try_login, user_input
|
||||
)
|
||||
|
||||
config_devices = self._get_tuya_devices_filtered(TUYA_TYPE_CONFIG, False, True)
|
||||
if config_devices:
|
||||
data_schema = data_schema.extend(
|
||||
{vol.Optional(CONF_LIST_DEVICES): cv.multi_select(config_devices)}
|
||||
if response.get("success", False):
|
||||
_LOGGER.debug("TuyaConfigFlow.async_step_user login success")
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_USERNAME],
|
||||
data=user_input,
|
||||
)
|
||||
errors["base"] = RESULT_AUTH_FAILED
|
||||
|
||||
if ProjectType(self.conf_project_type) == ProjectType.SMART_HOME:
|
||||
return self.async_show_form(
|
||||
step_id="login", data_schema=DATA_SCHEMA_SMART_HOME, errors=errors
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=data_schema,
|
||||
errors=self._get_form_error(),
|
||||
step_id="login",
|
||||
data_schema=DATA_SCHEMA_INDUSTRY_SOLUTIONS,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_device(self, user_input=None, dev_ids=None):
|
||||
"""Handle options flow for device."""
|
||||
if dev_ids is not None:
|
||||
return await self._async_device_form(dev_ids)
|
||||
if user_input is not None:
|
||||
for device_id in self._conf_devs_id:
|
||||
self._conf_devs_option[device_id] = user_input
|
||||
|
||||
return await self.async_step_init()
|
||||
|
||||
def _get_device_schema(self, device_type, curr_conf, device):
|
||||
"""Return option schema for device."""
|
||||
if device_type != device.device_type():
|
||||
return None
|
||||
schema = None
|
||||
if device_type == "light":
|
||||
schema = self._get_light_schema(curr_conf, device)
|
||||
elif device_type == "climate":
|
||||
schema = self._get_climate_schema(curr_conf, device)
|
||||
return schema
|
||||
|
||||
@staticmethod
|
||||
def _get_light_schema(curr_conf, device):
|
||||
"""Create option schema for light device."""
|
||||
min_kelvin = device.max_color_temp()
|
||||
max_kelvin = device.min_color_temp()
|
||||
|
||||
config_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_SUPPORT_COLOR,
|
||||
default=curr_conf.get(CONF_SUPPORT_COLOR, False),
|
||||
): bool,
|
||||
vol.Optional(
|
||||
CONF_BRIGHTNESS_RANGE_MODE,
|
||||
default=curr_conf.get(CONF_BRIGHTNESS_RANGE_MODE, 0),
|
||||
): vol.In({0: "Range 1-255", 1: "Range 10-1000"}),
|
||||
vol.Optional(
|
||||
CONF_MIN_KELVIN,
|
||||
default=curr_conf.get(CONF_MIN_KELVIN, min_kelvin),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=min_kelvin, max=max_kelvin)),
|
||||
vol.Optional(
|
||||
CONF_MAX_KELVIN,
|
||||
default=curr_conf.get(CONF_MAX_KELVIN, max_kelvin),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=min_kelvin, max=max_kelvin)),
|
||||
vol.Optional(
|
||||
CONF_TUYA_MAX_COLTEMP,
|
||||
default=curr_conf.get(
|
||||
CONF_TUYA_MAX_COLTEMP, DEFAULT_TUYA_MAX_COLTEMP
|
||||
),
|
||||
): vol.All(
|
||||
vol.Coerce(int),
|
||||
vol.Clamp(
|
||||
min=DEFAULT_TUYA_MAX_COLTEMP, max=DEFAULT_TUYA_MAX_COLTEMP * 10
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return config_schema
|
||||
|
||||
@staticmethod
|
||||
def _get_climate_schema(curr_conf, device):
|
||||
"""Create option schema for climate device."""
|
||||
unit = device.temperature_unit()
|
||||
def_unit = TEMP_FAHRENHEIT if unit == "FAHRENHEIT" else TEMP_CELSIUS
|
||||
supported_steps = device.supported_temperature_steps()
|
||||
default_step = device.target_temperature_step()
|
||||
|
||||
config_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
default=curr_conf.get(CONF_UNIT_OF_MEASUREMENT, def_unit),
|
||||
): vol.In({TEMP_CELSIUS: "Celsius", TEMP_FAHRENHEIT: "Fahrenheit"}),
|
||||
vol.Optional(
|
||||
CONF_TEMP_DIVIDER,
|
||||
default=curr_conf.get(CONF_TEMP_DIVIDER, 0),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=0)),
|
||||
vol.Optional(
|
||||
CONF_CURR_TEMP_DIVIDER,
|
||||
default=curr_conf.get(CONF_CURR_TEMP_DIVIDER, 0),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=0)),
|
||||
vol.Optional(
|
||||
CONF_SET_TEMP_DIVIDED,
|
||||
default=curr_conf.get(CONF_SET_TEMP_DIVIDED, True),
|
||||
): bool,
|
||||
vol.Optional(
|
||||
CONF_TEMP_STEP_OVERRIDE,
|
||||
default=curr_conf.get(CONF_TEMP_STEP_OVERRIDE, default_step),
|
||||
): vol.In(supported_steps),
|
||||
vol.Optional(
|
||||
CONF_MIN_TEMP,
|
||||
default=curr_conf.get(CONF_MIN_TEMP, 0),
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_MAX_TEMP,
|
||||
default=curr_conf.get(CONF_MAX_TEMP, 0),
|
||||
): int,
|
||||
}
|
||||
)
|
||||
|
||||
return config_schema
|
||||
|
@ -1,39 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Constants for the Tuya integration."""
|
||||
|
||||
CONF_BRIGHTNESS_RANGE_MODE = "brightness_range_mode"
|
||||
CONF_COUNTRYCODE = "country_code"
|
||||
CONF_CURR_TEMP_DIVIDER = "curr_temp_divider"
|
||||
CONF_DISCOVERY_INTERVAL = "discovery_interval"
|
||||
CONF_MAX_KELVIN = "max_kelvin"
|
||||
CONF_MAX_TEMP = "max_temp"
|
||||
CONF_MIN_KELVIN = "min_kelvin"
|
||||
CONF_MIN_TEMP = "min_temp"
|
||||
CONF_QUERY_DEVICE = "query_device"
|
||||
CONF_QUERY_INTERVAL = "query_interval"
|
||||
CONF_SET_TEMP_DIVIDED = "set_temp_divided"
|
||||
CONF_SUPPORT_COLOR = "support_color"
|
||||
CONF_TEMP_DIVIDER = "temp_divider"
|
||||
CONF_TEMP_STEP_OVERRIDE = "temp_step_override"
|
||||
CONF_TUYA_MAX_COLTEMP = "tuya_max_coltemp"
|
||||
|
||||
DEFAULT_DISCOVERY_INTERVAL = 605
|
||||
DEFAULT_QUERY_INTERVAL = 120
|
||||
DEFAULT_TUYA_MAX_COLTEMP = 10000
|
||||
|
||||
DOMAIN = "tuya"
|
||||
|
||||
SIGNAL_CONFIG_ENTITY = "tuya_config"
|
||||
SIGNAL_DELETE_ENTITY = "tuya_delete"
|
||||
SIGNAL_UPDATE_ENTITY = "tuya_update"
|
||||
CONF_PROJECT_TYPE = "tuya_project_type"
|
||||
CONF_ENDPOINT = "endpoint"
|
||||
CONF_ACCESS_ID = "access_id"
|
||||
CONF_ACCESS_SECRET = "access_secret"
|
||||
CONF_USERNAME = "username"
|
||||
CONF_PASSWORD = "password"
|
||||
CONF_COUNTRY_CODE = "country_code"
|
||||
CONF_APP_TYPE = "tuya_app_type"
|
||||
|
||||
TUYA_DATA = "tuya_data"
|
||||
TUYA_DEVICES_CONF = "devices_config"
|
||||
TUYA_DISCOVERY_NEW = "tuya_discovery_new_{}"
|
||||
TUYA_DEVICE_MANAGER = "tuya_device_manager"
|
||||
TUYA_HOME_MANAGER = "tuya_home_manager"
|
||||
TUYA_MQTT_LISTENER = "tuya_mqtt_listener"
|
||||
TUYA_HA_TUYA_MAP = "tuya_ha_tuya_map"
|
||||
TUYA_HA_DEVICES = "tuya_ha_devices"
|
||||
|
||||
TUYA_PLATFORMS = {
|
||||
"tuya": "Tuya",
|
||||
"smart_life": "Smart Life",
|
||||
"jinvoo_smart": "Jinvoo Smart",
|
||||
TUYA_HA_SIGNAL_UPDATE_ENTITY = "tuya_entry_update"
|
||||
|
||||
TUYA_ENDPOINT = {
|
||||
"https://openapi.tuyaus.com": "America",
|
||||
"https://openapi.tuyacn.com": "China",
|
||||
"https://openapi.tuyaeu.com": "Europe",
|
||||
"https://openapi.tuyain.com": "India",
|
||||
"https://openapi-ueaz.tuyaus.com": "EasternAmerica",
|
||||
"https://openapi-weaz.tuyaeu.com": "WesternEurope",
|
||||
}
|
||||
|
||||
TUYA_TYPE_NOT_QUERY = ["scene", "switch"]
|
||||
TUYA_PROJECT_TYPE = {1: "Custom Development", 0: "Smart Home PaaS"}
|
||||
|
||||
TUYA_APP_TYPE = {"tuyaSmart": "TuyaSmart", "smartlife": "Smart Life"}
|
||||
|
||||
PLATFORMS = ["climate", "fan", "light", "scene", "switch"]
|
||||
|
@ -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()
|
@ -1,141 +1,259 @@
|
||||
"""Support for Tuya fans."""
|
||||
"""Support for Tuya Fan."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from tuya_iot import TuyaDevice, TuyaDeviceManager
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
DIRECTION_FORWARD,
|
||||
DIRECTION_REVERSE,
|
||||
DOMAIN as DEVICE_DOMAIN,
|
||||
SUPPORT_DIRECTION,
|
||||
SUPPORT_OSCILLATE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SET_SPEED,
|
||||
FanEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_PLATFORM, STATE_OFF
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.percentage import (
|
||||
ordered_list_item_to_percentage,
|
||||
percentage_to_ordered_list_item,
|
||||
)
|
||||
|
||||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
from .base import TuyaHaEntity
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
TUYA_DEVICE_MANAGER,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
TUYA_HA_DEVICES,
|
||||
TUYA_HA_TUYA_MAP,
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up tuya sensors dynamically through tuya discovery."""
|
||||
# Fan
|
||||
# https://developer.tuya.com/en/docs/iot/f?id=K9gf45vs7vkge
|
||||
DPCODE_SWITCH = "switch"
|
||||
DPCODE_FAN_SPEED = "fan_speed_percent"
|
||||
DPCODE_MODE = "mode"
|
||||
DPCODE_SWITCH_HORIZONTAL = "switch_horizontal"
|
||||
DPCODE_FAN_DIRECTION = "fan_direction"
|
||||
|
||||
platform = config_entry.data[CONF_PLATFORM]
|
||||
# Air Purifier
|
||||
# https://developer.tuya.com/en/docs/iot/s?id=K9gf48r41mn81
|
||||
DPCODE_AP_FAN_SPEED = "speed"
|
||||
DPCODE_AP_FAN_SPEED_ENUM = "fan_speed_enum"
|
||||
|
||||
async def async_discover_sensor(dev_ids):
|
||||
"""Discover and add a discovered tuya sensor."""
|
||||
TUYA_SUPPORT_TYPE = {
|
||||
"fs", # Fan
|
||||
"kj", # Air Purifier
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
):
|
||||
"""Set up tuya fan dynamically through tuya discovery."""
|
||||
_LOGGER.debug("fan init")
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
|
||||
DEVICE_DOMAIN
|
||||
] = TUYA_SUPPORT_TYPE
|
||||
|
||||
@callback
|
||||
def async_discover_device(dev_ids: list[str]) -> None:
|
||||
"""Discover and add a discovered tuya fan."""
|
||||
_LOGGER.debug("fan add-> %s", dev_ids)
|
||||
if not dev_ids:
|
||||
return
|
||||
entities = await hass.async_add_executor_job(
|
||||
_setup_entities,
|
||||
hass,
|
||||
dev_ids,
|
||||
platform,
|
||||
)
|
||||
entities = _setup_entities(hass, entry, dev_ids)
|
||||
async_add_entities(entities)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
|
||||
entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
|
||||
)
|
||||
)
|
||||
|
||||
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN)
|
||||
await async_discover_sensor(devices_ids)
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
device_ids = []
|
||||
for (device_id, device) in device_manager.device_map.items():
|
||||
if device.category in TUYA_SUPPORT_TYPE:
|
||||
device_ids.append(device_id)
|
||||
async_discover_device(device_ids)
|
||||
|
||||
|
||||
def _setup_entities(hass, dev_ids, platform):
|
||||
"""Set up Tuya Fan device."""
|
||||
tuya = hass.data[DOMAIN][TUYA_DATA]
|
||||
def _setup_entities(
|
||||
hass: HomeAssistant, entry: ConfigEntry, device_ids: list[str]
|
||||
) -> list[TuyaHaFan]:
|
||||
"""Set up Tuya Fan."""
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
entities = []
|
||||
for dev_id in dev_ids:
|
||||
device = tuya.get_device_by_id(dev_id)
|
||||
for device_id in device_ids:
|
||||
device = device_manager.device_map[device_id]
|
||||
if device is None:
|
||||
continue
|
||||
entities.append(TuyaFanDevice(device, platform))
|
||||
entities.append(TuyaHaFan(device, device_manager))
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(device_id)
|
||||
return entities
|
||||
|
||||
|
||||
class TuyaFanDevice(TuyaDevice, FanEntity):
|
||||
"""Tuya fan devices."""
|
||||
class TuyaHaFan(TuyaHaEntity, FanEntity):
|
||||
"""Tuya Fan Device."""
|
||||
|
||||
def __init__(self, tuya, platform):
|
||||
"""Init Tuya fan device."""
|
||||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
self.speeds = []
|
||||
def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
|
||||
"""Init Tuya Fan Device."""
|
||||
super().__init__(device, device_manager)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Create fan list when add to hass."""
|
||||
await super().async_added_to_hass()
|
||||
self.speeds.extend(self._tuya.speed_list())
|
||||
self.ha_preset_modes = []
|
||||
if DPCODE_MODE in self.tuya_device.function:
|
||||
self.ha_preset_modes = json.loads(
|
||||
self.tuya_device.function[DPCODE_MODE].values
|
||||
).get("range", [])
|
||||
|
||||
# Air purifier fan can be controlled either via the ranged values or via the enum.
|
||||
# We will always prefer the enumeration if available
|
||||
# Enum is used for e.g. MEES SmartHIMOX-H06
|
||||
# Range is used for e.g. Concept CA3000
|
||||
self.air_purifier_speed_range_len = 0
|
||||
self.air_purifier_speed_range_enum = []
|
||||
if self.tuya_device.category == "kj" and (
|
||||
DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.function
|
||||
or DPCODE_AP_FAN_SPEED in self.tuya_device.function
|
||||
):
|
||||
if DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.function:
|
||||
self.dp_code_speed_enum = DPCODE_AP_FAN_SPEED_ENUM
|
||||
else:
|
||||
self.dp_code_speed_enum = DPCODE_AP_FAN_SPEED
|
||||
|
||||
data = json.loads(
|
||||
self.tuya_device.function[self.dp_code_speed_enum].values
|
||||
).get("range")
|
||||
if data:
|
||||
self.air_purifier_speed_range_len = len(data)
|
||||
self.air_purifier_speed_range_enum = data
|
||||
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set the preset mode of the fan."""
|
||||
self._send_command([{"code": DPCODE_MODE, "value": preset_mode}])
|
||||
|
||||
def set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
self._send_command([{"code": DPCODE_FAN_DIRECTION, "value": direction}])
|
||||
|
||||
def set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed percentage of the fan."""
|
||||
if percentage == 0:
|
||||
self.turn_off()
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
if self.tuya_device.category == "kj":
|
||||
value_in_range = percentage_to_ordered_list_item(
|
||||
self.air_purifier_speed_range_enum, percentage
|
||||
)
|
||||
self._send_command(
|
||||
[
|
||||
{
|
||||
"code": self.dp_code_speed_enum,
|
||||
"value": value_in_range,
|
||||
}
|
||||
]
|
||||
)
|
||||
else:
|
||||
tuya_speed = percentage_to_ordered_list_item(self.speeds, percentage)
|
||||
self._tuya.set_speed(tuya_speed)
|
||||
self._send_command([{"code": DPCODE_FAN_SPEED, "value": percentage}])
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the fan off."""
|
||||
self._send_command([{"code": DPCODE_SWITCH, "value": False}])
|
||||
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if percentage is not None:
|
||||
self.set_percentage(percentage)
|
||||
else:
|
||||
self._tuya.turn_on()
|
||||
self._send_command([{"code": DPCODE_SWITCH, "value": True}])
|
||||
|
||||
def turn_off(self, **kwargs) -> None:
|
||||
"""Turn the entity off."""
|
||||
self._tuya.turn_off()
|
||||
|
||||
def oscillate(self, oscillating) -> None:
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
"""Oscillate the fan."""
|
||||
self._tuya.oscillate(oscillating)
|
||||
self._send_command([{"code": DPCODE_SWITCH_HORIZONTAL, "value": oscillating}])
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if fan is on."""
|
||||
return self.tuya_device.status.get(DPCODE_SWITCH, False)
|
||||
|
||||
@property
|
||||
def current_direction(self) -> str:
|
||||
"""Return the current direction of the fan."""
|
||||
if self.tuya_device.status[DPCODE_FAN_DIRECTION]:
|
||||
return DIRECTION_FORWARD
|
||||
return DIRECTION_REVERSE
|
||||
|
||||
@property
|
||||
def oscillating(self) -> bool:
|
||||
"""Return true if the fan is oscillating."""
|
||||
return self.tuya_device.status.get(DPCODE_SWITCH_HORIZONTAL, False)
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> list[str]:
|
||||
"""Return the list of available preset_modes."""
|
||||
return self.ha_preset_modes
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> str:
|
||||
"""Return the current preset_mode."""
|
||||
return self.tuya_device.status[DPCODE_MODE]
|
||||
|
||||
@property
|
||||
def percentage(self) -> int:
|
||||
"""Return the current speed."""
|
||||
if not self.is_on:
|
||||
return 0
|
||||
|
||||
if (
|
||||
self.tuya_device.category == "kj"
|
||||
and self.air_purifier_speed_range_len > 1
|
||||
and not self.air_purifier_speed_range_enum
|
||||
and DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.status
|
||||
):
|
||||
# if air-purifier speed enumeration is supported we will prefer it.
|
||||
return ordered_list_item_to_percentage(
|
||||
self.air_purifier_speed_range_enum,
|
||||
self.tuya_device.status[DPCODE_AP_FAN_SPEED_ENUM],
|
||||
)
|
||||
|
||||
return self.tuya_device.status[DPCODE_FAN_SPEED]
|
||||
|
||||
@property
|
||||
def speed_count(self) -> int:
|
||||
"""Return the number of speeds the fan supports."""
|
||||
if self.speeds is None:
|
||||
return super().speed_count
|
||||
return len(self.speeds)
|
||||
if self.tuya_device.category == "kj":
|
||||
return self.air_purifier_speed_range_len
|
||||
return super().speed_count
|
||||
|
||||
@property
|
||||
def oscillating(self):
|
||||
"""Return current oscillating status."""
|
||||
if self.supported_features & SUPPORT_OSCILLATE == 0:
|
||||
return None
|
||||
if self.speed == STATE_OFF:
|
||||
return False
|
||||
return self._tuya.oscillating()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the entity is on."""
|
||||
return self._tuya.state()
|
||||
|
||||
@property
|
||||
def percentage(self) -> int | None:
|
||||
"""Return the current speed."""
|
||||
if not self.is_on:
|
||||
return 0
|
||||
if self.speeds is None:
|
||||
return None
|
||||
return ordered_list_item_to_percentage(self.speeds, self._tuya.speed())
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
if self._tuya.support_oscillate():
|
||||
return SUPPORT_SET_SPEED | SUPPORT_OSCILLATE
|
||||
return SUPPORT_SET_SPEED
|
||||
supports = 0
|
||||
if DPCODE_MODE in self.tuya_device.status:
|
||||
supports |= SUPPORT_PRESET_MODE
|
||||
if DPCODE_FAN_SPEED in self.tuya_device.status:
|
||||
supports |= SUPPORT_SET_SPEED
|
||||
if DPCODE_SWITCH_HORIZONTAL in self.tuya_device.status:
|
||||
supports |= SUPPORT_OSCILLATE
|
||||
if DPCODE_FAN_DIRECTION in self.tuya_device.status:
|
||||
supports |= SUPPORT_DIRECTION
|
||||
|
||||
# Air Purifier specific
|
||||
if (
|
||||
DPCODE_AP_FAN_SPEED in self.tuya_device.status
|
||||
or DPCODE_AP_FAN_SPEED_ENUM in self.tuya_device.status
|
||||
):
|
||||
supports |= SUPPORT_SET_SPEED
|
||||
return supports
|
||||
|
@ -1,198 +1,387 @@
|
||||
"""Support for the Tuya lights."""
|
||||
from datetime import timedelta
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from tuya_iot import TuyaDevice, TuyaDeviceManager
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_HS_COLOR,
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
COLOR_MODE_BRIGHTNESS,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_HS,
|
||||
COLOR_MODE_ONOFF,
|
||||
DOMAIN as DEVICE_DOMAIN,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util import color as colorutil
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TuyaDevice
|
||||
from .base import TuyaHaEntity
|
||||
from .const import (
|
||||
CONF_BRIGHTNESS_RANGE_MODE,
|
||||
CONF_MAX_KELVIN,
|
||||
CONF_MIN_KELVIN,
|
||||
CONF_SUPPORT_COLOR,
|
||||
CONF_TUYA_MAX_COLTEMP,
|
||||
DEFAULT_TUYA_MAX_COLTEMP,
|
||||
DOMAIN,
|
||||
SIGNAL_CONFIG_ENTITY,
|
||||
TUYA_DATA,
|
||||
TUYA_DEVICE_MANAGER,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
TUYA_HA_DEVICES,
|
||||
TUYA_HA_TUYA_MAP,
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TUYA_BRIGHTNESS_RANGE0 = (1, 255)
|
||||
TUYA_BRIGHTNESS_RANGE1 = (10, 1000)
|
||||
|
||||
BRIGHTNESS_MODES = {
|
||||
0: TUYA_BRIGHTNESS_RANGE0,
|
||||
1: TUYA_BRIGHTNESS_RANGE1,
|
||||
# Light(dj)
|
||||
# https://developer.tuya.com/en/docs/iot/f?id=K9i5ql3v98hn3
|
||||
DPCODE_SWITCH = "switch_led"
|
||||
DPCODE_WORK_MODE = "work_mode"
|
||||
DPCODE_BRIGHT_VALUE = "bright_value"
|
||||
DPCODE_TEMP_VALUE = "temp_value"
|
||||
DPCODE_COLOUR_DATA = "colour_data"
|
||||
DPCODE_COLOUR_DATA_V2 = "colour_data_v2"
|
||||
DPCODE_LIGHT = "light"
|
||||
|
||||
MIREDS_MAX = 500
|
||||
MIREDS_MIN = 153
|
||||
|
||||
HSV_HA_HUE_MIN = 0
|
||||
HSV_HA_HUE_MAX = 360
|
||||
HSV_HA_SATURATION_MIN = 0
|
||||
HSV_HA_SATURATION_MAX = 100
|
||||
|
||||
WORK_MODE_WHITE = "white"
|
||||
WORK_MODE_COLOUR = "colour"
|
||||
|
||||
TUYA_SUPPORT_TYPE = {
|
||||
"dj", # Light
|
||||
"dd", # Light strip
|
||||
"fwl", # Ambient light
|
||||
"dc", # Light string
|
||||
"jsq", # Humidifier's light
|
||||
"xdd", # Ceiling Light
|
||||
"xxj", # Diffuser's light
|
||||
"fs", # Fan
|
||||
}
|
||||
|
||||
DEFAULT_HSV = {
|
||||
"h": {"min": 1, "scale": 0, "unit": "", "max": 360, "step": 1},
|
||||
"s": {"min": 1, "scale": 0, "unit": "", "max": 255, "step": 1},
|
||||
"v": {"min": 1, "scale": 0, "unit": "", "max": 255, "step": 1},
|
||||
}
|
||||
|
||||
DEFAULT_HSV_V2 = {
|
||||
"h": {"min": 1, "scale": 0, "unit": "", "max": 360, "step": 1},
|
||||
"s": {"min": 1, "scale": 0, "unit": "", "max": 1000, "step": 1},
|
||||
"v": {"min": 1, "scale": 0, "unit": "", "max": 1000, "step": 1},
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up tuya sensors dynamically through tuya discovery."""
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up tuya light dynamically through tuya discovery."""
|
||||
_LOGGER.debug("light init")
|
||||
|
||||
platform = config_entry.data[CONF_PLATFORM]
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
|
||||
DEVICE_DOMAIN
|
||||
] = TUYA_SUPPORT_TYPE
|
||||
|
||||
async def async_discover_sensor(dev_ids):
|
||||
"""Discover and add a discovered tuya sensor."""
|
||||
@callback
|
||||
def async_discover_device(dev_ids: list[str]):
|
||||
"""Discover and add a discovered tuya light."""
|
||||
_LOGGER.debug("light add-> %s", dev_ids)
|
||||
if not dev_ids:
|
||||
return
|
||||
entities = await hass.async_add_executor_job(
|
||||
_setup_entities,
|
||||
hass,
|
||||
dev_ids,
|
||||
platform,
|
||||
)
|
||||
entities = _setup_entities(hass, entry, dev_ids)
|
||||
async_add_entities(entities)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
|
||||
entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
|
||||
)
|
||||
)
|
||||
|
||||
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN)
|
||||
await async_discover_sensor(devices_ids)
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
device_ids = []
|
||||
for (device_id, device) in device_manager.device_map.items():
|
||||
if device.category in TUYA_SUPPORT_TYPE:
|
||||
device_ids.append(device_id)
|
||||
async_discover_device(device_ids)
|
||||
|
||||
|
||||
def _setup_entities(hass, dev_ids, platform):
|
||||
def _setup_entities(
|
||||
hass, entry: ConfigEntry, device_ids: list[str]
|
||||
) -> list[TuyaHaLight]:
|
||||
"""Set up Tuya Light device."""
|
||||
tuya = hass.data[DOMAIN][TUYA_DATA]
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
entities = []
|
||||
for dev_id in dev_ids:
|
||||
device = tuya.get_device_by_id(dev_id)
|
||||
for device_id in device_ids:
|
||||
device = device_manager.device_map[device_id]
|
||||
if device is None:
|
||||
continue
|
||||
entities.append(TuyaLight(device, platform))
|
||||
|
||||
tuya_ha_light = TuyaHaLight(device, device_manager)
|
||||
entities.append(tuya_ha_light)
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(
|
||||
tuya_ha_light.tuya_device.id
|
||||
)
|
||||
|
||||
return entities
|
||||
|
||||
|
||||
class TuyaLight(TuyaDevice, LightEntity):
|
||||
class TuyaHaLight(TuyaHaEntity, LightEntity):
|
||||
"""Tuya light device."""
|
||||
|
||||
def __init__(self, tuya, platform):
|
||||
"""Init Tuya light device."""
|
||||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
self._min_kelvin = tuya.max_color_temp()
|
||||
self._max_kelvin = tuya.min_color_temp()
|
||||
def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
|
||||
"""Init TuyaHaLight."""
|
||||
self.dp_code_bright = DPCODE_BRIGHT_VALUE
|
||||
self.dp_code_temp = DPCODE_TEMP_VALUE
|
||||
self.dp_code_colour = DPCODE_COLOUR_DATA
|
||||
|
||||
@callback
|
||||
def _process_config(self):
|
||||
"""Set device config parameter."""
|
||||
config = self._get_device_config()
|
||||
if not config:
|
||||
return
|
||||
for key in device.function:
|
||||
if key.startswith(DPCODE_BRIGHT_VALUE):
|
||||
self.dp_code_bright = key
|
||||
elif key.startswith(DPCODE_TEMP_VALUE):
|
||||
self.dp_code_temp = key
|
||||
elif key.startswith(DPCODE_COLOUR_DATA):
|
||||
self.dp_code_colour = key
|
||||
|
||||
# support color config
|
||||
supp_color = config.get(CONF_SUPPORT_COLOR, False)
|
||||
if supp_color:
|
||||
self._tuya.force_support_color()
|
||||
# brightness range config
|
||||
self._tuya.brightness_white_range = BRIGHTNESS_MODES.get(
|
||||
config.get(CONF_BRIGHTNESS_RANGE_MODE, 0),
|
||||
TUYA_BRIGHTNESS_RANGE0,
|
||||
)
|
||||
# color set temp range
|
||||
min_tuya = self._tuya.max_color_temp()
|
||||
min_kelvin = config.get(CONF_MIN_KELVIN, min_tuya)
|
||||
max_tuya = self._tuya.min_color_temp()
|
||||
max_kelvin = config.get(CONF_MAX_KELVIN, max_tuya)
|
||||
self._min_kelvin = min(max(min_kelvin, min_tuya), max_tuya)
|
||||
self._max_kelvin = min(max(max_kelvin, self._min_kelvin), max_tuya)
|
||||
# color shown temp range
|
||||
max_color_temp = max(
|
||||
config.get(CONF_TUYA_MAX_COLTEMP, DEFAULT_TUYA_MAX_COLTEMP),
|
||||
DEFAULT_TUYA_MAX_COLTEMP,
|
||||
)
|
||||
self._tuya.color_temp_range = (1000, max_color_temp)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Set config parameter when add to hass."""
|
||||
await super().async_added_to_hass()
|
||||
self._process_config()
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_CONFIG_ENTITY, self._process_config
|
||||
)
|
||||
)
|
||||
return
|
||||
super().__init__(device, device_manager)
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of the light."""
|
||||
if self._tuya.brightness() is None:
|
||||
return None
|
||||
return int(self._tuya.brightness())
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the hs_color of the light."""
|
||||
return tuple(map(int, self._tuya.hs_color()))
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the color_temp of the light."""
|
||||
color_temp = int(self._tuya.color_temp())
|
||||
if color_temp is None:
|
||||
return None
|
||||
return colorutil.color_temperature_kelvin_to_mired(color_temp)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if light is on."""
|
||||
return self._tuya.state()
|
||||
return self.tuya_device.status.get(DPCODE_SWITCH, False)
|
||||
|
||||
@property
|
||||
def min_mireds(self):
|
||||
"""Return color temperature min mireds."""
|
||||
return colorutil.color_temperature_kelvin_to_mired(self._max_kelvin)
|
||||
|
||||
@property
|
||||
def max_mireds(self):
|
||||
"""Return color temperature max mireds."""
|
||||
return colorutil.color_temperature_kelvin_to_mired(self._min_kelvin)
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on or control the light."""
|
||||
if (
|
||||
ATTR_BRIGHTNESS not in kwargs
|
||||
and ATTR_HS_COLOR not in kwargs
|
||||
and ATTR_COLOR_TEMP not in kwargs
|
||||
):
|
||||
self._tuya.turn_on()
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
self._tuya.set_brightness(kwargs[ATTR_BRIGHTNESS])
|
||||
if ATTR_HS_COLOR in kwargs:
|
||||
self._tuya.set_color(kwargs[ATTR_HS_COLOR])
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
color_temp = colorutil.color_temperature_mired_to_kelvin(
|
||||
kwargs[ATTR_COLOR_TEMP]
|
||||
)
|
||||
self._tuya.set_color_temp(color_temp)
|
||||
commands = []
|
||||
_LOGGER.debug("light kwargs-> %s", kwargs)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
if (
|
||||
DPCODE_LIGHT in self.tuya_device.status
|
||||
and DPCODE_SWITCH not in self.tuya_device.status
|
||||
):
|
||||
commands += [{"code": DPCODE_LIGHT, "value": True}]
|
||||
else:
|
||||
commands += [{"code": DPCODE_SWITCH, "value": True}]
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
if self._work_mode().startswith(WORK_MODE_COLOUR):
|
||||
colour_data = self._get_hsv()
|
||||
v_range = self._tuya_hsv_v_range()
|
||||
colour_data["v"] = int(
|
||||
self.remap(kwargs[ATTR_BRIGHTNESS], 0, 255, v_range[0], v_range[1])
|
||||
)
|
||||
commands += [
|
||||
{"code": self.dp_code_colour, "value": json.dumps(colour_data)}
|
||||
]
|
||||
else:
|
||||
new_range = self._tuya_brightness_range()
|
||||
tuya_brightness = int(
|
||||
self.remap(
|
||||
kwargs[ATTR_BRIGHTNESS], 0, 255, new_range[0], new_range[1]
|
||||
)
|
||||
)
|
||||
commands += [{"code": self.dp_code_bright, "value": tuya_brightness}]
|
||||
|
||||
if ATTR_HS_COLOR in kwargs:
|
||||
colour_data = self._get_hsv()
|
||||
# hsv h
|
||||
colour_data["h"] = int(kwargs[ATTR_HS_COLOR][0])
|
||||
# hsv s
|
||||
ha_s = kwargs[ATTR_HS_COLOR][1]
|
||||
s_range = self._tuya_hsv_s_range()
|
||||
colour_data["s"] = int(
|
||||
self.remap(
|
||||
ha_s,
|
||||
HSV_HA_SATURATION_MIN,
|
||||
HSV_HA_SATURATION_MAX,
|
||||
s_range[0],
|
||||
s_range[1],
|
||||
)
|
||||
)
|
||||
# hsv v
|
||||
ha_v = self.brightness
|
||||
v_range = self._tuya_hsv_v_range()
|
||||
colour_data["v"] = int(self.remap(ha_v, 0, 255, v_range[0], v_range[1]))
|
||||
|
||||
commands += [
|
||||
{"code": self.dp_code_colour, "value": json.dumps(colour_data)}
|
||||
]
|
||||
if self.tuya_device.status[DPCODE_WORK_MODE] != "colour":
|
||||
commands += [{"code": DPCODE_WORK_MODE, "value": "colour"}]
|
||||
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
# temp color
|
||||
new_range = self._tuya_temp_range()
|
||||
color_temp = self.remap(
|
||||
self.max_mireds - kwargs[ATTR_COLOR_TEMP] + self.min_mireds,
|
||||
self.min_mireds,
|
||||
self.max_mireds,
|
||||
new_range[0],
|
||||
new_range[1],
|
||||
)
|
||||
commands += [{"code": self.dp_code_temp, "value": int(color_temp)}]
|
||||
|
||||
# brightness
|
||||
ha_brightness = self.brightness
|
||||
new_range = self._tuya_brightness_range()
|
||||
tuya_brightness = self.remap(
|
||||
ha_brightness, 0, 255, new_range[0], new_range[1]
|
||||
)
|
||||
commands += [{"code": self.dp_code_bright, "value": int(tuya_brightness)}]
|
||||
|
||||
if self.tuya_device.status[DPCODE_WORK_MODE] != "white":
|
||||
commands += [{"code": DPCODE_WORK_MODE, "value": "white"}]
|
||||
|
||||
self._send_command(commands)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn off."""
|
||||
self._tuya.turn_off()
|
||||
if (
|
||||
DPCODE_LIGHT in self.tuya_device.status
|
||||
and DPCODE_SWITCH not in self.tuya_device.status
|
||||
):
|
||||
commands = [{"code": DPCODE_LIGHT, "value": False}]
|
||||
else:
|
||||
commands = [{"code": DPCODE_SWITCH, "value": False}]
|
||||
self._send_command(commands)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
supports = SUPPORT_BRIGHTNESS
|
||||
if self._tuya.support_color():
|
||||
supports = supports | SUPPORT_COLOR
|
||||
if self._tuya.support_color_temp():
|
||||
supports = supports | SUPPORT_COLOR_TEMP
|
||||
return supports
|
||||
def brightness(self) -> int | None:
|
||||
"""Return the brightness of the light."""
|
||||
old_range = self._tuya_brightness_range()
|
||||
brightness = self.tuya_device.status.get(self.dp_code_bright, 0)
|
||||
|
||||
if self._work_mode().startswith(WORK_MODE_COLOUR):
|
||||
colour_json = self.tuya_device.status.get(self.dp_code_colour)
|
||||
if not colour_json:
|
||||
return None
|
||||
colour_data = json.loads(colour_json)
|
||||
v_range = self._tuya_hsv_v_range()
|
||||
hsv_v = colour_data.get("v", 0)
|
||||
return int(self.remap(hsv_v, v_range[0], v_range[1], 0, 255))
|
||||
|
||||
return int(self.remap(brightness, old_range[0], old_range[1], 0, 255))
|
||||
|
||||
def _tuya_brightness_range(self) -> tuple[int, int]:
|
||||
if self.dp_code_bright not in self.tuya_device.status:
|
||||
return 0, 255
|
||||
bright_item = self.tuya_device.function.get(self.dp_code_bright)
|
||||
if not bright_item:
|
||||
return 0, 255
|
||||
bright_value = json.loads(bright_item.values)
|
||||
return bright_value.get("min", 0), bright_value.get("max", 255)
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple[float, float] | None:
|
||||
"""Return the hs_color of the light."""
|
||||
colour_json = self.tuya_device.status.get(self.dp_code_colour)
|
||||
if not colour_json:
|
||||
return None
|
||||
colour_data = json.loads(colour_json)
|
||||
s_range = self._tuya_hsv_s_range()
|
||||
return colour_data.get("h", 0), self.remap(
|
||||
colour_data.get("s", 0),
|
||||
s_range[0],
|
||||
s_range[1],
|
||||
HSV_HA_SATURATION_MIN,
|
||||
HSV_HA_SATURATION_MAX,
|
||||
)
|
||||
|
||||
@property
|
||||
def color_temp(self) -> int:
|
||||
"""Return the color_temp of the light."""
|
||||
new_range = self._tuya_temp_range()
|
||||
tuya_color_temp = self.tuya_device.status.get(self.dp_code_temp, 0)
|
||||
ha_color_temp = (
|
||||
self.max_mireds
|
||||
- self.remap(
|
||||
tuya_color_temp,
|
||||
new_range[0],
|
||||
new_range[1],
|
||||
self.min_mireds,
|
||||
self.max_mireds,
|
||||
)
|
||||
+ self.min_mireds
|
||||
)
|
||||
return ha_color_temp
|
||||
|
||||
@property
|
||||
def min_mireds(self) -> int:
|
||||
"""Return color temperature min mireds."""
|
||||
return MIREDS_MIN
|
||||
|
||||
@property
|
||||
def max_mireds(self) -> int:
|
||||
"""Return color temperature max mireds."""
|
||||
return MIREDS_MAX
|
||||
|
||||
def _tuya_temp_range(self) -> tuple[int, int]:
|
||||
temp_item = self.tuya_device.function.get(self.dp_code_temp)
|
||||
if not temp_item:
|
||||
return 0, 255
|
||||
temp_value = json.loads(temp_item.values)
|
||||
return temp_value.get("min", 0), temp_value.get("max", 255)
|
||||
|
||||
def _tuya_hsv_s_range(self) -> tuple[int, int]:
|
||||
hsv_data_range = self._tuya_hsv_function()
|
||||
if hsv_data_range is not None:
|
||||
hsv_s = hsv_data_range.get("s", {"min": 0, "max": 255})
|
||||
return hsv_s.get("min", 0), hsv_s.get("max", 255)
|
||||
return 0, 255
|
||||
|
||||
def _tuya_hsv_v_range(self) -> tuple[int, int]:
|
||||
hsv_data_range = self._tuya_hsv_function()
|
||||
if hsv_data_range is not None:
|
||||
hsv_v = hsv_data_range.get("v", {"min": 0, "max": 255})
|
||||
return hsv_v.get("min", 0), hsv_v.get("max", 255)
|
||||
|
||||
return 0, 255
|
||||
|
||||
def _tuya_hsv_function(self) -> dict[str, dict] | None:
|
||||
hsv_item = self.tuya_device.function.get(self.dp_code_colour)
|
||||
if not hsv_item:
|
||||
return None
|
||||
hsv_data = json.loads(hsv_item.values)
|
||||
if hsv_data:
|
||||
return hsv_data
|
||||
colour_json = self.tuya_device.status.get(self.dp_code_colour)
|
||||
if not colour_json:
|
||||
return None
|
||||
colour_data = json.loads(colour_json)
|
||||
if (
|
||||
self.dp_code_colour == DPCODE_COLOUR_DATA_V2
|
||||
or colour_data.get("v", 0) > 255
|
||||
or colour_data.get("s", 0) > 255
|
||||
):
|
||||
return DEFAULT_HSV_V2
|
||||
return DEFAULT_HSV
|
||||
|
||||
def _work_mode(self) -> str:
|
||||
return self.tuya_device.status.get(DPCODE_WORK_MODE, "")
|
||||
|
||||
def _get_hsv(self) -> dict[str, int]:
|
||||
return json.loads(self.tuya_device.status[self.dp_code_colour])
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set[str] | None:
|
||||
"""Flag supported color modes."""
|
||||
color_modes = [COLOR_MODE_ONOFF]
|
||||
if self.dp_code_bright in self.tuya_device.status:
|
||||
color_modes.append(COLOR_MODE_BRIGHTNESS)
|
||||
|
||||
if self.dp_code_temp in self.tuya_device.status:
|
||||
color_modes.append(COLOR_MODE_COLOR_TEMP)
|
||||
|
||||
if (
|
||||
self.dp_code_colour in self.tuya_device.status
|
||||
and len(self.tuya_device.status[self.dp_code_colour]) > 0
|
||||
):
|
||||
color_modes.append(COLOR_MODE_HS)
|
||||
return set(color_modes)
|
||||
|
@ -1,17 +1,13 @@
|
||||
{
|
||||
"domain": "tuya",
|
||||
"name": "Tuya",
|
||||
"documentation": "https://www.home-assistant.io/integrations/tuya",
|
||||
"requirements": ["tuyaha==0.0.10"],
|
||||
"codeowners": ["@ollo69"],
|
||||
"documentation": "https://github.com/tuya/tuya-home-assistant",
|
||||
"requirements": [
|
||||
"tuya-iot-py-sdk==0.4.1"
|
||||
],
|
||||
"codeowners": [
|
||||
"@Tuya"
|
||||
],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling",
|
||||
"dhcp": [
|
||||
{"macaddress": "508A06*"},
|
||||
{"macaddress": "7CF666*"},
|
||||
{"macaddress": "10D561*"},
|
||||
{"macaddress": "D4A651*"},
|
||||
{"macaddress": "68572D*"},
|
||||
{"macaddress": "1869D8*"}
|
||||
]
|
||||
}
|
||||
"iot_class": "cloud_push"
|
||||
}
|
@ -1,61 +1,74 @@
|
||||
"""Support for the Tuya scenes."""
|
||||
"""Support for Tuya scenes."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.scene import DOMAIN as SENSOR_DOMAIN, Scene
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from tuya_iot import TuyaHomeManager, TuyaScene
|
||||
|
||||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
from homeassistant.components.scene import Scene
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"
|
||||
from .const import DOMAIN, TUYA_HOME_MANAGER
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up tuya sensors dynamically through tuya discovery."""
|
||||
|
||||
platform = config_entry.data[CONF_PLATFORM]
|
||||
|
||||
async def async_discover_sensor(dev_ids):
|
||||
"""Discover and add a discovered tuya sensor."""
|
||||
if not dev_ids:
|
||||
return
|
||||
entities = await hass.async_add_executor_job(
|
||||
_setup_entities,
|
||||
hass,
|
||||
dev_ids,
|
||||
platform,
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
|
||||
)
|
||||
|
||||
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN)
|
||||
await async_discover_sensor(devices_ids)
|
||||
|
||||
|
||||
def _setup_entities(hass, dev_ids, platform):
|
||||
"""Set up Tuya Scene."""
|
||||
tuya = hass.data[DOMAIN][TUYA_DATA]
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up tuya scenes."""
|
||||
entities = []
|
||||
for dev_id in dev_ids:
|
||||
device = tuya.get_device_by_id(dev_id)
|
||||
if device is None:
|
||||
continue
|
||||
entities.append(TuyaScene(device, platform))
|
||||
return entities
|
||||
|
||||
home_manager = hass.data[DOMAIN][entry.entry_id][TUYA_HOME_MANAGER]
|
||||
scenes = await hass.async_add_executor_job(home_manager.query_scenes)
|
||||
for scene in scenes:
|
||||
entities.append(TuyaHAScene(home_manager, scene))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class TuyaScene(TuyaDevice, Scene):
|
||||
"""Tuya Scene."""
|
||||
class TuyaHAScene(Scene):
|
||||
"""Tuya Scene Remote."""
|
||||
|
||||
def __init__(self, tuya, platform):
|
||||
"""Init Tuya scene."""
|
||||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
def __init__(self, home_manager: TuyaHomeManager, scene: TuyaScene) -> None:
|
||||
"""Init Tuya Scene."""
|
||||
super().__init__()
|
||||
self.home_manager = home_manager
|
||||
self.scene = scene
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Hass should not poll."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str | None:
|
||||
"""Return a unique ID."""
|
||||
return f"tys{self.scene.scene_id}"
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
"""Return Tuya scene name."""
|
||||
return self.scene.name
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, f"{self.unique_id}")},
|
||||
"manufacturer": "tuya",
|
||||
"name": self.scene.name,
|
||||
"model": "Tuya Scene",
|
||||
}
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if the scene is enabled."""
|
||||
return self.scene.enabled
|
||||
|
||||
def activate(self, **kwargs: Any) -> None:
|
||||
"""Activate the scene."""
|
||||
self._tuya.activate()
|
||||
self.home_manager.trigger_scene(self.scene.home_id, self.scene.scene_id)
|
||||
|
@ -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.
|
@ -1,64 +1,29 @@
|
||||
{
|
||||
"config": {
|
||||
"flow_title": "Tuya configuration",
|
||||
"step": {
|
||||
"user": {
|
||||
"user":{
|
||||
"title":"Tuya Integration",
|
||||
"data":{
|
||||
"tuya_project_type": "Tuya cloud project type"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"title": "Tuya",
|
||||
"description": "Enter your Tuya credentials.",
|
||||
"description": "Enter your Tuya credential",
|
||||
"data": {
|
||||
"country_code": "Your account country code (e.g., 1 for USA or 86 for China)",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"platform": "The app where your account is registered",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
"endpoint": "Availability Zone",
|
||||
"access_id": "Access ID",
|
||||
"access_secret": "Access Secret",
|
||||
"tuya_app_type": "Mobile App",
|
||||
"country_code": "Country Code",
|
||||
"username": "Account",
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"abort": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Configure Tuya Options",
|
||||
"description": "Do not set pollings interval values too low or the calls will fail generating error message in the log",
|
||||
"data": {
|
||||
"discovery_interval": "Discovery device polling interval in seconds",
|
||||
"query_device": "Select device that will use query method for faster status update",
|
||||
"query_interval": "Query device polling interval in seconds",
|
||||
"list_devices": "Select the devices to configure or leave empty to save configuration"
|
||||
}
|
||||
},
|
||||
"device": {
|
||||
"title": "Configure Tuya Device",
|
||||
"description": "Configure options to adjust displayed information for {device_type} device `{device_name}`",
|
||||
"data": {
|
||||
"support_color": "Force color support",
|
||||
"brightness_range_mode": "Brightness range used by device",
|
||||
"min_kelvin": "Min color temperature supported in kelvin",
|
||||
"max_kelvin": "Max color temperature supported in kelvin",
|
||||
"tuya_max_coltemp": "Max color temperature reported by device",
|
||||
"unit_of_measurement": "Temperature unit used by device",
|
||||
"temp_divider": "Temperature values divider (0 = use default)",
|
||||
"curr_temp_divider": "Current Temperature value divider (0 = use default)",
|
||||
"set_temp_divided": "Use divided Temperature value for set temperature command",
|
||||
"temp_step_override": "Target Temperature step",
|
||||
"min_temp": "Min target temperature (use min and max = 0 for default)",
|
||||
"max_temp": "Max target temperature (use min and max = 0 for default)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"dev_multi_type": "Multiple selected devices to configure must be of the same type",
|
||||
"dev_not_config": "Device type not configurable",
|
||||
"dev_not_found": "Device not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Support for Tuya switches."""
|
||||
from datetime import timedelta
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
SwitchEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from tuya_iot import TuyaDevice, TuyaDeviceManager
|
||||
|
||||
from homeassistant.components.switch import DOMAIN as DEVICE_DOMAIN, SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TuyaDevice
|
||||
from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW
|
||||
from .base import TuyaHaEntity
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
TUYA_DEVICE_MANAGER,
|
||||
TUYA_DISCOVERY_NEW,
|
||||
TUYA_HA_DEVICES,
|
||||
TUYA_HA_TUYA_MAP,
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TUYA_SUPPORT_TYPE = {
|
||||
"kg", # Switch
|
||||
"cz", # Socket
|
||||
"pc", # Power Strip
|
||||
"bh", # Smart Kettle
|
||||
"dlq", # Breaker
|
||||
"cwysj", # Pet Water Feeder
|
||||
"kj", # Air Purifier
|
||||
"xxj", # Diffuser
|
||||
}
|
||||
|
||||
# Switch(kg), Socket(cz), Power Strip(pc)
|
||||
# https://developer.tuya.com/en/docs/iot/categorykgczpc?id=Kaiuz08zj1l4y
|
||||
DPCODE_SWITCH = "switch"
|
||||
|
||||
# Air Purifier
|
||||
# https://developer.tuya.com/en/docs/iot/categorykj?id=Kaiuz1atqo5l7
|
||||
# Pet Water Feeder
|
||||
# https://developer.tuya.com/en/docs/iot/f?id=K9gf46aewxem5
|
||||
DPCODE_ANION = "anion" # Air Purifier - Ionizer unit
|
||||
# Air Purifier - Filter cartridge resetting; Pet Water Feeder - Filter cartridge resetting
|
||||
DPCODE_FRESET = "filter_reset"
|
||||
DPCODE_LIGHT = "light" # Air Purifier - Light
|
||||
DPCODE_LOCK = "lock" # Air Purifier - Child lock
|
||||
# Air Purifier - UV sterilization; Pet Water Feeder - UV sterilization
|
||||
DPCODE_UV = "uv"
|
||||
DPCODE_WET = "wet" # Air Purifier - Humidification unit
|
||||
DPCODE_PRESET = "pump_reset" # Pet Water Feeder - Water pump resetting
|
||||
DPCODE_WRESET = "water_reset" # Pet Water Feeder - Resetting of water usage days
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
DPCODE_START = "start"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up tuya sensors dynamically through tuya discovery."""
|
||||
_LOGGER.debug("switch init")
|
||||
|
||||
platform = config_entry.data[CONF_PLATFORM]
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
|
||||
DEVICE_DOMAIN
|
||||
] = TUYA_SUPPORT_TYPE
|
||||
|
||||
async def async_discover_sensor(dev_ids):
|
||||
async def async_discover_device(dev_ids):
|
||||
"""Discover and add a discovered tuya sensor."""
|
||||
_LOGGER.debug("switch add-> %s", dev_ids)
|
||||
if not dev_ids:
|
||||
return
|
||||
entities = await hass.async_add_executor_job(
|
||||
_setup_entities,
|
||||
hass,
|
||||
dev_ids,
|
||||
platform,
|
||||
)
|
||||
entities = _setup_entities(hass, entry, dev_ids)
|
||||
async_add_entities(entities)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(SENSOR_DOMAIN), async_discover_sensor
|
||||
entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
|
||||
)
|
||||
)
|
||||
|
||||
devices_ids = hass.data[DOMAIN]["pending"].pop(SENSOR_DOMAIN)
|
||||
await async_discover_sensor(devices_ids)
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
device_ids = []
|
||||
for (device_id, device) in device_manager.device_map.items():
|
||||
if device.category in TUYA_SUPPORT_TYPE:
|
||||
device_ids.append(device_id)
|
||||
await async_discover_device(device_ids)
|
||||
|
||||
|
||||
def _setup_entities(hass, dev_ids, platform):
|
||||
def _setup_entities(hass, entry: ConfigEntry, device_ids: list[str]) -> list[Entity]:
|
||||
"""Set up Tuya Switch device."""
|
||||
tuya = hass.data[DOMAIN][TUYA_DATA]
|
||||
entities = []
|
||||
for dev_id in dev_ids:
|
||||
device = tuya.get_device_by_id(dev_id)
|
||||
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
||||
entities: list[Entity] = []
|
||||
for device_id in device_ids:
|
||||
device = device_manager.device_map[device_id]
|
||||
if device is None:
|
||||
continue
|
||||
entities.append(TuyaSwitch(device, platform))
|
||||
|
||||
for function in device.function:
|
||||
tuya_ha_switch = None
|
||||
if device.category == "kj":
|
||||
if function in [
|
||||
DPCODE_ANION,
|
||||
DPCODE_FRESET,
|
||||
DPCODE_LIGHT,
|
||||
DPCODE_LOCK,
|
||||
DPCODE_UV,
|
||||
DPCODE_WET,
|
||||
]:
|
||||
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
|
||||
# Main device switch is handled by the Fan object
|
||||
elif device.category == "cwysj":
|
||||
if function in [DPCODE_FRESET, DPCODE_UV, DPCODE_PRESET, DPCODE_WRESET]:
|
||||
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
|
||||
|
||||
if function.startswith(DPCODE_SWITCH):
|
||||
# Main device switch
|
||||
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
|
||||
else:
|
||||
if function.startswith(DPCODE_START):
|
||||
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
|
||||
if function.startswith(DPCODE_SWITCH):
|
||||
tuya_ha_switch = TuyaHaSwitch(device, device_manager, function)
|
||||
|
||||
if tuya_ha_switch is not None:
|
||||
entities.append(tuya_ha_switch)
|
||||
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(
|
||||
tuya_ha_switch.tuya_device.id
|
||||
)
|
||||
return entities
|
||||
|
||||
|
||||
class TuyaSwitch(TuyaDevice, SwitchEntity):
|
||||
class TuyaHaSwitch(TuyaHaEntity, SwitchEntity):
|
||||
"""Tuya Switch Device."""
|
||||
|
||||
def __init__(self, tuya, platform):
|
||||
"""Init Tuya switch device."""
|
||||
super().__init__(tuya, platform)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
dp_code_switch = DPCODE_SWITCH
|
||||
dp_code_start = DPCODE_START
|
||||
|
||||
def __init__(
|
||||
self, device: TuyaDevice, device_manager: TuyaDeviceManager, dp_code: str = ""
|
||||
) -> None:
|
||||
"""Init TuyaHaSwitch."""
|
||||
super().__init__(device, device_manager)
|
||||
|
||||
self.dp_code = dp_code
|
||||
self.channel = (
|
||||
dp_code.replace(DPCODE_SWITCH, "")
|
||||
if dp_code.startswith(DPCODE_SWITCH)
|
||||
else dp_code
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def unique_id(self) -> str | None:
|
||||
"""Return a unique ID."""
|
||||
return f"{super().unique_id}{self.channel}"
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
"""Return Tuya device name."""
|
||||
return f"{self.tuya_device.name}{self.channel}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if switch is on."""
|
||||
return self._tuya.state()
|
||||
return self.tuya_device.status.get(self.dp_code, False)
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
self._tuya.turn_on()
|
||||
self._send_command([{"code": self.dp_code, "value": True}])
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
self._tuya.turn_off()
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
self._send_command([{"code": self.dp_code, "value": False}])
|
||||
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"options": {
|
||||
"error": {
|
||||
"dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar",
|
||||
"dev_not_found": "Ger\u00e4t nicht gefunden"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +1,29 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Invalid authentication"
|
||||
},
|
||||
"flow_title": "Tuya configuration",
|
||||
"step": {
|
||||
"user": {
|
||||
"user":{
|
||||
"title":"Tuya Integration",
|
||||
"data":{
|
||||
"tuya_project_type": "Tuya cloud project type"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"data": {
|
||||
"country_code": "Your account country code (e.g., 1 for USA or 86 for China)",
|
||||
"password": "Password",
|
||||
"platform": "The app where your account is registered",
|
||||
"username": "Username"
|
||||
"endpoint": "Availability Zone",
|
||||
"access_id": "Access ID",
|
||||
"access_secret": "Access Secret",
|
||||
"tuya_app_type": "Mobile App",
|
||||
"country_code": "Country Code",
|
||||
"username": "Account",
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "Enter your Tuya credentials.",
|
||||
"description": "Enter your Tuya credential.",
|
||||
"title": "Tuya"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"abort": {
|
||||
"cannot_connect": "Failed to connect"
|
||||
},
|
||||
"error": {
|
||||
"dev_multi_type": "Multiple selected devices to configure must be of the same type",
|
||||
"dev_not_config": "Device type not configurable",
|
||||
"dev_not_found": "Device not found"
|
||||
},
|
||||
"step": {
|
||||
"device": {
|
||||
"data": {
|
||||
"brightness_range_mode": "Brightness range used by device",
|
||||
"curr_temp_divider": "Current Temperature value divider (0 = use default)",
|
||||
"max_kelvin": "Max color temperature supported in kelvin",
|
||||
"max_temp": "Max target temperature (use min and max = 0 for default)",
|
||||
"min_kelvin": "Min color temperature supported in kelvin",
|
||||
"min_temp": "Min target temperature (use min and max = 0 for default)",
|
||||
"set_temp_divided": "Use divided Temperature value for set temperature command",
|
||||
"support_color": "Force color support",
|
||||
"temp_divider": "Temperature values divider (0 = use default)",
|
||||
"temp_step_override": "Target Temperature step",
|
||||
"tuya_max_coltemp": "Max color temperature reported by device",
|
||||
"unit_of_measurement": "Temperature unit used by device"
|
||||
},
|
||||
"description": "Configure options to adjust displayed information for {device_type} device `{device_name}`",
|
||||
"title": "Configure Tuya Device"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
"discovery_interval": "Discovery device polling interval in seconds",
|
||||
"list_devices": "Select the devices to configure or leave empty to save configuration",
|
||||
"query_device": "Select device that will use query method for faster status update",
|
||||
"query_interval": "Query device polling interval in seconds"
|
||||
},
|
||||
"description": "Do not set pollings interval values too low or the calls will fail generating error message in the log",
|
||||
"title": "Configure Tuya Options"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +1,29 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"cannot_connect": "\u8fde\u63a5\u5931\u8d25",
|
||||
"invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548",
|
||||
"single_instance_allowed": "\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86\uff0c\u4e14\u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002"
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548"
|
||||
"invalid_auth": "身份认证无效"
|
||||
},
|
||||
"flow_title": "\u6d82\u9e26\u914d\u7f6e",
|
||||
"flow_title": "涂鸦配置",
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "\u60a8\u7684\u5e10\u6237\u56fd\u5bb6(\u5730\u533a)\u4ee3\u7801\uff08\u4f8b\u5982\u4e2d\u56fd\u4e3a 86\uff0c\u7f8e\u56fd\u4e3a 1\uff09",
|
||||
"password": "\u5bc6\u7801",
|
||||
"platform": "\u60a8\u6ce8\u518c\u5e10\u6237\u7684\u5e94\u7528",
|
||||
"username": "\u7528\u6237\u540d"
|
||||
},
|
||||
"description": "\u8bf7\u8f93\u5165\u6d82\u9e26\u8d26\u6237\u4fe1\u606f\u3002",
|
||||
"title": "\u6d82\u9e26"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"abort": {
|
||||
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
|
||||
},
|
||||
"error": {
|
||||
"dev_multi_type": "\u591a\u4e2a\u8981\u914d\u7f6e\u7684\u8bbe\u5907\u5fc5\u987b\u5177\u6709\u76f8\u540c\u7684\u7c7b\u578b",
|
||||
"dev_not_config": "\u8bbe\u5907\u7c7b\u578b\u4e0d\u53ef\u914d\u7f6e",
|
||||
"dev_not_found": "\u672a\u627e\u5230\u8bbe\u5907"
|
||||
},
|
||||
"step": {
|
||||
"device": {
|
||||
"data": {
|
||||
"brightness_range_mode": "\u8bbe\u5907\u4f7f\u7528\u7684\u4eae\u5ea6\u8303\u56f4",
|
||||
"max_kelvin": "\u6700\u9ad8\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09",
|
||||
"max_temp": "\u6700\u9ad8\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09",
|
||||
"min_kelvin": "\u6700\u4f4e\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09",
|
||||
"min_temp": "\u6700\u4f4e\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09",
|
||||
"support_color": "\u5f3a\u5236\u652f\u6301\u8c03\u8272",
|
||||
"tuya_max_coltemp": "\u8bbe\u5907\u62a5\u544a\u7684\u6700\u9ad8\u8272\u6e29",
|
||||
"unit_of_measurement": "\u8bbe\u5907\u4f7f\u7528\u7684\u6e29\u5ea6\u5355\u4f4d"
|
||||
},
|
||||
"title": "\u914d\u7f6e\u6d82\u9e26\u8bbe\u5907"
|
||||
"user":{
|
||||
"title":"Tuya插件",
|
||||
"data":{
|
||||
"tuya_project_type": "涂鸦云项目类型"
|
||||
}
|
||||
},
|
||||
"init": {
|
||||
"login": {
|
||||
"data": {
|
||||
"discovery_interval": "\u53d1\u73b0\u8bbe\u5907\u8f6e\u8be2\u95f4\u9694\uff08\u4ee5\u79d2\u4e3a\u5355\u4f4d\uff09",
|
||||
"list_devices": "\u8bf7\u9009\u62e9\u8981\u914d\u7f6e\u7684\u8bbe\u5907\uff0c\u6216\u7559\u7a7a\u4ee5\u4fdd\u5b58\u914d\u7f6e",
|
||||
"query_device": "\u8bf7\u9009\u62e9\u4f7f\u7528\u67e5\u8be2\u65b9\u6cd5\u7684\u8bbe\u5907\uff0c\u4ee5\u4fbf\u66f4\u5feb\u5730\u66f4\u65b0\u72b6\u6001",
|
||||
"query_interval": "\u67e5\u8be2\u8bbe\u5907\u8f6e\u8be2\u95f4\u9694\uff08\u4ee5\u79d2\u4e3a\u5355\u4f4d\uff09"
|
||||
"endpoint": "可用区域",
|
||||
"access_id": "Access ID",
|
||||
"access_secret": "Access Secret",
|
||||
"tuya_app_type": "移动应用",
|
||||
"country_code": "国家码",
|
||||
"username": "账号",
|
||||
"password": "密码"
|
||||
},
|
||||
"description": "\u8bf7\u4e0d\u8981\u5c06\u8f6e\u8be2\u95f4\u9694\u8bbe\u7f6e\u5f97\u592a\u4f4e\uff0c\u5426\u5219\u5c06\u8c03\u7528\u5931\u8d25\u5e76\u5728\u65e5\u5fd7\u751f\u6210\u9519\u8bef\u6d88\u606f",
|
||||
"title": "\u914d\u7f6e\u6d82\u9e26\u9009\u9879"
|
||||
"description": "请输入涂鸦账户信息。",
|
||||
"title": "涂鸦"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -359,30 +359,6 @@ DHCP = [
|
||||
"hostname": "lb*",
|
||||
"macaddress": "B09575*"
|
||||
},
|
||||
{
|
||||
"domain": "tuya",
|
||||
"macaddress": "508A06*"
|
||||
},
|
||||
{
|
||||
"domain": "tuya",
|
||||
"macaddress": "7CF666*"
|
||||
},
|
||||
{
|
||||
"domain": "tuya",
|
||||
"macaddress": "10D561*"
|
||||
},
|
||||
{
|
||||
"domain": "tuya",
|
||||
"macaddress": "D4A651*"
|
||||
},
|
||||
{
|
||||
"domain": "tuya",
|
||||
"macaddress": "68572D*"
|
||||
},
|
||||
{
|
||||
"domain": "tuya",
|
||||
"macaddress": "1869D8*"
|
||||
},
|
||||
{
|
||||
"domain": "verisure",
|
||||
"macaddress": "0023C1*"
|
||||
|
@ -2332,7 +2332,7 @@ tp-connected==0.0.4
|
||||
transmissionrpc==0.11
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuyaha==0.0.10
|
||||
tuya-iot-py-sdk==0.4.1
|
||||
|
||||
# homeassistant.components.twentemilieu
|
||||
twentemilieu==0.3.0
|
||||
|
@ -1315,7 +1315,7 @@ total_connect_client==0.57
|
||||
transmissionrpc==0.11
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuyaha==0.0.10
|
||||
tuya-iot-py-sdk==0.4.1
|
||||
|
||||
# homeassistant.components.twentemilieu
|
||||
twentemilieu==0.3.0
|
||||
|
@ -1,68 +1,81 @@
|
||||
"""Tests for the Tuya config flow."""
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from tuyaha.devices.climate import STEP_HALVES
|
||||
from tuyaha.tuyaapi import TuyaAPIException, TuyaNetException
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components.tuya.config_flow import (
|
||||
CONF_LIST_DEVICES,
|
||||
ERROR_DEV_MULTI_TYPE,
|
||||
ERROR_DEV_NOT_CONFIG,
|
||||
ERROR_DEV_NOT_FOUND,
|
||||
RESULT_AUTH_FAILED,
|
||||
RESULT_CONN_ERROR,
|
||||
RESULT_SINGLE_INSTANCE,
|
||||
)
|
||||
from homeassistant.components.tuya.config_flow import RESULT_AUTH_FAILED
|
||||
from homeassistant.components.tuya.const import (
|
||||
CONF_BRIGHTNESS_RANGE_MODE,
|
||||
CONF_COUNTRYCODE,
|
||||
CONF_CURR_TEMP_DIVIDER,
|
||||
CONF_DISCOVERY_INTERVAL,
|
||||
CONF_MAX_KELVIN,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_KELVIN,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_QUERY_DEVICE,
|
||||
CONF_QUERY_INTERVAL,
|
||||
CONF_SET_TEMP_DIVIDED,
|
||||
CONF_SUPPORT_COLOR,
|
||||
CONF_TEMP_DIVIDER,
|
||||
CONF_TEMP_STEP_OVERRIDE,
|
||||
CONF_TUYA_MAX_COLTEMP,
|
||||
DOMAIN,
|
||||
TUYA_DATA,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_ACCESS_ID,
|
||||
CONF_ACCESS_SECRET,
|
||||
CONF_APP_TYPE,
|
||||
CONF_COUNTRY_CODE,
|
||||
CONF_ENDPOINT,
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_PROJECT_TYPE,
|
||||
CONF_USERNAME,
|
||||
TEMP_CELSIUS,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
from .common import CLIMATE_ID, LIGHT_ID, LIGHT_ID_FAKE1, LIGHT_ID_FAKE2, MockTuya
|
||||
MOCK_SMART_HOME_PROJECT_TYPE = 0
|
||||
MOCK_INDUSTRY_PROJECT_TYPE = 1
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
MOCK_ACCESS_ID = "myAccessId"
|
||||
MOCK_ACCESS_SECRET = "myAccessSecret"
|
||||
MOCK_USERNAME = "myUsername"
|
||||
MOCK_PASSWORD = "myPassword"
|
||||
MOCK_COUNTRY_CODE = "1"
|
||||
MOCK_APP_TYPE = "smartlife"
|
||||
MOCK_ENDPOINT = "https://openapi-ueaz.tuyaus.com"
|
||||
|
||||
USERNAME = "myUsername"
|
||||
PASSWORD = "myPassword"
|
||||
COUNTRY_CODE = "1"
|
||||
TUYA_PLATFORM = "tuya"
|
||||
TUYA_SMART_HOME_PROJECT_DATA = {
|
||||
CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE,
|
||||
}
|
||||
TUYA_INDUSTRY_PROJECT_DATA = {
|
||||
CONF_PROJECT_TYPE: MOCK_INDUSTRY_PROJECT_TYPE,
|
||||
}
|
||||
|
||||
TUYA_USER_DATA = {
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
CONF_COUNTRYCODE: COUNTRY_CODE,
|
||||
CONF_PLATFORM: TUYA_PLATFORM,
|
||||
TUYA_INPUT_SMART_HOME_DATA = {
|
||||
CONF_ACCESS_ID: MOCK_ACCESS_ID,
|
||||
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
|
||||
CONF_USERNAME: MOCK_USERNAME,
|
||||
CONF_PASSWORD: MOCK_PASSWORD,
|
||||
CONF_COUNTRY_CODE: MOCK_COUNTRY_CODE,
|
||||
CONF_APP_TYPE: MOCK_APP_TYPE,
|
||||
}
|
||||
|
||||
TUYA_INPUT_INDUSTRY_DATA = {
|
||||
CONF_ENDPOINT: MOCK_ENDPOINT,
|
||||
CONF_ACCESS_ID: MOCK_ACCESS_ID,
|
||||
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
|
||||
CONF_USERNAME: MOCK_USERNAME,
|
||||
CONF_PASSWORD: MOCK_PASSWORD,
|
||||
}
|
||||
|
||||
TUYA_IMPORT_SMART_HOME_DATA = {
|
||||
CONF_ACCESS_ID: MOCK_ACCESS_ID,
|
||||
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
|
||||
CONF_USERNAME: MOCK_USERNAME,
|
||||
CONF_PASSWORD: MOCK_PASSWORD,
|
||||
CONF_COUNTRY_CODE: MOCK_COUNTRY_CODE,
|
||||
CONF_APP_TYPE: MOCK_APP_TYPE,
|
||||
}
|
||||
|
||||
|
||||
TUYA_IMPORT_INDUSTRY_DATA = {
|
||||
CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE,
|
||||
CONF_ENDPOINT: MOCK_ENDPOINT,
|
||||
CONF_ACCESS_ID: MOCK_ACCESS_ID,
|
||||
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
|
||||
CONF_USERNAME: MOCK_USERNAME,
|
||||
CONF_PASSWORD: MOCK_PASSWORD,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="tuya")
|
||||
def tuya_fixture() -> Mock:
|
||||
"""Patch libraries."""
|
||||
with patch("homeassistant.components.tuya.config_flow.TuyaApi") as tuya:
|
||||
with patch("homeassistant.components.tuya.config_flow.TuyaOpenAPI") as tuya:
|
||||
yield tuya
|
||||
|
||||
|
||||
@ -73,8 +86,8 @@ def tuya_setup_fixture():
|
||||
yield
|
||||
|
||||
|
||||
async def test_user(hass, tuya):
|
||||
"""Test user config."""
|
||||
async def test_industry_user(hass, tuya):
|
||||
"""Test industry user config."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
@ -83,192 +96,92 @@ async def test_user(hass, tuya):
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=TUYA_USER_DATA
|
||||
result["flow_id"], user_input=TUYA_INDUSTRY_PROJECT_DATA
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "login"
|
||||
|
||||
tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024})
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=TUYA_INPUT_INDUSTRY_DATA
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == USERNAME
|
||||
assert result["data"][CONF_USERNAME] == USERNAME
|
||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||
assert result["data"][CONF_COUNTRYCODE] == COUNTRY_CODE
|
||||
assert result["data"][CONF_PLATFORM] == TUYA_PLATFORM
|
||||
assert result["title"] == MOCK_USERNAME
|
||||
assert result["data"][CONF_ACCESS_ID] == MOCK_ACCESS_ID
|
||||
assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET
|
||||
assert result["data"][CONF_USERNAME] == MOCK_USERNAME
|
||||
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
|
||||
assert not result["result"].unique_id
|
||||
|
||||
|
||||
async def test_abort_if_already_setup(hass, tuya):
|
||||
"""Test we abort if Tuya is already setup."""
|
||||
MockConfigEntry(domain=DOMAIN, data=TUYA_USER_DATA).add_to_hass(hass)
|
||||
|
||||
# Should fail, config exist (import)
|
||||
async def test_smart_home_user(hass, tuya):
|
||||
"""Test smart home user config."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=TUYA_USER_DATA
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == RESULT_SINGLE_INSTANCE
|
||||
|
||||
|
||||
async def test_abort_on_invalid_credentials(hass, tuya):
|
||||
"""Test when we have invalid credentials."""
|
||||
tuya().init.side_effect = TuyaAPIException("Boom")
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=TUYA_USER_DATA
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": RESULT_AUTH_FAILED}
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_abort_on_connection_error(hass, tuya):
|
||||
"""Test when we have a network error."""
|
||||
tuya().init.side_effect = TuyaNetException("Boom")
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=TUYA_USER_DATA
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=TUYA_SMART_HOME_PROJECT_DATA
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == RESULT_CONN_ERROR
|
||||
|
||||
|
||||
async def test_options_flow(hass):
|
||||
"""Test config flow options."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=TUYA_USER_DATA,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Set up the integration to make sure the config flow module is loaded.
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Unload the integration to prepare for the test.
|
||||
with patch("homeassistant.components.tuya.async_unload_entry", return_value=True):
|
||||
assert await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "login"
|
||||
|
||||
# Test check for integration not loaded
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == RESULT_CONN_ERROR
|
||||
|
||||
# Load integration and enter options
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
tuya().login = MagicMock(return_value={"success": False, "errorCode": 1024})
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
hass.data[DOMAIN] = {TUYA_DATA: MockTuya()}
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
assert result["errors"]["base"] == RESULT_AUTH_FAILED
|
||||
|
||||
# Test dev not found error
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID_FAKE1}"]},
|
||||
tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024})
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
assert result["errors"] == {"base": ERROR_DEV_NOT_FOUND}
|
||||
|
||||
# Test dev type error
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID_FAKE2}"]},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
assert result["errors"] == {"base": ERROR_DEV_NOT_CONFIG}
|
||||
|
||||
# Test multi dev error
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_LIST_DEVICES: [f"climate-{CLIMATE_ID}", f"light-{LIGHT_ID}"]},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
assert result["errors"] == {"base": ERROR_DEV_MULTI_TYPE}
|
||||
|
||||
# Test climate options form
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={CONF_LIST_DEVICES: [f"climate-{CLIMATE_ID}"]}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "device"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
|
||||
CONF_TEMP_DIVIDER: 10,
|
||||
CONF_CURR_TEMP_DIVIDER: 5,
|
||||
CONF_SET_TEMP_DIVIDED: False,
|
||||
CONF_TEMP_STEP_OVERRIDE: STEP_HALVES,
|
||||
CONF_MIN_TEMP: 12,
|
||||
CONF_MAX_TEMP: 22,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
# Test light options form
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID}"]}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "device"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_SUPPORT_COLOR: True,
|
||||
CONF_BRIGHTNESS_RANGE_MODE: 1,
|
||||
CONF_MIN_KELVIN: 4000,
|
||||
CONF_MAX_KELVIN: 5000,
|
||||
CONF_TUYA_MAX_COLTEMP: 12000,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
# Test common options
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_DISCOVERY_INTERVAL: 100,
|
||||
CONF_QUERY_INTERVAL: 50,
|
||||
CONF_QUERY_DEVICE: LIGHT_ID,
|
||||
},
|
||||
)
|
||||
|
||||
# Verify results
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == MOCK_USERNAME
|
||||
assert result["data"][CONF_ACCESS_ID] == MOCK_ACCESS_ID
|
||||
assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET
|
||||
assert result["data"][CONF_USERNAME] == MOCK_USERNAME
|
||||
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
|
||||
assert result["data"][CONF_COUNTRY_CODE] == MOCK_COUNTRY_CODE
|
||||
assert result["data"][CONF_APP_TYPE] == MOCK_APP_TYPE
|
||||
assert not result["result"].unique_id
|
||||
|
||||
climate_options = config_entry.options[CLIMATE_ID]
|
||||
assert climate_options[CONF_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS
|
||||
assert climate_options[CONF_TEMP_DIVIDER] == 10
|
||||
assert climate_options[CONF_CURR_TEMP_DIVIDER] == 5
|
||||
assert climate_options[CONF_SET_TEMP_DIVIDED] is False
|
||||
assert climate_options[CONF_TEMP_STEP_OVERRIDE] == STEP_HALVES
|
||||
assert climate_options[CONF_MIN_TEMP] == 12
|
||||
assert climate_options[CONF_MAX_TEMP] == 22
|
||||
|
||||
light_options = config_entry.options[LIGHT_ID]
|
||||
assert light_options[CONF_SUPPORT_COLOR] is True
|
||||
assert light_options[CONF_BRIGHTNESS_RANGE_MODE] == 1
|
||||
assert light_options[CONF_MIN_KELVIN] == 4000
|
||||
assert light_options[CONF_MAX_KELVIN] == 5000
|
||||
assert light_options[CONF_TUYA_MAX_COLTEMP] == 12000
|
||||
async def test_error_on_invalid_credentials(hass, tuya):
|
||||
"""Test when we have invalid credentials."""
|
||||
|
||||
assert config_entry.options[CONF_DISCOVERY_INTERVAL] == 100
|
||||
assert config_entry.options[CONF_QUERY_INTERVAL] == 50
|
||||
assert config_entry.options[CONF_QUERY_DEVICE] == LIGHT_ID
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=TUYA_INDUSTRY_PROJECT_DATA
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "login"
|
||||
|
||||
tuya().login = MagicMock(return_value={"success": False, "errorCode": 1024})
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=TUYA_INPUT_INDUSTRY_DATA
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["errors"]["base"] == RESULT_AUTH_FAILED
|
||||
|
Loading…
x
Reference in New Issue
Block a user