Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Device Support Request] Device support for Tuya TS110E #1415

Closed
jonnylangefeld opened this issue Mar 6, 2022 · 49 comments · Fixed by #1422
Closed

[Device Support Request] Device support for Tuya TS110E #1415

jonnylangefeld opened this issue Mar 6, 2022 · 49 comments · Fixed by #1422

Comments

@jonnylangefeld
Copy link
Contributor

jonnylangefeld commented Mar 6, 2022

I am not sure if this is a device request, or if the device itself is just not working. Maybe someone can halp?

I ordered two of these in-wall dimmers:

If successfully installed it and paired it with home assistant (I'm using zha) and was able to use the on-off switch right away, but the dimmer slider wouldn't do anything. So naturally I thought I'd need a quirk. However, I wasn't able to get the dimmer slider working.

I pasted the device signature below and you can see that the in_clusters already have "0x0006" and "0x0008" which is OnOff and LevelControl. A lot of the other tuya quirks for the dimmers didn't have those, so I understand that a quirk was necessary. In this case however I was surprised that it wasn't working right out of the box, because the blakadder.com entry confirmed it working with ZHA. However, the blakadder.com entry lists it as Zigbee ID: "TS110F"; "_TYZB01_qezuin6k", while the one I actually got it Zigbee ID: "TS110E"; "_TZ3210_ngqk6jia". So a small difference, even though the device itself looks exactly as on the pictures on blakadder.com and aliexpress.com.

I came up with the following quirk that I stored in tuya/ts110e.py:

class DimmerSwitchWithNeutral1Gang(TuyaDimmerSwitch):
    """Tuya Dimmer Switch Module With Neutral 1 Gang"""

    signature = {
        MODELS_INFO: [("_TZ3210_ngqk6jia", "TS110E")],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=257
            # input_clusters=[0, 4, 5, 6, 8, 57345]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    LevelControl.cluster_id,
                    TuyaZBExternalSwitchTypeCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
                # input_clusters=[]
                # output_clusters=[33]
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }
    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    LevelControl.cluster_id,
                    # TuyaManufacturerClusterOnOff,
                    # TuyaOnOff,
                    # TuyaManufacturerLevelControl,
                    # TuyaLevelControl,
                    TuyaZBExternalSwitchTypeCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

The quirk is seen by home assistant (I see the Quirk: zhaquirks.tuya.ts110e.DimmerSwitchWithNeutral1Gang on the device in the home assistant UI). As you can see I experimented with all combinations of the OnOff and LevelControl alternatives. So far only the original

                    OnOff.cluster_id,
                    LevelControl.cluster_id,

works for at least the switch but not the dimmer.
Other combinations i tried:

                    TuyaOnOff,
                    TuyaLevelControl,

that comes with error

2022-03-06 22:48:32 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [548252938000] 'DimmerSwitchWithNeutral1Gang' object has no attribute 'command_bus'

Which makes sense. So I tried

                    TuyaManufacturerClusterOnOff,
                    TuyaOnOff,
                    TuyaManufacturerLevelControl,
                    TuyaLevelControl,

Which didn't throw an error and allowed me to call the switch in the UI of home assistant, but it wouldn't turn on the light and the switch in the UI jumps back like 2 seconds later.

Does anyone have any other suggestions of what I could try?

Device signature:

{
  "node_descriptor": "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)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0101",
      "in_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0x0006",
        "0x0008",
        "0xe001"
      ],
      "out_clusters": [
        "0x000a",
        "0x0019"
      ]
    },
    "242": {
      "profile_id": 41440,
      "device_type": "0x0061",
      "in_clusters": [],
      "out_clusters": [
        "0x0021"
      ]
    }
  },
  "manufacturer": "_TZ3210_ngqk6jia",
  "model": "TS110E",
  "class": "zhaquirks.tuya.ts110e.DimmerSwitchWithNeutral1Gang"
}

Additional context
Add any other context or screenshots about the feature request here.

@jonnylangefeld jonnylangefeld changed the title [Device Support Request] Device support for Tuya TS110F [Device Support Request] Device support for Tuya TS110E Mar 6, 2022
@javicalle
Copy link
Collaborator

I suggest you to remove your quirk and post the 'original' device signature. The one you have posted is with the quirk applied.
Also can be useful to attach the logs when you operate the physical device. Enable debug logs as described here:
https://www.home-assistant.io/integrations/zha/#debug-logging

@jonnylangefeld
Copy link
Contributor Author

jonnylangefeld commented Mar 7, 2022

Thanks so much for getting back to me!
Ah I didn't realize that was the device signature with the quirk enabled. I ran rm zhaquirks/tuya/ts110e.py and restarted home assistant. As expected, the quirk doesn't show in the home assistant UI:
Screen Shot 2022-03-06 at 5 44 32 PM
The device signature looks pretty close to what I originally posted, just the last line is different. It says "class": "zigpy.device.Device" now:

{
  "node_descriptor": "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)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0101",
      "in_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0x0006",
        "0x0008",
        "0xe001"
      ],
      "out_clusters": [
        "0x000a",
        "0x0019"
      ]
    },
    "242": {
      "profile_id": 41440,
      "device_type": "0x0061",
      "in_clusters": [],
      "out_clusters": [
        "0x0021"
      ]
    }
  },
  "manufacturer": "_TZ3210_ngqk6jia",
  "model": "TS110E",
  "class": "zigpy.device.Device"
}

I also activated the logs (thanks for the link) and got these logs when clicking the switch on the home assistant UI to 'off':

2022-03-07 01:48:32 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_off, service_data=entity_id=light.kitchen_backsplash_2_level_on_off>
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=6, TSN=39, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=30, Data=b'\x01\x27\x00')
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=39)
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=6, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=75, SecurityUse=<Bool.false: 0>, TimeStamp=647701, TSN=0, Data=b'\x18\x27\x0B\x00\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=39 command_id=Command.Default_Response>
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=6, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=75, SecurityUse=<Bool.false: 0>, TimeStamp=647903, TSN=0, Data=b'\x08\x63\x0A\x00\x00\x10\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=99 command_id=Command.Report_Attributes>
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0006] ZCL request 0x000a: [[Attribute(attrid=0, value=<TypeValue type=Bool, value=Bool.false>)]]
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0006] Attribute report received: on_off=0
2022-03-07 01:48:32 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=light.kitchen_backsplash_2_level_on_off, old_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=201, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:42:40.532954+00:00>, new_state=<state light.kitchen_backsplash_2_level_on_off=off; supported_color_modes=['brightness'], off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:48:32.570891+00:00>>
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=6, TSN=99, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x63\x0B\x0A\x00')
2022-03-07 01:48:32 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0006]: executed 'off' command with args: '()' kwargs: '{}' result: [0, <Status.SUCCESS: 0>]
2022-03-07 01:48:32 DEBUG (MainThread) [homeassistant.components.zha.entity] light.kitchen_backsplash_2_level_on_off: turned off: [0, <Status.SUCCESS: 0>]
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-07 01:48:32 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=99)

Then I used the home assistant UI to slide the brightness to 49 and got these logs:

