Open QuadGhoST opened 3 months ago
I have the exact same problem
I confirm, same problem
This custom quirk works for me
"""Tuya MCU based cover and blinds."""
from typing import Dict, Optional, Union
from zigpy.profiles import zha
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.closures import WindowCovering
from zigpy.zcl.clusters.general import (
Basic,
GreenPowerProxy,
Groups,
Ota,
Scenes,
Time,
)
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import (
NoManufacturerCluster,
TUYA_MCU_COMMAND,
TuyaLocalCluster,
TuyaWindowCover,
TuyaManufacturerWindowCover,
)
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaClusterData,
TuyaMCUCluster,
)
# Maps OPEN/CLOSE/STOP cover commands from Tuya to Zigbee
# https://github.com/zigpy/zigpy/blob/master/zigpy/zcl/clusters/closures.py#L558
# https://developer.tuya.com/en/docs/iot-device-dev/zigbee-curtain-switch-access-standard?id=K9ik6zvra3twv#title-7-DP1%20and%20DP4%20Curtain%20switch%201%20and%202
class TuyaCC(t.enum8):
"""Tuya cover commands."""
OPEN = 0x00
STOP = 0x01
CLOSE = 0x02
class ZclCC(t.enum8):
"""ZCL cover commands."""
OPEN = 0x00
CLOSE = 0x01
STOP = 0x02
TUYA2ZB_COMMANDS = {
ZclCC.OPEN: TuyaCC.OPEN,
ZclCC.CLOSE: TuyaCC.CLOSE,
ZclCC.STOP: TuyaCC.STOP,
}
class TuyaWindowCovering(NoManufacturerCluster, WindowCovering, TuyaLocalCluster):
"""Tuya MCU WindowCovering cluster."""
"""Add additional attributes for direction"""
attributes = WindowCovering.attributes.copy()
attributes.update(
{
0xF000: ("curtain_switch", t.enum8, True), # 0: open, 1: stop, 2: close
0xF001: ("accurate_calibration", t.enum8, True), # 0: calibration started, 1: calibration finished
0xF002: ("motor_steering", t.enum8, True), # 0: default, 1: reverse
0xF003: ("travel", t.uint16_t, True), # 30 to 9000 (units of 0.1 seconds)
}
)
async def command(
self,
command_id: Union[foundation.GeneralCommand, int, t.uint8_t],
*args,
manufacturer: Optional[Union[int, t.uint16_t]] = None,
expect_reply: bool = True,
tsn: Optional[Union[int, t.uint8_t]] = None,
):
"""Override the default Cluster command."""
# if manufacturer is None:
# manufacturer = self.endpoint.device.manufacturer
self.debug(
"Sending Tuya Cluster Command. Cluster Command is %x, Arguments are %s",
command_id,
args,
)
# (upopen, downclose, stop)
if command_id in (0x0002, 0x0000, 0x0001): # ?0x0003: continue?
cluster_data = TuyaClusterData(
endpoint_id=self.endpoint.endpoint_id,
cluster_name=self.ep_attribute,
cluster_attr="curtain_switch",
attr_value=TUYA2ZB_COMMANDS[command_id], # convert tuya2zigbee command
expect_reply=expect_reply,
manufacturer=-1,
)
self.endpoint.device.command_bus.listener_event(
TUYA_MCU_COMMAND,
cluster_data,
)
return foundation.GENERAL_COMMANDS[
foundation.GeneralCommand.Default_Response
].schema(command_id=command_id, status=foundation.Status.SUCCESS)
# (go_to_lift_percentage)
elif command_id == 0x0005:
lift_value = args[0]
cluster_data = TuyaClusterData(
endpoint_id=self.endpoint.endpoint_id,
cluster_name=self.ep_attribute,
cluster_attr="current_position_lift_percentage",
attr_value=lift_value,
expect_reply=expect_reply,
manufacturer=-1,
)
self.endpoint.device.command_bus.listener_event(
TUYA_MCU_COMMAND,
cluster_data,
)
return foundation.GENERAL_COMMANDS[
foundation.GeneralCommand.Default_Response
].schema(command_id=command_id, status=foundation.Status.SUCCESS)
self.warning("Unsupported command_id: %s", command_id)
return foundation.GENERAL_COMMANDS[
foundation.GeneralCommand.Default_Response
].schema(command_id=command_id, status=foundation.Status.UNSUP_CLUSTER_COMMAND)
class TuyaWindowCoverManufCluster(TuyaMCUCluster):
"""Tuya with WindowCover data points."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
0x5000: ("backlight_mode", t.enum8, True), # 0: off, 1: on
0x8001: ("indicator_status", t.enum8, True), # 0: status, 1: position, 2: off (?backlight_mode?)
}
)
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaWindowCovering.ep_attribute,
"curtain_switch",
),
2: DPToAttributeMapping(
TuyaWindowCovering.ep_attribute,
"current_position_lift_percentage", # for Slider movement
# dp_type=TuyaDPType.VALUE,
),
3: DPToAttributeMapping(
TuyaWindowCovering.ep_attribute,
"current_position_lift_percentage", # for Slider updates
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
3: "_dp_2_attr_update",
}
class TuyaCover0601_GP(TuyaWindowCover):
"""Tuya blind controller device."""
signature = {
# "NodeDescriptor(
# logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0,
# reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>,
# mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>,
# manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752,
# maximum_outgoing_transfer_size=66, descriptor_capability_field=<DescriptorCapability.NONE: 0>,
# *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False,
# *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False
# )
MODELS_INFO: [
("_TZE204_xu4a5rhj", "TS0601"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaWindowCoverManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
},
}
replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaWindowCoverManufCluster,
TuyaWindowCovering,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}
Problem description
Request to add a quirk for the "DS82-Tuya" curtain motor (cover). This is a zigbee device that looks exactly as other tuya curtain motors.
Solution description
A custom quirk that works and allows to control the curtain motor.
Screenshots/Video
No response
Device signature
```json { "node_descriptor": "NodeDescriptor(logical_type=Diagnostic information
```json { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2024.5.5", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.12.2", "docker": true, "arch": "x86_64", "timezone": "Asia/Irkutsk", "os_name": "Linux", "os_version": "6.6.29-haos", "supervisor": "2024.05.1", "host_os": "Home Assistant OS 12.3", "docker_version": "25.0.5", "chassis": "vm", "run_as_root": true }, "custom_components": { "hacs": { "documentation": "https://hacs.xyz/docs/configuration/start", "version": "1.34.0", "requirements": [ "aiogithubapi>=22.10.1" ] }, "localtuya": { "documentation": "https://github.com/rospogrigio/localtuya/", "version": "5.2.1", "requirements": [] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "onboarding", "usb" ], "codeowners": [ "@dmulcahey", "@adminiuga", "@puddly", "@TheJulianJES" ], "config_flow": true, "dependencies": [ "file_upload" ], "documentation": "https://www.home-assistant.io/integrations/zha", "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp", "universal_silabs_flasher" ], "requirements": [ "bellows==0.38.4", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.115", "zigpy-deconz==0.23.1", "zigpy==0.64.0", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.1", "universal-silabs-flasher==0.0.18", "pyserial-asyncio-fast==0.11" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "vid": "10C4", "pid": "EA60", "description": "*slzb-07*", "known_devices": [ "smlight slzb-07" ] }, { "vid": "1A86", "pid": "55D4", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus v2" ] }, { "vid": "10C4", "pid": "EA60", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ] }, { "vid": "10C4", "pid": "EA60", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ] }, { "vid": "1CF1", "pid": "0030", "description": "*conbee*", "known_devices": [ "Conbee II" ] }, { "vid": "0403", "pid": "6015", "description": "*conbee*", "known_devices": [ "Conbee III" ] }, { "vid": "10C4", "pid": "8A2A", "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ] }, { "vid": "0403", "pid": "6015", "description": "*zigate*", "known_devices": [ "ZiGate+" ] }, { "vid": "10C4", "pid": "EA60", "description": "*zigate*", "known_devices": [ "ZiGate" ] }, { "vid": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" }, { "type": "_zigstar_gw._tcp.local.", "name": "*zigstar*" }, { "type": "_uzg-01._tcp.local.", "name": "uzg-01*" }, { "type": "_slzb-06._tcp.local.", "name": "slzb-06*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 7403, "manufacturer": "_TZE204_xu4a5rhj", "model": "TS0601", "name": "_TZE204_xu4a5rhj TS0601", "quirk_applied": true, "quirk_class": "tuya.ts0601.TuyaCover0601_GP", "quirk_id": null, "manufacturer_code": 4417, "power_source": "Mains", "lqi": 196, "rssi": -51, "last_seen": "2024-05-30T18:11:55", "available": true, "device_type": "Router", "signature": { "node_descriptor": "NodeDescriptor(logical_type=Logs
```python 2024-05-30 18:16:55.897 DEBUG (MainThread) [bellows.ezsp.protocol] Send command sendUnicast: (Custom quirk
```python """Tuya based cover and blinds.""" from zigpy.profiles import zgp, zha from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time, OnOff from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import ( TuyaManufacturerWindowCover, TuyaManufCluster, TuyaWindowCover, TuyaWindowCoverControl, ) class TuyaCover0601_GP(TuyaWindowCover): """Tuya blind cover motor.""" signature = { MODELS_INFO: [ ("_TZE204_xu4a5rhj", "TS0601"), ], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, 242: { PROFILE_ID: 41440, DEVICE_TYPE: 97, INPUT_CLUSTERS: [], OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaManufacturerWindowCover, TuyaWindowCoverControl, ], OUTPUT_CLUSTERS: [Ota.cluster_id], }, 242: { PROFILE_ID: 41440, DEVICE_TYPE: 97, INPUT_CLUSTERS: [], OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], }, }, } ```Additional information
custom quirk gives an error File "/usr/src/homeassistant/homeassistant/components/zha/cover.py", line 290, in async_close_cover raise HomeAssistantError(f"Failed to close cover: {res[1]}") homeassistant.exceptions.HomeAssistantError: Failed to close cover: <Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>