ZHA Curtains module calibration


I bought Zigbee (Tuya) curtain (shutter) modules. But there is no way to calibration the up/down time with ZHA.

Is it possible to add service to start/stop calibration of the module ?


Request for device support for ZHA must be done here:

This does not sound like device request but instead cover setting/configuration to set top + bottom stop?

Yes maybe already done with a quirks. So needs to be set in « Manage clusters » on the device page

This is about the Zigbee Curtain Module QS-Zigbee-C01, also in Home Assistant recognised as Tuya TS130F.
What I did to make it work without a lot of hassle in a ZHA integration is using Clusters (go to Configuration > Devices > the device > Manage Clusters) and to select the

TuyaCoveringCluster (Endpoint id: 1, Id: 0x0102, Type: in)

and the

Cluster Attribute Calibration_time (id: 0xf003)

Find out how many second your motor needs for the full movement and multiply x 10, so the value is in 0.1s increments. Click Set Zigbee Attribute to confirm and see how it works out and finetune if necessary. The percentage scale will now work and reflect the true movement percentage.

If you like to do it another way, you can also use the module’s calibration function, which can be invoked by setting the Value at

calibration (id: 0xf001)

To 0. Yes zero! Very odd, but this is how is is apparently.
Then go to the controls of the entity and engage each direction till you are happy with the desired endpoints. Then go back to the Cluster setting and set

calibration (id: 0xf001)

back to 1 to store the calibration. You can now check in the

Cluster Attribute Calibration_time (id: 0xf003)

the calculated total motor time to get from 0 to 100 %, which needs to be divided by 10 to get seconds.
And if needed,

motor_reversal (id: 0xf002)

also works.
Value 1 is reversed, 0 is default (I needed to reverse).
Regularly check with Get Zigbee Attribute to know what you are doing and also what Home Assistant did. I noticed that after a while the Cluster window sometimes needs to be closed and opened in order to correctly work with the values again, so this is a bit “delicate” process.
Remember every time to delete the hexadecimal result and just enter decimal here.

Regarding build quality, the module is pretty decent.
The casing might be a bit cheapish but the clip which fits on a DIN rail is a nice extra and also helps to prevent that the back plate would come off. Obviously opening it did not contribute to the tightness of the backplate cover once put all back together.
Inside there are 2 pretty beafy relais rating 10A, which even supercedes the 3A spec on the case, soldered directly on the surface board. In another brand/model Curtain Module I saw way smaller relais and those were mounted on a secondary surface board and just with a small drop of solder as the 90 degree attachment which I found a bit of a disturbing design flaw considering 230VAC motors are controlled by it.


Thank you @basmeijer for this pristine explanation

YES, Thank you for this super explanation.

I have the problem that I don’t have the cluster- attribute F001 in my selection
so i’ve written a script, but unfortunately this doesn’t work
can you help me?

WindowCovering (Endpoint id: 1, Id: 0x0102, Type: in)

alias: xxx

service: zha.set_zigbee_cluster_attribute
ieee: “04:cd:15:ff:fe:06:69:66”
endpoint_id: 1
cluster_id: 258
attribute: 61441
cluster_type: “in”
value: 0

Thanks for the information. I have some of the loratap devices with ts130f.

There is an issue on my side. My shutter motors take longer up than going down. And I have set for example the calibration_time to 320.

What happens is:
100% is fully opened in HA

  • Set position to 60% → Shutter goes down to position 60%
  • Set position to 100% → Shutter goes up to 90-95% and thinks it is 100%. Since it takes longer going up.

Only solution is to close fully the cover and open again. Than it opens 100% and displays 100% as well.

Does anybody know a solution how to solve this? Or any link to a solution?


Hi Guys,

I have the same problem.
When the curtain is open, I press down button and stop it after 5 sec for example. Then I press UP and the curtain is not completely open. (Because the curtain is faster when go down).

How to solve this issue or any workaround?


1 Like

Really great!! Thank you so much

Hello, I have the same problem, no new idea about it ?
Did someone find a solution ?

I’ve seen the cluster : Acceleration_time_lift, could be part of the solution ? The value is “none” and I didn’t manage to change it :frowning:


Hello @basmeijer , I was successfull to set Cluster Attribute Calibration_time (id: 0xf003) for on device but not for the second. It seems that it is read only, The valure remanins to the default of 100. Any idea to unlock the variable ?


Still no solution for this problem ?
Do you know if this can help : Custom Component: Cover Time Based

Thanks !

Hi @Eden91, @edecae, @alaturqua,
without changing or hacking the zigbee firmware we can not solve this in the actor himself. (If you use or replace it with a wifi version and flash it with esphome you can do it - but my try to flash a wifi device was not successful…)
But we can solve it on server side with home assistant.