2022-03-07 01:56:22 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_on, service_data=entity_id=light.kitchen_backsplash_2_level_on_off, brightness_pct=49>
2022-03-07 01:56:22 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=40, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=30, Data=b'\x01\x28\x04\x7D\x01\x00')
2022-03-07 01:56:22 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-07 01:56:22 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=40)
2022-03-07 01:56:22 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=75, SecurityUse=<Bool.false: 0>, TimeStamp=14300235, TSN=0, Data=b'\x18\x28\x0B\x04\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-07 01:56:22 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=40 command_id=Command.Default_Response>
2022-03-07 01:56:22 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: executed 'move_to_level_with_on_off' command with args: '(125, 1)' kwargs: '{}' result: [4, <Status.SUCCESS: 0>]
2022-03-07 01:56:22 DEBUG (MainThread) [homeassistant.components.zha.entity] light.kitchen_backsplash_2_level_on_off: turned on: {'move_to_level_with_on_off': [4, <Status.SUCCESS: 0>]}
2022-03-07 01:56:22 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=light.kitchen_backsplash_2_level_on_off, old_state=<state light.kitchen_backsplash_2_level_on_off=off; supported_color_modes=['brightness'], off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:48:32.570891+00:00>, new_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=125, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:56:22.649522+00:00>>

Only interesting thing I can find in the logs is that the off command shows something like brightness=201 and the brightness command shows something like brightness=125. Maybe all the brightnesses ever set are over 100?

@jonnylangefeld
Copy link
Contributor Author

Hmm inspecting this closer I don't think the problem is that the brightness is over 100. It seems like brightness is a scale from 0 to 255. Like when I slide the slider to 2, there is a

executed 'move_to_level_with_on_off' command with args: '(5, 1)'

full logs for that:

2022-03-07 02:03:23 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_on, service_data=entity_id=light.kitchen_backsplash_2_level_on_off, brightness_pct=2>
2022-03-07 02:03:23 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=44, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=30, Data=b'\x01\x2C\x04\x05\x01\x00')
2022-03-07 02:03:23 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-07 02:03:23 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=44)
2022-03-07 02:03:23 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=72, SecurityUse=<Bool.false: 0>, TimeStamp=9161317, TSN=0, Data=b'\x18\x2C\x0B\x04\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-07 02:03:23 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=44 command_id=Command.Default_Response>
2022-03-07 02:03:23 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: executed 'move_to_level_with_on_off' command with args: '(5, 1)' kwargs: '{}' result: [4, <Status.SUCCESS: 0>]
2022-03-07 02:03:23 DEBUG (MainThread) [homeassistant.components.zha.entity] light.kitchen_backsplash_2_level_on_off: turned on: {'move_to_level_with_on_off': [4, <Status.SUCCESS: 0>]}
2022-03-07 02:03:23 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=light.kitchen_backsplash_2_level_on_off, old_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=252, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:56:22.649522+00:00>, new_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=5, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:56:22.649522+00:00>>

And If I move it to 98 there is a

executed 'move_to_level_with_on_off' command with args: '(250, 1)'

full logs for that

2022-03-07 02:06:34 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_on, service_data=entity_id=light.kitchen_backsplash_2_level_on_off, brightness_pct=98>
2022-03-07 02:06:34 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=45, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=30, Data=b'\x01\x2D\x04\xFA\x01\x00')
2022-03-07 02:06:34 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-07 02:06:34 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=45)
2022-03-07 02:06:34 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=72, SecurityUse=<Bool.false: 0>, TimeStamp=5381833, TSN=0, Data=b'\x18\x2D\x0B\x04\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-07 02:06:34 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=45 command_id=Command.Default_Response>
2022-03-07 02:06:34 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: executed 'move_to_level_with_on_off' command with args: '(250, 1)' kwargs: '{}' result: [4, <Status.SUCCESS: 0>]
2022-03-07 02:06:34 DEBUG (MainThread) [homeassistant.components.zha.entity] light.kitchen_backsplash_2_level_on_off: turned on: {'move_to_level_with_on_off': [4, <Status.SUCCESS: 0>]}
2022-03-07 02:06:34 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=light.kitchen_backsplash_2_level_on_off, old_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=5, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:56:22.649522+00:00>, new_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=250, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-07T01:56:22.649522+00:00>>

Is there anything else interesting you can find in the logs?

@javicalle
Copy link
Collaborator

Thanks for the signature.

It seems that your device its a 'normal' zigbee device with the 0xe001 cluster from Tuya. Your quirk seems adequate for device signature, but for some reason the dim events don't seem to arrive/come from the proper cluster.

Try to get traces from operate the physical device. Usually when you interact with the device, it reports its status and we can get some information from that message.

PS: dim values are from 0 to 255 😉

@jonnylangefeld
Copy link
Contributor Author

I see now what you meant. Not just control the slider of the UI, but actually the physical device.
So I did that and got a good chunk of logs. Attached are the logs for me dimming the light from all the way bright to all the way dark.

I can't quite wrap my head around yet what I can make out of that yet. The slider in the UI for the dimmer doesn't move at all when I dim the physical light.

I do see log lines like

2022-03-08 04:48:25 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=75>)]]
2022-03-08 04:48:25 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=75
2022-03-08 04:48:25 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 75

I see that the dim values from the device itself are between 0 and 1000. So that has to be mapped to the dim values 0 to 255. I think for the existing quirks that's done here:

level = (((state[3] << 8) + state[4]) * 255) // 1000

So I probably somehow have to incorporate that TuyaLevelControl class. Which I tried before with no success.

I also see that 0x0008 in there, which translates to the generic LevelControl, so I think that's a good thing.

I also see that attribute 0x3891 and 61440 but not sure what that means.

Do you see anything in the logs when operating the physical device below that could give me a hint of what I'm missing?

