Cover Controls freezing up
Hi, I'm using Tuya blinds motor model: LY-1690, version 3.4, battery powered with USB-C charging. WiFi + RF The issue I have encauntered is:
This code works perfectly:
import os
import tinytuya
import json
import subprocess
import time
DEVICE_ID = os.environ.get('DEVICE_ID')
DEVICE_IP = os.environ.get('DEVICE_IP')
LOCAL_KEY = os.environ.get('DEVICE_LOCAL_KEY')
VERSION = os.environ.get('DEVICE_VERSION')
DPS = 1
d = tinytuya.OutletDevice(DEVICE_ID, DEVICE_IP, LOCAL_KEY)
d.set_version(VERSION)
d.set_socketPersistent(False)
d.set_value(DPS, 'close')
data = d.status()
result = {
"state": data['dps']['1'],
"battery": data['dps']['13']
}
print(json.dumps(result))
But if we use
d = tinytuya.CoverDevice(DEVICE_ID, DEVICE_IP, LOCAL_KEY)
d.set_version(VERSION)
d.set_socketPersistent(False)
d.open_cover(switch=1)
data = d.status()
result = {
"state": data['dps']['1'],
"battery": data['dps']['13']
}
print(json.dumps(result))
the d.open_cover(switch=1) will sometimes work, sometimes not work. If we have for example close_blinds.py, open_blinds.py, status_blinds.py
The status_blinds.py will always work perfectly fine doesn't matter if it`s the 'CoverDevice; or 'OutletDevice' but if we call for example an open_blinds.py and it works, then after that calling close_blinds.py won't work. All of those use 'CoverDevice' and d.open/close_cover(switch=1) function. So there is a workorund for that but I just let you know there are some issues.
Hi @Demax121 - I see the issue. The open_cover() and close_cover() are using "on" and "off" as the state of the switch rather than "close" as in your example - here is the code:
def open_cover(self, switch=1, nowait=False):
"""Open the cover"""
self.set_status("on", switch, nowait=nowait)
def close_cover(self, switch=1, nowait=False):
"""Close the cover"""
self.set_status("off", switch, nowait=nowait)
def stop_cover(self, switch=1, nowait=False):
"""Stop the motion of the cover"""
self.set_status("stop", switch, nowait=nowait)
This is a legacy class that came over from pytuya and I don't have any devices to test. I would be interested in knowing how many devices use "close" instead of "off". It would be interesting to see what you get with something like this:
d = tinytuya.OutletDevice(DEVICE_ID, DEVICE_IP, LOCAL_KEY)
d.set_version(VERSION)
d.set_socketPersistent(False)
res = d.set_value(1, 'close')
print(f"using close = {res}")
res = d.set_value(1, 'off')
print(f"using off = {res}")
Hi @jasonacox I've tried your code, and have achieved interesting results
#Device configuration BEGIN
import os
import tinytuya
# Load device configuration from environment variables
DEVICE_ID = os.environ.get("DEVICE_ID")
DEVICE_IP = os.environ.get("DEVICE_IP")
LOCAL_KEY = os.environ.get("DEVICE_LOCAL_KEY")
VERSION = os.environ.get("DEVICE_VERSION")
# Initialize Tuya device
device = tinytuya.OutletDevice(DEVICE_ID, DEVICE_IP, LOCAL_KEY)
device.set_version(VERSION)
device.set_socketPersistent(False)
#Device configuration END
The first script is:
res = d.set_value(1, 'close')
print(f"using close = {res}")
res = d.set_value(1, 'off')
print(f"using off = {res}")
The second scripts is:
res = d.set_value(1, 'open')
print(f"using open = {res}")
res = d.set_value(1, 'on')
print(f"using on = {res}")
Results of the first script and device behaviour:
The covers has been initiali set to "OPEN" state.
using close = {'protocol': 4, 't': 466, 'data': {'dps': {'1': 'close'}}, 'dps': {'1': 'close'}}
using off = {'protocol': 4, 't': 466, 'data': {'dps': {'3': 12}}, 'dps': {'3': 12}}
The "CLOSE" command started closing the cover. The "OFF" command started opening the cover.
When I've tried firing the script second time it didn't work at all, I got the results, completely different from the first firing of the script:
using close = {'protocol': 4, 't': 1854, 'data': {'dps': {'108': 'AhE+4qZGIw=='}}, 'dps': {'108': 'AhE+4qZGIw=='}}
using off = {'protocol': 4, 't': 1855, 'data': {'dps': {'108': 'AhE+4qZGIw=='}}, 'dps': {'108': 'AhE+4qZGIw=='}}
the commands didn't do anything.
Results of the second script and device behaviour:
The covers has been initiali set to "CLOSE" state.
using open = {'protocol': 4, 't': 910, 'data': {'dps': {'1': 'open'}}, 'dps': {'1': 'open'}}
using on = {'protocol': 4, 't': 910, 'data': {'dps': {'3': 88}}, 'dps': {'3': 88}}
The "OPEN" command worked properly, the covers started to open. The "ON" command worked properly, didn't interupt the work of the "OPEN" command.
When I've fired the script the second time I have recived those results:
using open = {'protocol': 4, 't': 2139, 'data': {'dps': {'1': 'open'}, 'type': 'query'}, 'dps': {'1': 'open'}}
using on = None
After using the second scipt the covers were closed, and I could once again use the second script properly and results were even more interesting:
The first firing of the script, it has worked exaclty as the first firing first covers on "CLOSE" command started closing, then on "OFF command started openning:
using close = None
using off = {'protocol': 4, 't': 2402, 'data': {'dps': {'3': 11}}, 'dps': {'3': 11}}
The second firing of the script, worked exactly the same first covers start to close, then start to open.
using close = {'protocol': 4, 't': 2423, 'data': {'dps': {'1': 'close'}}, 'dps': {'1': 'close'}}
using off = {'protocol': 4, 't': 2424, 'data': {'dps': {'3': 12}}, 'dps': {'3': 12}}
The third firing of the script, didn't work at all just gave the results
using close = {'protocol': 4, 't': 2430, 'data': {'dps': {'3': 0}}, 'dps': {'3': 0}}
using off = {'protocol': 4, 't': 2434, 'data': {'dps': {'108': 'AhE+4qZGIw=='}}, 'dps': {'108': 'AhE+4qZGIw=='}}
Between firings I have waited for 5 second just ot be sure it's not bugging out because of commands spam.
I don't know how many devices use 'OPEN' and 'CLOSE" commands as I only have one blinds motor but I will try to go around discord and my friends to find out more.
It looks to me like it does not understand on/off and simply treats anything it does not understand as open. We might have to add a status request similar to BulbDevice or IRRemoteDevice so it can auto-detect on/off vs open/close, assuming on/off has ever worked for any device.
I agree @uzlonewolf, we could poll for status at init and if we see "open/close" vs "on/off", select cover type to use "open/close".
If you want to test this change:
# Uninstall current version (if installed)
pip uninstall tinytuya -y
# Install directly from the v1.17.5 branch
pip install git+https://github.com/jasonacox/[email protected]
I have tested the changes and come with the results.
First I've checked using my earlier scripts blinds state:
$ python blinds_status.py
{"state": "open", "battery": 90}
Then I`ve run the script for CLOSE/OFF state
res = d.set_value(1, 'close')
print(f"using close = {res}")
res = d.set_value(1, 'off')
print(f"using off = {res}")
$ python test1.py
using close = {'protocol': 4, 't': 2434, 'data': {'dps': {'1': 'close'}}, 'dps': {'1': 'close'}}
using off = {'protocol': 4, 't': 2435, 'data': {'dps': {'3': 12}}, 'dps': {'3': 12}}
Then I've checked blinds status again:
$ python blinds_status.py
{"state": "open", "battery": 90}
Nothing changed, works just like the previous implementation, the close starts closing the blinds then off starts opening the blinds. The diffrence is that it doesn't get stuck anymore.
Then I've used again my previous script to close the blinds:
$ python blinds_close.py
{"state": "close", "battery": 90}
And used again a script this time for OPEN/ON state
res = d.set_value(1, 'open')
print(f"using open = {res}")
res = d.set_value(1, 'on')
print(f"using on = {res}")
$ python test1.py
using open = {'protocol': 4, 't': 2525, 'data': {'dps': {'1': 'open'}}, 'dps': {'1': 'open'}}
using on = {'protocol': 4, 't': 2525, 'data': {'dps': {'3': 88}}, 'dps': {'3': 88}}
The blinds have succesfuly gone from close to open state.
$ python blinds_status.py
{"state": "open", "battery": 80}
One of my ideas to fix it and add a little bit more complex implementation would be to add a function that a user can call.
If user has IoT Core API added to their projects it could be done like that:
First function would make the API call for Query Device Details using the device ID, using this function we colud fetch the local key.
It will give us a lot of information, the result looks like this:
{
"result": [
{
"active_time": 1759954208,
"bind_space_id": "...",
"category": "cl",
"create_time": 1752909092,
"custom_name": "",
"icon": "...",
"id": "...",
"ip": "...",
"is_online": false,
"lat": "...",
"local_key": "...",
"lon": "...",
"model": "外置wifi",
"name": "Blinds Motor",
"product_id": "...",
"product_name": "百叶帘电机",
"sub": false,
"time_zone": "+02:00",
"update_time": 1759954210,
"uuid": "..."
}
],
"success": true,
"t": 1760299931146,
"tid": "..."
}
I have swaped the sensitive data with ...
Second function would be calling Get the instruction set of the device using the device ID from this we can get an instrucion set that device uses.
For my blinds motor it looks like this:
{
"result": {
"category": "cl",
"functions": [
{
"code": "control",
"desc": "control",
"name": "control",
"type": "Enum",
"values": "{\"range\":[\"open\",\"stop\",\"close\",\"continue\"]}"
},
{
"code": "percent_control",
"desc": "percent control",
"name": "percent control",
"type": "Integer",
"values": "{\"unit\":\"%\",\"min\":0,\"max\":100,\"scale\":0,\"step\":1}"
}
]
},
"success": true,
"t": 1760299825230,
"tid": "..."
}
Then extract the key and value pairs into single JSON file which we can use to call in functions because it will contain instruction set, local_key, device_id, the rest of the things like local ip and protocol version would have to be set by user.
Hi @Demax121 , I'm sorry I wasn't clear. The fix was to make the CoverDevice class functions open_cover() and close_closer() work without doing the direct DPS settings. In other words, hopefully something like this would work:
d = tinytuya.CoverDevice(DEVICE_ID, DEVICE_IP, LOCAL_KEY)
d.set_version(VERSION)
# close it
d.open_cover(switch=1)
data = d.status()
print(data)
input("Press Enter")
# open it
d.close_cover(switch=1)
data = d.status()
print(data)
Based on the cover devices supported in tuya-local, "open", "close", "stop" and often "continue" are by far the most common strings used for covers. There are some early devices that use other strings like "0", "1" and "2", maybe also "on" and "off" (I guess that came from somewhere even if I don't recall seeing it), but they seem to have standardised things now.
FYI, here is a "poll" of values used by various cover devices (continue has been left out of many of these even though the device supports it, because Home Assistant does not, but I do not recall seeing a "continue" option paired with anything other than "open", "closed", "stop"):
abalon_bcm700d_curtain.yaml: ['ZZ', 'FZ', 'STOP']
agl_ultracontato.yaml: [True, False]
agl_ultramagic_lock.yaml: [True, False]
am24_blind_motor.yaml: ['open', 'stop', 'close', 'continue']
avatto_cls02_curtainduallights.yaml: ['open', 'close', 'stop']
avatto_curtain_duallights.yaml: ['open', 'close', 'stop']
avatto_curtain_light.yaml: ['open', 'close', 'stop']
avatto_curtain_switch.yaml: ['open', 'close', 'stop']
avatto_roller_blinds.yaml: ['open', 'close', 'stop']
benexmart_blind_motor.yaml: ['open', 'close', 'stop']
ccb11_blind_controller.yaml: ['open', 'close', 'stop']
cc_curtain.yaml: [1, 2, 3]
cover_switch_with_backlight.yaml: ['open', 'close', 'stop']
currysmarter_6gen_rollershutter.yaml: ['open', 'close', 'stop']
curtain_with_feedback.yaml: ['open', 'close', 'stop']
dongguan_electric_curtain.yaml: ['open', 'close', 'stop']
dongguan_garage_door_opener.yaml: [True, False]
dooya_curtain.yaml: ['open', 'close', 'stop']
eruiklink_curtains.yaml: ['open', 'close', 'stop']
etersky_curtain_switch.yaml: ['open', 'close', 'stop']
fs_03w_curtain.yaml: ['open', 'close', 'stop']
garage_door_opener.yaml: [True, False]
graywind_shades.yaml: ['open', 'close', 'stop']
gw_motor_roller_blind.yaml: ['0', '1', '2']
kimex_cinemascreen.yaml: ['open', 'stop', 'close']
kogan_garage_opener.yaml: ['fopen', 'fclose']
ky_35w10_shutter.yaml: ['open', 'close', 'stop']
loonas_curtain.yaml: ['open', 'close', 'stop']
loratap_curtain_switch.yaml: ['']
loratap_curtain_switch_QCSC400ZB-V2.yaml: ['open', 'close', 'stop']
loratap_garage_door.yaml: [True, False]
loratap_wifi_curtain_switch_double.yaml: ['open', 'close', 'stop']
loratap_wifi_curtain_switch_double.yaml: ['open', 'close', 'stop']
loratap_zigbee_curtain.yaml: ['open', 'close', 'stop']
ls830ty_curtain.yaml: ['open', 'close', 'stop']
m027_curtain.yaml: ['open', 'close', 'stop']
m515_curtain_motor.yaml: ['open', 'stop', 'close']
moes_double_curtainswitch.yaml: ['open', 'close', 'stop']
moes_double_curtainswitch.yaml: ['open', 'close', 'stop']
moes_touch_curtain_switch.yaml: ['open', 'close', 'continue']
moes_wsyeuc_curtainswitch.yaml: ['on', 'off', 'stop']
outdoor_inc_zipblind.yaml: ['open', 'close', 'stop']
position_blinds.yaml: ['open', 'close', 'stop']
qs_c01_curtain.yaml: ['open', 'close', 'stop']
qs_c02_curtain.yaml: ['open', 'close', 'stop']
qs_c02_curtain.yaml: ['open', 'close', 'stop']
safe_conn09_barrier.yaml: [True, False]
sherko_curtain.yaml: ['open', 'close', 'stop']
simple_blinds.yaml: ['open', 'close', 'stop']
simple_gate_opener.yaml: [True, False]
smart_motorized_pop_up_socket.yaml: ['open', '']
steigen_solarpro_dryer.yaml: ['up', 'down', 'stop']
wistar_roller_blind.yaml: ['0', '1', '2']
wistar_roller_blind_nopos.yaml: ['0', '1', '2']
ZC34T-03-3A_swing_arm_opener.yaml: ['open', 'close', 'stop']
zemismart_am25_rollerblind.yaml: ['open', 'stop', 'close', 'continue']
zemismart_curtain.yaml: ['open', 'close', 'stop']
zemismart_roller_shade.yaml: ['open', 'close', 'stop', 'continue']
zemismart_roller_shade_zm25r2.yaml: ['open', 'close', 'stop', 'continue']
zemismart_zm85el1x_rollershade.yaml: ['open', 'close', 'stop']
That is great data @make-all!
From that, we can see 8 different classes:
- Type 1:
["open", "close", "stop", "continue"]- Most curtains, blinds, roller shades - Type 2:
[true, false]- Simple relays, garage doors, locks - Type 3:
["0", "1", "2"]- String-numeric position/state - Type 4:
[1, 2, 3]- Integer-numeric position/state - Type 5:
["fopen", "fclose"]- Directional binary (no stop) - Type 6:
["on", "off", "stop"]- Switch-lexicon (default) - Type 7:
["up", "down", "stop"]- Vertical-motion (lifts, hoists) - Type 8:
["ZZ", "FZ", "STOP"]- Vendor-specific (Abalon-style)
I added a handler for that in the CoverDevice class.
Looking at the actual config, the type 4 is actually string type, and should be prefixed with 0, so ["01", "02", "03"], the contributor forgot to add quotes to get them interpreted correctly in the yaml.
["ZZ", "FZ", "STOP"] seems to be the older standard, and is probably second most common after ["open", "close", "stop", "continue"], followed by ["1", "2", "3"]. It might not be immediately apparent from the list above, because when devices follow a standard dps layout, a single config can support many devices. The rest are odd variations that often only appear in a single device. Most common dp id for this is 1, followed by 101. 4 is commonly used for the second curtain of a dual curtain device (2 and 3 are commonly position write and read, and likewise 5 and 6 for the second curtain, with other config dps and timers etc starting from 7 onward)
Thanks @make-all - Good find. I hope I captured it in the update.