I have a workaround for you:
Every time the cover is complete up or down I change the calibration time via script and automation.
The script has two params: ieee (the zigbee device id) and deciseconds (e.g. 100 for 10 Seconds)


alias: Rolladen Zeit setzen
  - service: zha.set_zigbee_cluster_attribute
      ieee: "{{ ieee }}"
      endpoint_id: 1
      cluster_id: 258
      cluster_type: in
      attribute: 61443
      value: "{{ deciseconds }}"
    example: 84:71:27:ff:fe:cf:d2:32
    example: 250
mode: parallel
icon: mdi:window-shutter-alert
max: 10

Automation is closed → set uptime:

alias: Rolladen Zeit einstellen hoch
description: ""
  - platform: numeric_state
    entity_id: cover.hwr
    attribute: current_position
    below: 1
      hours: 0
      minutes: 1
      seconds: 0
      ieee: a4:c1:38:ad:2e:84:0e:78
      deciseconds: 215
condition: []
  - service: script.rolladen_zeit_setzen
      ieee: "{{ ieee }}"
      deciseconds: "{{ deciseconds }}"
mode: single

Automation is opened → set downtime:

alias: Rolladen Zeit einstellen runter
description: ""
  - platform: numeric_state
    entity_id: cover.hwr
    attribute: current_position
    above: 99
      hours: 0
      minutes: 1
      seconds: 0
    enabled: true
      ieee: a4:c1:38:ad:2e:84:0e:78
      deciseconds: 195
condition: []
  - service: script.rolladen_zeit_setzen
      ieee: "{{ ieee }}"
      deciseconds: "{{ deciseconds }}"
mode: single

You have to change the params for your case.
The only disadvantage in this solution is that we write every final curtain move in the zigbee memory. I`m not sure that the zigbee memory chip likes this many write cycles…
Has anyone an idea how many write cycles are allowed or planed in this devices?


I believe that im having a similar problem to @edacae where the Cluster: calibration_time (id: 0xf003) is set to 450 by default when i try to override it with 250 for example. The value changes to ‘None’ and after few minutes it goes back to 450.
it seems that it is not possible to override the cluster value for some reason. any other method to force override the value.


1 Like

Thanks! This really helped!

I have the same issue. I can’t update any attribute in my Tuya OXT Curtains modules.
I can read the default value like @Hakbar: in my case the calibration_time is 100 by default. Once I change it and try to read it I get None, and after some time it’s 100. I could not enable calibration (id: 0xf001) - initially it has value: enum8.undefined_0x01. Once I try to update it to 0 I receive None value. I cannot calibrate my curtains with ZHA. Reconfiguration of the curtains module does not change anything.

Home Assistant 2022.12.8
Supervisor 2022.12.1
Operating System 9.4

While trying to set new calibration_time a following error is in the logs:
Logger: zigpy.zcl
Source: components/zha/core/device.py:658
First occurred: 09:01:46 (1 occurrences)
Last logged: 09:01:46

[0x85C2:1:0x0102] Failed to convert attribute 0xF003 from (<class ‘str’>) to type <class ‘zigpy.types.basic.uint16_t’>: invalid literal for int() with base 10: ‘’


I can confirm the issue @Hakbar and @RafalC reported. My Tuya based curtain modules (brand is ‘Moes’) shows the same behaviour. Pairing works perfectly fine. The modules are identified by ZHA as ‘Tuya TS130F’ and the commands to move up and down also work right out of the box. But trying to change ANY parameters in the configuration results in errors and no changes are written to the devices.

Just for reference my set up is:
Home Assistant 2022.12.0
Running from a docker container on Arm v7 hf

And the device is identified as:
manufacturer: _TZ3000_1dd0d5yi
model: TS130F
name: _TZ3000_1dd0d5yi TS130F
quirk_class: zhaquirks.tuya.ts130f.TuyaTS130FTI2
manufacturer_code: 4098

There is however a workaround that’s in fact so easy I’m a little embarassed not to have thought of this earlier. While ZHA fails to write the configuration, deConz does without a problem. So as long as you use a Conbee or similar, you’re good to go.

  1. Take the Conbee out of your usual Home Assistant server and plug it into your favorite x86 laptop. Mac might also work, but I can’t try it.
  2. Install deConz
  3. Then open the phoscon web GUI and pair just the curtain module. You can skip all other devices.
  4. Go to the local deConz installation. There select the module and enter all the configuration you want.
  5. Now return the Conbee to your main Home Assistant and pair the module again. It now has the configuration and is ready to go.

While this works with only little additional effort, I do hope ZHA also gets fixed soon to fully support these type of modules.

1 Like

Thanks a lot, very helpful! Please allow me a further question, if I want change the calibration to 0 it’s doesn’t work, always shows „None“

Do I something wrong?