2022-03-08 04:48:25 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=90, SecurityUse=<Bool.false: 0>, TimeStamp=3993970, TSN=0, Data=b'\x08\x87\x0A\x00\xF0\x21\x4B\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:25 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=135 command_id=Command.Report_Attributes>
2022-03-08 04:48:25 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=75>)]]
2022-03-08 04:48:25 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=75
2022-03-08 04:48:25 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 75
2022-03-08 04:48:25 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=135, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x87\x0B\x0A\x00')
2022-03-08 04:48:25 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:25 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=135)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=84, SecurityUse=<Bool.false: 0>, TimeStamp=4019549, TSN=0, Data=b'\x08\x88\x0A\x00\xF0\x21\x89\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=136 command_id=Command.Report_Attributes>
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=137>)]]
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=137
2022-03-08 04:48:26 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 137
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=136, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x88\x0B\x0A\x00')
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=136)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=96, SecurityUse=<Bool.false: 0>, TimeStamp=4045189, TSN=0, Data=b'\x08\x89\x0A\x00\xF0\x21\xC8\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=137 command_id=Command.Report_Attributes>
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=200>)]]
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=200
2022-03-08 04:48:26 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 200
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=137, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x89\x0B\x0A\x00')
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=137)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=90, SecurityUse=<Bool.false: 0>, TimeStamp=4070767, TSN=0, Data=b'\x08\x8A\x0A\x00\xF0\x21\x07\x01', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=138 command_id=Command.Report_Attributes>
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=263>)]]
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=263
2022-03-08 04:48:26 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 263
2022-03-08 04:48:26 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=138, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x8A\x0B\x0A\x00')
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=138)
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=90, SecurityUse=<Bool.false: 0>, TimeStamp=4096392, TSN=0, Data=b'\x08\x8B\x0A\x00\xF0\x21\x45\x01', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=139 command_id=Command.Report_Attributes>
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=325>)]]
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=325
2022-03-08 04:48:27 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 325
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=139, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x8B\x0B\x0A\x00')
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=139)
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=84, SecurityUse=<Bool.false: 0>, TimeStamp=4121931, TSN=0, Data=b'\x08\x8C\x0A\x00\xF0\x21\x84\x01', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=140 command_id=Command.Report_Attributes>
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=388>)]]
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=388
2022-03-08 04:48:27 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 388
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=140, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x8C\x0B\x0A\x00')
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:27 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=140)
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=87, SecurityUse=<Bool.false: 0>, TimeStamp=4147594, TSN=0, Data=b'\x08\x8D\x0A\x00\xF0\x21\xC3\x01', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=141 command_id=Command.Report_Attributes>
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=451>)]]
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=451
2022-03-08 04:48:28 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 451
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=141, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x8D\x0B\x0A\x00')
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=141)
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=90, SecurityUse=<Bool.false: 0>, TimeStamp=4173234, TSN=0, Data=b'\x08\x8E\x0A\x00\xF0\x21\x02\x02', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=142 command_id=Command.Report_Attributes>
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=514>)]]
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=514
2022-03-08 04:48:28 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 514
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=142, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x8E\x0B\x0A\x00')
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:28 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=142)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=90, SecurityUse=<Bool.false: 0>, TimeStamp=4198871, TSN=0, Data=b'\x08\x8F\x0A\x00\xF0\x21\x40\x02', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=143 command_id=Command.Report_Attributes>
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=576>)]]
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=576
2022-03-08 04:48:29 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 576
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=143, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x8F\x0B\x0A\x00')
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=143)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=90, SecurityUse=<Bool.false: 0>, TimeStamp=4224444, TSN=0, Data=b'\x08\x90\x0A\x00\xF0\x21\x7F\x02', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=144 command_id=Command.Report_Attributes>
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=639>)]]
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=639
2022-03-08 04:48:29 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 639
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=144, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x90\x0B\x0A\x00')
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=144)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=87, SecurityUse=<Bool.false: 0>, TimeStamp=4250026, TSN=0, Data=b'\x08\x91\x0A\x00\xF0\x21\xBE\x02', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=145 command_id=Command.Report_Attributes>
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=702>)]]
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=702
2022-03-08 04:48:29 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 702
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=145, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x91\x0B\x0A\x00')
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:29 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=145)
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=87, SecurityUse=<Bool.false: 0>, TimeStamp=4275645, TSN=0, Data=b'\x08\x92\x0A\x00\xF0\x21\xFD\x02', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=146 command_id=Command.Report_Attributes>
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=765>)]]
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=765
2022-03-08 04:48:30 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 765
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=146, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x92\x0B\x0A\x00')
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=146)
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=87, SecurityUse=<Bool.false: 0>, TimeStamp=4301214, TSN=0, Data=b'\x08\x93\x0A\x00\xF0\x21\x3B\x03', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=147 command_id=Command.Report_Attributes>
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=827>)]]
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=827
2022-03-08 04:48:30 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 827
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=147, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x93\x0B\x0A\x00')
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:30 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=147)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=84, SecurityUse=<Bool.false: 0>, TimeStamp=4326890, TSN=0, Data=b'\x08\x94\x0A\x00\xF0\x21\x7A\x03', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=148 command_id=Command.Report_Attributes>
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=890>)]]
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=890
2022-03-08 04:48:31 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 890
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=148, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x94\x0B\x0A\x00')
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=148)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=87, SecurityUse=<Bool.false: 0>, TimeStamp=4352469, TSN=0, Data=b'\x08\x95\x0A\x00\xF0\x21\xB9\x03', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=149 command_id=Command.Report_Attributes>
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=953>)]]
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=953
2022-03-08 04:48:31 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 953
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=149, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x95\x0B\x0A\x00')
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=149)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=87, SecurityUse=<Bool.false: 0>, TimeStamp=4372171, TSN=0, Data=b'\x08\x96\x0A\x00\xF0\x21\xE4\x03', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=150 command_id=Command.Report_Attributes>
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=996>)]]
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=996
2022-03-08 04:48:31 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 61440 update with value: 996
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=150, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK: 32>, Radius=30, Data=b'\x18\x96\x0B\x0A\x00')
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 04:48:31 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=150)

@jonnylangefeld
Copy link
Contributor Author

Maybe interesting: When I bring back my custom quirk with these input clusters enabled:

                    TuyaManufacturerClusterOnOff,
                    TuyaOnOff,
                    TuyaManufacturerLevelControl,
                    TuyaLevelControl,

And then setting the brightness via UI slider I get

2022-03-08 06:13:36 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_on, service_data=entity_id=light.kitchen_backsplash_2_level_on_off, brightness_pct=53>
2022-03-08 06:13:36 DEBUG (MainThread) [zhaquirks.tuya] a4:c1:38:76:3f:03:3e:46 Sending Tuya Cluster Command.. Cluster Command is 4, Arguments are (135, 1)
2022-03-08 06:13:36 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=61184, TSN=25, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=30, Data=b'\x05\x41\x11\x19\x00\x00\x00\x02\x02\x00\x04\x00\x00\x02\x11')
2022-03-08 06:13:36 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 06:13:36 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=25)
2022-03-08 06:13:36 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=61184, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=84, SecurityUse=<Bool.false: 0>, TimeStamp=2763000, TSN=0, Data=b'\x18\x19\x0B\x00\x83', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 06:13:36 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0xef00] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=25 command_id=Command.Default_Response>
2022-03-08 06:13:36 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: executed 'move_to_level_with_on_off' command with args: '(135, 1)' kwargs: '{}' result: [0, <Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>]
2022-03-08 06:13:36 DEBUG (MainThread) [homeassistant.components.zha.entity] light.kitchen_backsplash_2_level_on_off: turned on: {'move_to_level_with_on_off': [0, <Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>]}

Which has a different ClusterId=61184 and it says result: [0, <Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>].

But I do I get my quirk to set this attribute 61440 (which is 0xF000) to a value between 0 and 1000?

@javicalle
Copy link
Collaborator

javicalle commented Mar 8, 2022

Ok, first I am going to try to explain to you what we are seeing and then we will see what can be done.

2022-03-08 04:48:25 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=)]]

Here we have an attributte report from the device.
This [0x3891:1:0x0008] is some standard part of ZHA messages and is telling us that the device 0x3891 from endpoint 1 and cluster 0x0008 is sending the attribute report.
Tha part is correct. You can see in the device signature this endpoint and this cluster (which, as you said, corresponds to the LevelControl)

