core/homeassistant/components/tuya/switch.py

541 lines
16 KiB
Python

"""Support for Tuya switches."""
from __future__ import annotations
from typing import Any
from tuya_iot import TuyaDevice, TuyaDeviceManager
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeAssistantTuyaData
from .base import TuyaEntity
from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode
# All descriptions can be found here. Mostly the Boolean data types in the
# default instruction set of each category end up being a Switch.
# https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq
SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = {
# Smart Kettle
# https://developer.tuya.com/en/docs/iot/fbh?id=K9gf484m21yq7
"bh": (
SwitchEntityDescription(
key=DPCode.START,
name="Start",
icon="mdi:kettle-steam",
),
SwitchEntityDescription(
key=DPCode.WARM,
name="Heat Preservation",
entity_category=EntityCategory.CONFIG,
),
),
# Smart Pet Feeder
# https://developer.tuya.com/en/docs/iot/categorycwwsq?id=Kaiuz2b6vydld
"cwwsq": (
SwitchEntityDescription(
key=DPCode.SLOW_FEED,
name="Slow Feed",
icon="mdi:speedometer-slow",
entity_category=EntityCategory.CONFIG,
),
),
# Pet Water Feeder
# https://developer.tuya.com/en/docs/iot/f?id=K9gf46aewxem5
"cwysj": (
SwitchEntityDescription(
key=DPCode.FILTER_RESET,
name="Filter reset",
icon="mdi:filter",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.PUMP_RESET,
name="Water pump reset",
icon="mdi:pump",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Power",
),
SwitchEntityDescription(
key=DPCode.WATER_RESET,
name="Reset of water usage days",
icon="mdi:water-sync",
entity_category=EntityCategory.CONFIG,
),
),
# Light
# https://developer.tuya.com/en/docs/iot/f?id=K9i5ql3v98hn3
"dj": (
# There are sockets available with an RGB light
# that advertise as `dj`, but provide an additional
# switch to control the plug.
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Plug",
),
),
# Cirquit Breaker
"dlq": (
SwitchEntityDescription(
key=DPCode.CHILD_LOCK,
name="Child Lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Switch",
),
),
# Switch
# https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
"kg": (
SwitchEntityDescription(
key=DPCode.CHILD_LOCK,
name="Child Lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.SWITCH_1,
name="Switch 1",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_2,
name="Switch 2",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_3,
name="Switch 3",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_4,
name="Switch 4",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_5,
name="Switch 5",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_6,
name="Switch 6",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB1,
name="USB 1",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB2,
name="USB 2",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB3,
name="USB 3",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB4,
name="USB 4",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB5,
name="USB 5",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB6,
name="USB 6",
),
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Switch",
device_class=SwitchDeviceClass.OUTLET,
),
),
# Air Purifier
# https://developer.tuya.com/en/docs/iot/f?id=K9gf46h2s6dzm
"kj": (
SwitchEntityDescription(
key=DPCode.ANION,
name="Ionizer",
icon="mdi:minus-circle-outline",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.FILTER_RESET,
name="Filter cartridge reset",
icon="mdi:filter",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.LOCK,
name="Child lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Power",
),
SwitchEntityDescription(
key=DPCode.WET,
name="Humidification",
icon="mdi:water-percent",
entity_category=EntityCategory.CONFIG,
),
),
# Air conditioner
# https://developer.tuya.com/en/docs/iot/categorykt?id=Kaiuz0z71ov2n
"kt": (
SwitchEntityDescription(
key=DPCode.ANION,
name="Ionizer",
icon="mdi:minus-circle-outline",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.LOCK,
name="Child Lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
),
),
# Power Socket
# https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
"pc": (
SwitchEntityDescription(
key=DPCode.CHILD_LOCK,
name="Child Lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.SWITCH_1,
name="Socket 1",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_2,
name="Socket 2",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_3,
name="Socket 3",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_4,
name="Socket 4",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_5,
name="Socket 5",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_6,
name="Socket 6",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB1,
name="USB 1",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB2,
name="USB 2",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB3,
name="USB 3",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB4,
name="USB 4",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB5,
name="USB 5",
),
SwitchEntityDescription(
key=DPCode.SWITCH_USB6,
name="USB 6",
),
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Socket",
device_class=SwitchDeviceClass.OUTLET,
),
),
# Heater
# https://developer.tuya.com/en/docs/iot/categoryqn?id=Kaiuz18kih0sm
"qn": (
SwitchEntityDescription(
key=DPCode.ANION,
name="Ionizer",
icon="mdi:minus-circle-outline",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.LOCK,
name="Child Lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
),
),
# Robot Vacuum
# https://developer.tuya.com/en/docs/iot/fsd?id=K9gf487ck1tlo
"sd": (
SwitchEntityDescription(
key=DPCode.SWITCH_DISTURB,
name="Do Not Disturb",
icon="mdi:minus-circle",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.VOICE_SWITCH,
name="Voice",
icon="mdi:account-voice",
entity_category=EntityCategory.CONFIG,
),
),
# Siren Alarm
# https://developer.tuya.com/en/docs/iot/categorysgbj?id=Kaiuz37tlpbnu
"sgbj": (
SwitchEntityDescription(
key=DPCode.MUFFLING,
name="Mute",
entity_category=EntityCategory.CONFIG,
),
),
# Smart Camera
# https://developer.tuya.com/en/docs/iot/categorysp?id=Kaiuz35leyo12
"sp": (
SwitchEntityDescription(
key=DPCode.WIRELESS_BATTERYLOCK,
name="Battery Lock",
icon="mdi:battery-lock",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.CRY_DETECTION_SWITCH,
icon="mdi:emoticon-cry",
name="Cry Detection",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.DECIBEL_SWITCH,
icon="mdi:microphone-outline",
name="Sound Detection",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.RECORD_SWITCH,
icon="mdi:record-rec",
name="Video Recording",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.MOTION_RECORD,
icon="mdi:record-rec",
name="Motion Recording",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.BASIC_PRIVATE,
icon="mdi:eye-off",
name="Privacy Mode",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.BASIC_FLIP,
icon="mdi:flip-horizontal",
name="Flip",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.BASIC_OSD,
icon="mdi:watermark",
name="Time Watermark",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.BASIC_WDR,
icon="mdi:watermark",
name="Wide Dynamic Range",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.MOTION_TRACKING,
icon="mdi:motion-sensor",
name="Motion Tracking",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.MOTION_SWITCH,
icon="mdi:motion-sensor",
name="Motion Alarm",
entity_category=EntityCategory.CONFIG,
),
),
# Fingerbot
"szjqr": (
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Switch",
icon="mdi:cursor-pointer",
),
),
# IoT Switch?
# Note: Undocumented
"tdq": (
SwitchEntityDescription(
key=DPCode.SWITCH_1,
name="Switch 1",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_2,
name="Switch 2",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.SWITCH_3,
name="Switch 3",
device_class=SwitchDeviceClass.OUTLET,
),
SwitchEntityDescription(
key=DPCode.CHILD_LOCK,
name="Child Lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
),
),
# Solar Light
# https://developer.tuya.com/en/docs/iot/tynd?id=Kaof8j02e1t98
"tyndj": (
SwitchEntityDescription(
key=DPCode.SWITCH_SAVE_ENERGY,
name="Energy Saving",
icon="mdi:leaf",
entity_category=EntityCategory.CONFIG,
),
),
# Ceiling Light
# https://developer.tuya.com/en/docs/iot/ceiling-light?id=Kaiuz03xxfc4r
"xdd": (
SwitchEntityDescription(
key=DPCode.DO_NOT_DISTURB,
name="Do not disturb",
icon="mdi:minus-circle-outline",
entity_category=EntityCategory.CONFIG,
),
),
# Diffuser
# https://developer.tuya.com/en/docs/iot/categoryxxj?id=Kaiuz1f9mo6bl
"xxj": (
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Power",
),
SwitchEntityDescription(
key=DPCode.SWITCH_SPRAY,
name="Spray",
icon="mdi:spray",
),
SwitchEntityDescription(
key=DPCode.SWITCH_VOICE,
name="Voice",
icon="mdi:account-voice",
entity_category=EntityCategory.CONFIG,
),
),
# Smart Electricity Meter
# https://developer.tuya.com/en/docs/iot/smart-meter?id=Kaiuz4gv6ack7
"zndb": (
SwitchEntityDescription(
key=DPCode.SWITCH,
name="Switch",
),
),
}
# Socket (duplicate of `pc`)
# https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
SWITCHES["cz"] = SWITCHES["pc"]
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up tuya sensors dynamically through tuya discovery."""
hass_data: HomeAssistantTuyaData = hass.data[DOMAIN][entry.entry_id]
@callback
def async_discover_device(device_ids: list[str]) -> None:
"""Discover and add a discovered tuya sensor."""
entities: list[TuyaSwitchEntity] = []
for device_id in device_ids:
device = hass_data.device_manager.device_map[device_id]
if descriptions := SWITCHES.get(device.category):
for description in descriptions:
if description.key in device.status:
entities.append(
TuyaSwitchEntity(
device, hass_data.device_manager, description
)
)
async_add_entities(entities)
async_discover_device([*hass_data.device_manager.device_map])
entry.async_on_unload(
async_dispatcher_connect(hass, TUYA_DISCOVERY_NEW, async_discover_device)
)
class TuyaSwitchEntity(TuyaEntity, SwitchEntity):
"""Tuya Switch Device."""
def __init__(
self,
device: TuyaDevice,
device_manager: TuyaDeviceManager,
description: SwitchEntityDescription,
) -> None:
"""Init TuyaHaSwitch."""
super().__init__(device, device_manager)
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
@property
def is_on(self) -> bool:
"""Return true if switch is on."""
return self.device.status.get(self.entity_description.key, False)
def turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
self._send_command([{"code": self.entity_description.key, "value": True}])
def turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
self._send_command([{"code": self.entity_description.key, "value": False}])