The second part is telling us that the attribute 61440 is updating its value (from 0 to 1000). And that is a problem.
HA (and the Zigbee standard) expect dim values (current_level) to be reported in the 0x0000 attribute (and not the 0xF000 as that device is doing):

https://github.com/zigpy/zigpy/blob/0c4b9a8a25c5eef5b008d66c2e56aae3f8d5e769/zigpy/zcl/clusters/general.py#L696-L716

Also, the expected that values are from 0 to 255.

And, what can be done?
Obviously we need to create a quirk for this cluster. It is necessary to convert the attrid and its value both ways ZHA --> device and device --> ZHA

Create a new quirk for this LevelControl cluster. It may be something like:

  class F000LevelControlCluster(LevelControl):
    """LevelControlCluster that reports to attrid 0xF000."""

    class TuyaLevelPayload(t.Struct):
        """Tuya Level payload."""

        level: t.uint16_t
        transtime: t.uint16_t

    manufacturer_attributes = {0xF000: ("manufacturer_current_level", t.uint16_t),} 

    manufacturer_server_commands = {
        0x00F0: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

    # 0xF000 reported values are 0-1000, convert to 0-255
    def _update_attribute(self, attrid, value):
        if attrid == 0xF000:
            value = (value * 255) // 1000
            attrid = 0x0000

        super()._update_attribute(attrid, value)

    async def command(
        self,
        command_id: Union[foundation.Command, 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."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            return await super().command(
                0x00F0, 
                TuyaLevelPayload(level=brightness, transtime=0), 
                manufacturer, 
                expect_reply,
                tsn
            )

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

Be carefull with new imports.

Then you can use this cluster in your quirk:

class DimmerSwitchWithNeutral1Gang(TuyaDimmerSwitch):
    """Tuya Dimmer Switch Module With Neutral 1 Gang"""

    signature = {
        MODELS_INFO: [("_TZ3210_ngqk6jia", "TS110E")],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=257
            # input_clusters=[0, 4, 5, 6, 8, 57345]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    LevelControl.cluster_id,
                    TuyaZBExternalSwitchTypeCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
                # input_clusters=[]
                # output_clusters=[33]
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }
    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    F000LevelControlCluster,
                    TuyaZBExternalSwitchTypeCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

The _update_attribute will handle "device --> ZHA" messages and is responsible for the reported values being updated in HA.
The command will be in charge of sending the values that the device expects. This part makes me more doubtful because I haven't located a similar implementation.

edit: code update

@MattWestb
Copy link
Contributor

Only for info i have one 2 gang with no neutral version TS110F _TZ3000_92chsky7 that is working OK but its not having the extra function with min and max dim level cluster (0xE001) (what i knowing) that this is having and is working OK in ZHA without quirk.
This devices its little normal tuya devices we is having but its working nice (until IKEA was taking the group binding away and i cant controlling the gangs from them only the all the device on / off).

@javicalle
Copy link
Collaborator

javicalle commented Mar 8, 2022

@MattWestb do you know about any quirk that is 'translating' from one cluster attribute (0xF000 here) to other one (the standard 0x0000)?
I haven't find any implementation like this and I'm not sure about the "HA --> device" part.

@MattWestb
Copy link
Contributor

I have not seen one, then tuya is making all thing not 100% the same like the power on default is 2/3 function OK and have leaving it on the tuya attribute and not "converting" it to ZCL standard.

Perhaps the tuya TRVs is using many attributes but i dont knowing is some is being "moved" to ZCL standard but its not the same then its to and from DP commands.

Perhaps our @puddly is knowing if some Xiaomi devices is doing it (they using different attributes / commands strings for attribute reporting and setting)?

@MattWestb
Copy link
Contributor

I have putting in the new found attribute in the matrix as WIP and linked here. zigpy/zigpy#823 (comment)

Then have the level working is the device very likely have more attributes that is not implanted but can being done like what Bulb type is connected (found in latest tuya dev docks and all is not tested but some is working like min and may dim).

@jonnylangefeld
Copy link
Contributor Author

jonnylangefeld commented Mar 8, 2022

Wow awesome, thanks so much for your help so far. So you were exactly right, the _update_attribute function works as expected. As I dim on the physical device I can see the brightness slider in the home assistant UI change in real time. That's pretty cool.

2022-03-08 22:21:36 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL request 0x000a: [[Attribute(attrid=61440, value=<TypeValue type=uint16_t, value=494>)]]
2022-03-08 22:21:36 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Attribute report received: 61440=494
2022-03-08 22:21:36 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: received attribute: 0 update with value: 125

You were also right that the command function doesn't quite work yet, so I can't update the brightness from home assistant yet.
First I got an error that I fixed quickly by adding an async before the def command because it told me await can't be in a function without async.
Now home assistant starts up and successfully updates the values as I dim the physical device, but whenever I move the slider int he UI I get

2022-03-08 22:22:41 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Sending Cluster Command. Cluster Command is 4, Arguments are (138, 1)
2022-03-08 22:22:41 ERROR (MainThread) [zigpy.zcl] [0x3891:1:0x0008] 61440 is not a valid attribute id

and then a little further down

2022-03-08 22:22:41 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [546955499648] list index out of range
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 185, in handle_call_service
    await hass.services.async_call(
  File "/usr/src/homeassistant/homeassistant/core.py", line 1495, in async_call
    task.result()
  File "/usr/src/homeassistant/homeassistant/core.py", line 1530, in _execute_service
    await handler.job.target(service_call)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 209, in handle_service
    await self.hass.helpers.service.entity_service_call(
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 663, in entity_service_call
    future.result()  # pop exception if have
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 896, in async_request_call
    await coro
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 700, in _handle_entity_call
    await result
  File "/usr/src/homeassistant/homeassistant/components/light/__init__.py", line 494, in async_handle_light_on_service
    await light.async_turn_on(**filter_turn_on_params(light, params))
  File "/usr/src/homeassistant/homeassistant/components/zha/light.py", line 236, in async_turn_on
    if not isinstance(result, list) or result[1] is not Status.SUCCESS:
IndexError: list index out of range

So it expects result[1] to be there.
I guess what we're returning in return await self.write_attributes({0xF000: brightness}, manufacturer=manufacturer) somehow needs to return a list?

Is it now guesswork to find out what command the device needs?

@jonnylangefeld
Copy link
Contributor Author

Do you think something similar to this helps?

async def write_attributes(self, attributes, manufacturer=None):
"""Defer attributes writing to the set_data tuya command."""
records = self._write_attr_records(attributes)
for record in records:
cmd_payload = TuyaManufCluster.Command()
cmd_payload.status = 0
cmd_payload.tsn = self.endpoint.device.application.get_sequence()
cmd_payload.command_id = record.attrid
cmd_payload.function = 0
cmd_payload.data = Data.from_value(record.value.value)
await super().command(
TUYA_SET_DATA,
cmd_payload,
manufacturer=manufacturer,
expect_reply=False,
tsn=cmd_payload.tsn,
)
return [[foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)]]

I already tries some variations with now success yet.

@javicalle
Copy link
Collaborator

Try uncomment this line from F000LevelControlCluster:

    manufacturer_attributes = {0xF000: ("current_level", t.uint8_t),} # maybe needed for write_attributes

@jonnylangefeld
Copy link
Contributor Author

Hmm no, still the same error:

  File "/usr/src/homeassistant/homeassistant/components/zha/light.py", line 236, in async_turn_on
    if not isinstance(result, list) or result[1] is not Status.SUCCESS:
IndexError: list index out of range

But what's new is that for every tuya value greater than 256 I get this error as well:

2022-03-08 23:17:28 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Sending Cluster Command. Cluster Command is 4, Arguments are (102, 1)
2022-03-08 23:17:28 ERROR (MainThread) [zigpy.zcl] [0x3891:1:0x0008] 400 is not an unsigned 8 bit integer

@jonnylangefeld
Copy link
Contributor Author

changing it to

    manufacturer_attributes = {0xF000: ("current_level", t.uint16_t),} # maybe needed for write_attributes

removes the second error, but still has the first error...

@javicalle
Copy link
Collaborator

Maybe this way (as a quick&dirty workaround):

    async def command(
        self,
        command_id: Union[foundation.Command, 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."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            self.write_attributes({0xF000: brightness}, manufacturer=manufacturer)
            return [[foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)]] 

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

I'll keep looking...

@javicalle
Copy link
Collaborator

I think that there is a better approach:

    async def command(
        self,
        command_id: Union[foundation.Command, 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."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            (res,) = await self.write_attributes({0xF000: brightness}, manufacturer=manufacturer)
            return [command_id, res[0].status]

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

🤞🏻

@javicalle
Copy link
Collaborator

Another test you can perform is try to read & update this attribute from cluster F000LevelControlCluster.
From the device view --> "Manage clusters" --> Select 'F000LevelControlCluster' --> Select attribute from cluster 'current_level (id: 0xF000)' --> 'Get zigbee attribute'

You can try to put a 0-255 value and hit 'Set zigbee attribute', this should change the dim value from device.

Look at the logs and attach all you find relevant.

@jonnylangefeld
Copy link
Contributor Author

So I tried the second better approach you sent and no error anymore, but also not changing the brightness on the device.
Log of sliding the brightness to a new point:

2022-03-08 23:56:44 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_on, service_data=entity_id=light.kitchen_backsplash_2_level_on_off, brightness_pct=78>
2022-03-08 23:56:44 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] Sending Cluster Command. Cluster Command is 4, Arguments are (199, 1)
2022-03-08 23:56:44 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x3891), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=8, TSN=25, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=30, Data=b'\x00\x19\x02\x00\xF0\x21\x0C\x03')
2022-03-08 23:56:44 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataRequestExt.Rsp(Status=<Status.SUCCESS: 0>)
2022-03-08 23:56:44 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.DataConfirm.Callback(Status=<Status.SUCCESS: 0>, Endpoint=1, TSN=25)
2022-03-08 23:56:44 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=8, SrcAddr=0x3891, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=87, SecurityUse=<Bool.false: 0>, TimeStamp=6899442, TSN=0, Data=b'\x18\x19\x04\x00', MacSrcAddr=0x3891, MsgResultRadius=29)
2022-03-08 23:56:44 DEBUG (MainThread) [zigpy.zcl] [0x3891:1:0x0008] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=25 command_id=Command.Write_Attributes_rsp>
2022-03-08 23:56:44 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x3891:1:0x0008]: executed 'move_to_level_with_on_off' command with args: '(199, 1)' kwargs: '{}' result: [4, <Status.SUCCESS: 0>]
2022-03-08 23:56:44 DEBUG (MainThread) [homeassistant.components.zha.entity] light.kitchen_backsplash_2_level_on_off: turned on: {'move_to_level_with_on_off': [4, <Status.SUCCESS: 0>]}
2022-03-08 23:56:44 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=light.kitchen_backsplash_2_level_on_off, old_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=110, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-08T23:55:22.696105+00:00>, new_state=<state light.kitchen_backsplash_2_level_on_off=on; supported_color_modes=['brightness'], color_mode=brightness, brightness=199, off_brightness=None, friendly_name=Kitchen Backsplash 2 level, on_off, supported_features=33 @ 2022-03-08T23:55:22.696105+00:00>>

I will try the "Manage clusters" read and update attribute next.

@jonnylangefeld
Copy link
Contributor Author

First thing that's interesting there is that there's two current_level attributes with different ids:
Screen Shot 2022-03-08 at 4 00 40 PM
Does this look wrong?

@javicalle
Copy link
Collaborator

No, that is what I has expected.

0xF000 is the manufacturer_attribute that we have defined you can change the current_level to whatever you want. Also is the attribute that this device is using to report the dim values.
0x0000 is the zigbee standar current_level attribute for dimmers.

The problem is that HA will 'talk' with the 0x0000 attribute, but it seems that device is listening to the 0xF000.

@javicalle
Copy link
Collaborator

It would be probably more appropriate to rename it to manufacturer_current_level 🤷🏻‍♂️

@jonnylangefeld
Copy link
Contributor Author

hmm so I tried all combinations in the "Manage Cluster" interface. But all I got working from there was the on and off commands from the on off cluster.
Screen Shot 2022-03-08 at 4 13 44 PM
No attributes or commands of the F000LevelControlCluster were working yet. They all didn't change the brightness.
What's also interesting is that if I move the slider in the UI and click 'Get Zigbee Attribute', then I get the updated value. If I change the brightness on the physical dimmer, then the click on 'Get Zigbee Attribute' doesn't give me a new value...

@jonnylangefeld
Copy link
Contributor Author

If I click 'Issue Zigbee Command' for move_to_level_with_on_off, I get this error:

  File "/usr/local/lib/python3.9/site-packages/zhaquirks/tuya/ts110e.py", line 81, in command
    brightness = (args[0] * 1000) // 255
IndexError: tuple index out of range

@javicalle
Copy link
Collaborator

javicalle commented Mar 9, 2022

The 'sets' from 0x0000 attribute are not expected to does nothing.
'Gets' probably return an empty value. Maybe even after trying to assign a value to it.

'Gets' for the 0xF000 attribute should return the dimm value.
Setting values should change the dimm value.

Wich one of the current_level attributes is updating his value?

@javicalle
Copy link
Collaborator

If I click 'Issue Zigbee Command' for move_to_level_with_on_off, I get this error:

That's normal because UI command can't get values for methods.
This method requires a 'value' to work (and no, the manufacturer Id is not that value). That is why args[0] is out of range.

@jonnylangefeld
Copy link
Contributor Author

Okay so for the 0xF000 attribute it only changes the values if I set it via the UI slider (I'm setting it on my phone as a second device and keep getting the value).
As I'm dimming the physical device, the 0xF000 one doesn't change. It stays on what I set in the UI slider last. Even though the UI slider gets updated to the current dim level (the success we had earlier).

@javicalle
Copy link
Collaborator

So, it seems that device is NOT using the standard 0x0000 attribute for current_level
It is reporting the current_level in attribute 0xF000
But is NOT using neither the 0xF000 attribute for setting current_level

Wonderfull... maybe it's using another attribute for setting values.

One last try that you can make is to change the write_attributes call this way (yes b""):

            (res,) = await self.write_attributes({0xF000: brightness}, manufacturer=b"")

It's rest time for me. Tomorrow I will look if this device has been implemented in other libraries and maybe someone have found how to set the dim value 🤦🏻‍♂️

@jonnylangefeld
Copy link
Contributor Author

Yeah it's really weird. But the write_attribute method definitely works because as I'm sliding the slider in the UI, I can click 'Get Zigbee Attribute' and the value for 0xf000 updates.
Also as I'm using the physical device, the slider in the UI updates, that means the _update_attribute works as well.

What's weird is that as I'm using the physical device the click on 'Get Zigbee Attribute' doesn't update and also that when I'm using the slider in the UI the brightness of the physical device doesn't update.

I tried manufacturer=b"" as well with no success.

That would be awesome to look at other implementations. I actually did look around a bit and found these lines in zigbee2mqtt:
https://github.com/Koenkk/zigbee-herdsman-converters/blob/baf446a61da472667ac1a263f7dde90a7861d02f/devices/lonsonho.js#L84-L91

I'm just really bad at reading this and it doesn't mean much to me. Does that mean anything to you?
It looks like it's using this behind the scenes.

@jonnylangefeld
Copy link
Contributor Author

Hmm it appears in this thread they also discovered that this device is hard to implement and in September they reverted support for the TS110E device. After all, I thought I would get the TS110F device, but apparently they sent me the TS110E device... Maybe I'm just in bad luck?

@jonnylangefeld
Copy link
Contributor Author

jonnylangefeld commented Mar 9, 2022

Okay sorry for posting so much, but after even more digging, there's a post that promises more hope: A lot of other issues on zigbee2mqtt linked to this post: Koenkk/zigbee2mqtt#9878
They report that this converter is working for them, they just never added it properly to zigbee2mqtt.
Can you interpret what the converter is doing and how we could translate it into our quirk?

The key seems to be this non-standard command:

In the file .src/zcl/definition/cluster.ts you need to add a new command for the 0008 cluster:

           moveToLevelTuya: {
               ID: 240,
               parameters: [
                   { name: 'level', type: dataType_1.default.uint16 },
                   { name: 'transtime', type: dataType_1.default.uint16 },
               ],
           },

@javicalle
Copy link
Collaborator

I'm also reading the same posts.

Today last offer:

  class F000LevelControlCluster(LevelControl):
    """LevelControlCluster that reports to attrid 0xF000."""

    class TuyaLevelPayload(t.Struct):
        """Tuya Level payload."""

        level: t.uint16_t
        transtime: t.uint16_t

    manufacturer_attributes = {0xF000: ("manufacturer_current_level", t.uint16_t),} 

    manufacturer_server_commands = {
        0x00F0: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

    # 0xF000 reported values are 0-1000, convert to 0-255
    def _update_attribute(self, attrid, value):
        if attrid == 0xF000:
            value = (value * 255) // 1000
            attrid = 0x0000

        super()._update_attribute(attrid, value)

    async def command(
        self,
        command_id: Union[foundation.Command, 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."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            return await super().command(
                0x00F0, 
                TuyaLevelPayload(level=brightness, transtime=0), 
                manufacturer, 
                expect_reply,
                tsn
            )

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

Not sure if it must be manufacturer_server_commands or manufacturer_client_commands. Try to change if gives you problems.

I will check the results in (my) morning. 😴

@jonnylangefeld
Copy link
Contributor Author

Wow! Good news for when you wake up 😄 After a few more changes it's finally working 🎉! Thank you so much!!

I had to move the class TuyaLevelPayload outside the class because otherwise it would say NameError: name 'TuyaLevelPayload' is not defined.
I also had to name the parameters manufacturer, expect_reply and tsn cause otherwise they would count into the *args of the command method. But with that it's finally working now!

Here's the final product:

class TuyaLevelPayload(t.Struct):
    """Tuya Level payload."""

    level: t.uint16_t
    transtime: t.uint16_t


class F000LevelControlCluster(LevelControl):
    """LevelControlCluster that reports to attrid 0xF000."""

    manufacturer_attributes = {0xF000: ("manufacturer_current_level", t.uint16_t),} 

    manufacturer_server_commands = {
        0x00F0: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

    # 0xF000 reported values are 0-1000, convert to 0-255
    def _update_attribute(self, attrid, value):
        if attrid == 0xF000:
            value = (value * 255) // 1000
            attrid = 0x0000

        super()._update_attribute(attrid, value)

    async def command(
        self,
        command_id: Union[foundation.Command, 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."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            return await super().command(
                0x00F0, 
                TuyaLevelPayload(level=brightness, transtime=0), 
                manufacturer=manufacturer, 
                expect_reply=expect_reply,
                tsn=tsn
            )

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

I will clean up the imports, format with black and whip up a PR.

Also, do you have a bitcoin lightning address? I'd like to send you a tip! This was amazing help!

jonnylangefeld added a commit to jonnylangefeld/zha-device-handlers that referenced this issue Mar 9, 2022
This device uses a non-default set level command.
Tested in local docker environment.
Thanks to @javicalle this was possible.
Fix zigpy#1415
@javicalle
Copy link
Collaborator

javicalle commented Mar 9, 2022

I'm glad you got it working. Nice job!

I have a few changes (and tests) if you like to play a little more.

min-max level attributes

Z2M indicates that there are a few more attributes for this device:

  • 0xFC02: bulb_type
  • 0xFC03: manufacturer_min_level
  • 0xFC04: manufacturer_max_level

The bulb_type has been seen around with other ID.
There are standard zigbee attributes in LevelControl cluster for the min/max level, but for now I think we're going to leave them unchanged.

To keep or not to keep manufacturer_values?

The manufacturer_attributes could be removed from the device definition and use only the standard attributes.
I think I prefer to keep both values. For the manufacturer_current_level also the standar current_levelattribute will be updated.
The same change can be done for the min/max level, but it will requiere modify the _update_attributes and also the write_attributes method.
For now I don't think it's worth it.

write_attributes overwrite?

If you want to use the standard cluster attributes to be able to read and write its values (in addition to _update_attributes) you must override the write_attributes method.
This may be necessary if (for example) in the future HA allows to configure the min/max level attribute from UI. The same way that HA only understand the standard current_level attribute, in this case HA will function only with the standard min_level and max_level attributes.
Overriding the method will require 'translating' the attributes to use and performing the conversion of values 0-255 vs 0-1000
For now I think we can leave it as is.

This is (more or less) the proposed version:

TUYA_LEVEL_ATTRIBUTE = 0xF000
TUYA_BULB_TYPE_ATTRIBUTE = 0xFC02
TUYA_MIN_LEVEL_ATTRIBUTE = 0xFC03
TUYA_MAX_LEVEL_ATTRIBUTE = 0xFC04
TUYA_CUSTOM_LEVEL_COMMAND = 0x00F0


class TuyaLevelPayload(t.Struct):
    """Tuya Level payload."""

    level: t.uint16_t
    transtime: t.uint16_t


class TuyaBulbType(t.enum8):
    """Tuya bulb type."""

    LED = 0x00
    INCANDESCENT = 0x01
    HALOGEN = 0x02


class F000LevelControlCluster(LevelControl):
    """LevelControlCluster that reports to attrid 0xF000."""

    manufacturer_attributes = {
        TUYA_LEVEL_ATTRIBUTE: ("manufacturer_current_level", t.uint16_t),
        TUYA_BULB_TYPE_ATTRIBUTE: ("bulb_type", TuyaBulbType), # 0xFC02
        TUYA_MIN_LEVEL_ATTRIBUTE: ("manufacturer_min_level", t.uint16_t), # 0xFC03
        TUYA_MAX_LEVEL_ATTRIBUTE: ("manufacturer_max_level", t.uint16_t), # 0xFC04
    }

    manufacturer_server_commands = {
        TUYA_CUSTOM_LEVEL_COMMAND: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

    def _update_attribute(self, attrid, value):
        super()._update_attribute(attrid, value)
        # update also `current_level` attribute
        if attrid == TUYA_LEVEL_ATTRIBUTE:
            # manufacturer reported values are 0-1000, convert to 0-255
            value = (value * 255) // 1000
            attrid = 0x0000
            super()._update_attribute(attrid, value)

    async def command(
        self,
        command_id: Union[foundation.Command, 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."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            return await super().command(
                TUYA_CUSTOM_LEVEL_COMMAND,
                TuyaLevelPayload(level=brightness, transtime=0),
                manufacturer=manufacturer,
                expect_reply=expect_reply,
                tsn=tsn,
            )

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

@javicalle
Copy link
Collaborator

javicalle commented Mar 9, 2022

A few comments about the new attributes.

  • I have no idea of the impact of the bulb_type attribute. I haven't read anything conclusive about it.
  • The range for the others manufacturer_attributes is 0-1000.
  • You only can update manufacturer_attributes from the "manage clusters" view (except manufacturer_current_level that must be addressed from the UI slider or any HA method)
  • Min/max values only affects to the dim from the physical device so that the values always stay in the range min<max. I'm not sure about this, but probably dim from HA will not respect this min/max values.
    Most Z2M devices take these limits into account and allow replicate the behavior from the physical device.
    I have not seen it implemented in ZHA, but IMHO it is a topic to address in a more generic way and not for each device.

@javicalle
Copy link
Collaborator

    manufacturer_attributes = {
        TUYA_LEVEL_ATTRIBUTE: ("manufacturer_current_level", t.uint16_t),
        TUYA_BULB_TYPE_ATTRIBUTE: ("bulb_type", TuyaBulbType), # 0xFC02
        TUYA_MIN_LEVEL_ATTRIBUTE: ("manufacturer_min_level", t.uint16_t), # 0xFC03
        TUYA_MAX_LEVEL_ATTRIBUTE: ("manufacturer_max_level", t.uint16_t), # 0xFC04
    }

    manufacturer_server_commands = {
        TUYA_CUSTOM_LEVEL_COMMAND: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

@MattWestb a few more Tuya attributes and a new server command for the inventory. Tuya devices are plenty of 🎁

@MattWestb
Copy link
Contributor

We is having one device that looks the same but is being one 10V dimer (its sending 0-10V to light and for the light level in PRO light systems) that is using the 0xfc00 attribute for min and max level and its verified working OK for the user but we have not getting one PR for it (I think hi cant doing so much coding and testing for the moment (UA) ;-/ #1149.
ZCL is having one attribute for min and one for max. If you have the knowledge "converting it to ZCL attribute its one great things for the user and was the reason its not making on PR then its more or less useless for normal users.

The attribute 0xfc03 Scr state (uint8) 0x00–0xFF (default 0x01) and 0xfc04 Current percentage (uint8) 0x00–0xFF (default 0x01) i have found in lates tuya dev docks but is the function not OK ??

The 0xF000 "tuya Level control" (TUYA_LEVEL_ATTRIBUTE) is one t.uint16_t is it having full range (0x0 - 0xffff) or is it limited ?

Is the command TUYA_CUSTOM_LEVEL_COMMAND having one number that i can putting in the matrix and also parameter (unit number and range) ?

And last but most important great work done !!!

@javicalle
Copy link
Collaborator

The attribute 0xfc03 Scr state (uint8) 0x00–0xFF (default 0x01) and 0xfc04 Current percentage (uint8) 0x00–0xFF (default 0x01) i have found in lates tuya dev docks but is the function not OK ??

Not for this device. According to Z2M (not tested yet here):

  • 0xF000: values 0-1000 (verified)
  • 0xFC03: values 0-1000 (min_level?)
  • 0xFC04: values 0-1000 (max_level?)

The 0xF000 "tuya Level control" (TUYA_LEVEL_ATTRIBUTE) is one t.uint16_t is it having full range (0x0 - 0xffff) or is it limited ?

Has been verified to get 0-1000 values. This is the reason we put t.uint16_t here.

Is the command TUYA_CUSTOM_LEVEL_COMMAND having one number that i can putting in the matrix and also parameter (unit number and range) ?

Yes, TUYA_CUSTOM_LEVEL_COMMAND = 0x00F0. I got this value (again) in Z2M
Its parameters are the TuyaLevelPayload:

  • level: t.uint16_t
  • transtime: t.uint16_t

And you can guess where this values came from...

@MattWestb
Copy link
Contributor

And you can guess where this values came from...

deCONZ ???
:-)))))))

I putting in the 0xF000 attribute and the 0x00F0 command and we can fixing the other then its being tested and perhaps its can having different functions (its still tuya devices).

@MattWestb
Copy link
Contributor

Do you knowing the unit (S / 1/4 S / 1/10 S ?) an the range for the 0x00F0 transtime: (t.uint16_t) ?
Not nervelessly but if you knowing i think its good pouting it in for reference and for future.

@javicalle
Copy link
Collaborator

Do you knowing the unit (S / 1/4 S / 1/10 S ?) an the range for the 0x00F0 transtime: (t.uint16_t) ?
Not nervelessly but if you knowing i think its good pouting it in for reference and for future.

No, I haven't found any clues about yhis attribute units.
Maybe @jonnylangefeld can do some tests by changing the transtime value from 0 to (for example) 1, 10, 100, 1000... to see if it gives us an idea of what is used for and its meaning (I guess it's about the transition time between values):

            return await super().command(
                TUYA_CUSTOM_LEVEL_COMMAND,
                TuyaLevelPayload(level=brightness, transtime=0),
                manufacturer=manufacturer,
                expect_reply=expect_reply,
                tsn=tsn,
            )

@jonnylangefeld
Copy link
Contributor Author

So I set the values from 0 to 100 and then also to 1000 and always restarted in between, but nothing changed. I was expecting for the dimmer to just "dim slower", but it was always the same.
I also read the values for man level and max level and they were 10 and 1000. So technically the scale has to adapt to that right?

I also noticed two more things:

  1. Whenever I set the slider in the UI to 100%, 1 second later it would jump to 99%.
    I noticed in the logs that it correctly translated the value to 1000, then there would be another log line saying it gets the brightness at 1000 and then another log line saying it would get the brightness at 996. The second get value is always 4 lower than the first.
  2. When I slide the UI slider to 100%, the argument value sent to the cluster command is always 254, not to 255.

Here the logs:

2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Sending Cluster Command. Cluster Command is 4, Arguments are (254, 1)
2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Setting brightness to 1000
2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Attribute report received: manufacturer_current_level=1000
2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Getting brightness 1000
2022-03-12 23:40:50 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0xA1EC:1:0x0008]: received attribute: 0 update with value: 255
2022-03-12 23:40:51 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Attribute report received: manufacturer_current_level=996
2022-03-12 23:40:51 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Getting brightness 996
2022-03-12 23:40:51 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0xA1EC:1:0x0008]: received attribute: 0 update with value: 254

I updated the PR, but feel free to change things around.

@javicalle
Copy link
Collaborator

javicalle commented Mar 17, 2022

The usual behavior of the devices is as follows:

  • you execute an action on the device
  • the device reports the new status (full or partial)

What you are seeing are the traces of those 2 events:

  • the dim value is changed to 1000
2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Sending Cluster Command. Cluster Command is 4, Arguments are (254, 1)
2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Setting brightness to 1000
2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Attribute report received: manufacturer_current_level=1000
2022-03-12 23:40:50 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Getting brightness 1000
2022-03-12 23:40:50 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0xA1EC:1:0x0008]: received attribute: 0 update with value: 255
  • the device reports that its new status is 996
2022-03-12 23:40:51 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Attribute report received: manufacturer_current_level=996
2022-03-12 23:40:51 DEBUG (MainThread) [zigpy.zcl] [0xa1ec:1:0x0008] Getting brightness 996
2022-03-12 23:40:51 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0xA1EC:1:0x0008]: received attribute: 0 update with value: 254

I would not modify the calculation of the dim value. As I've said before IMHO it's something that should be approached in a generic way and not device by device.

  • Min/max values only affects to the dim from the physical device so that the values always stay in the range min<max. I'm not sure about this, but probably dim from HA will not respect this min/max values.
    Most Z2M devices take these limits into account and allow replicate the behavior from the physical device.
    I have not seen it implemented in ZHA, but IMHO it is a topic to address in a more generic way and not for each device.

@jonnylangefeld Can you check if the cluster attributes manufacturer_min_level and manufacturer_max_level have any value for your device?

dmulcahey pushed a commit that referenced this issue Jul 8, 2022
* Add device support for TS110E

This device uses a non-default set level command.
Tested in local docker environment.
Thanks to @javicalle this was possible.
Fix #1415

* Lint

* Lint

* Update ts110e.py

* Update ts110e.py

* Update ts110e.py

* Update ts110e.py
@colombianlizard
Copy link

colombianlizard commented Jul 21, 2022

This one worked for my one gang with neutral lonsonho smart dimmers as intended but I have dimmer function on my 2 gang no neutral ones, they are both TS110E devices, is it possible to modify the quirk to make it compatible with those too?, also the dimming function from HA on the ones that work is going from around 20% to 80% from totally off to totally on, there is no difference between 0-20 and 80 to 100...
@javicalle
@jonnylangefeld

@colombianlizard
Copy link

Thid is the device signature for the two gang with no neutral ones (QS-Zigbee-D02-TRIAC-2C-L)

{
"node_descriptor": "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)",
"endpoints": {
"1": {
"profile_id": 260,
"device_type": "0x0101",
"in_clusters": [
"0x0000",
"0x0004",
"0x0005",
"0x0006",
"0x0008",
"0x0300",
"0xef00"
],
"out_clusters": [
"0x000a",
"0x0019"
]
},
"2": {
"profile_id": 260,
"device_type": "0x0101",
"in_clusters": [
"0x0004",
"0x0005",
"0x0006",
"0x0008",
"0x0300",
"0xef00"
],
"out_clusters": []
},
"242": {
"profile_id": 41440,
"device_type": "0x0061",
"in_clusters": [],
"out_clusters": [
"0x0021"
]
}
},
"manufacturer": "_TZ3210_4ubylghk",
"model": "TS110E",
"class": "zigpy.device.Device"
}

@Philje123
Copy link

Hi guys.

I rushed out and bought one of these on ebay without checking first they were compatible.

I'm having the same issues with not being able to dim them.

I've tried using the code above and putting it into a quirk but I get errors. It says that some of the code has been deprecated.

Is there another fix for this?

@Trzinka
Copy link

Trzinka commented Nov 24, 2023

Hello. I have https://www.zigbee2mqtt.io/devices/TS110E_2gang_2.html,
which does not directly know the state of the physical push switch (S1 or S2 state).
Is there any way to get the status information of (S1 or S2) in zigbee2mqtt and Home Assistant? Thank you

@xcooleex
Copy link

xcooleex commented Jul 21, 2024

Hi, I have the same exact model _TZ3210_ngqk6jia (TS110E) and tried to apply the quirk but its not working for me, its not working and Hass doestn even display the quirk. I have the quirk stored in config - custom_zha_quirks - ts110e.py

Im sorry if im being stupid, Im a little noob ;)

Here is the code I've inserted, I dont even know if I copied the right one, sorry!

TUYA_BULB_TYPE_ATTRIBUTE = 0xFC02
TUYA_MIN_LEVEL_ATTRIBUTE = 0xFC03
TUYA_MAX_LEVEL_ATTRIBUTE = 0xFC04
TUYA_CUSTOM_LEVEL_COMMAND = 0x00F0


class TuyaLevelPayload(t.Struct):
    """Tuya Level payload."""

    level: t.uint16_t
    transtime: t.uint16_t


class TuyaBulbType(t.enum8):
    """Tuya bulb type."""

    LED = 0x00
    INCANDESCENT = 0x01
    HALOGEN = 0x02


class F000LevelControlCluster(LevelControl):
    """LevelControlCluster that reports to attrid 0xF000."""

    manufacturer_attributes = {
        TUYA_LEVEL_ATTRIBUTE: ("manufacturer_current_level", t.uint16_t),
        TUYA_BULB_TYPE_ATTRIBUTE: ("bulb_type", TuyaBulbType), # 0xFC02
        TUYA_MIN_LEVEL_ATTRIBUTE: ("manufacturer_min_level", t.uint16_t), # 0xFC03
        TUYA_MAX_LEVEL_ATTRIBUTE: ("manufacturer_max_level", t.uint16_t), # 0xFC04
    }

    manufacturer_server_commands = {
        TUYA_CUSTOM_LEVEL_COMMAND: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

    def _update_attribute(self, attrid, value):
        super()._update_attribute(attrid, value)
        # update also `current_level` attribute
        if attrid == TUYA_LEVEL_ATTRIBUTE:
            # manufacturer reported values are 0-1000, convert to 0-255
            value = (value * 255) // 1000
            attrid = 0x0000
            super()._update_attribute(attrid, value)

    async def command(
        self,
        command_id: Union[foundation.Command, 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."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            return await super().command(
                TUYA_CUSTOM_LEVEL_COMMAND,
                TuyaLevelPayload(level=brightness, transtime=0),
                manufacturer=manufacturer,
                expect_reply=expect_reply,
                tsn=tsn,
            )

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants