jasonacox / tinytuya

Python API for Tuya WiFi smart devices using a direct local area network (LAN) connection or the cloud (TuyaCloud API).
MIT License
867 stars 157 forks source link

how can I get if set_value was successfull without recall status. #480

Open Titriel opened 3 months ago

Titriel commented 3 months ago

Hi, I am on creating an universal MQTT- Adapter. I was able to add your tinytuya class in my Adapter, and it works fine. Some devices are hard on the border of the range of my WIFI. So it would be helpfull, that calls like set_value, sends back if it was successtull sending data to the device. Or is there a way how I can requst this state ?

Thank You

jasonacox commented 2 months ago

Hi @Titriel - Can you give an example of your code? I think what you want is something like:

import tinytuya

# Connect
d = tinytuya.OutletDevice(id, ip, key, version="3.3")

# Set value of DPS index 25
d.set_value(25, 'xyz')

# Read value
data = d.status()
setting = data['dps']['25']
print(setting)
Titriel commented 2 months ago

@jasonacox Thank you very mutch for your answer. But this is that what I not want do do. Because I have to sent a second request to the device. I have seen, it is possibel to do settings about retrys by setting up the OutletDevice eg. So I think you had figured out a posibility to check if a retry is needed or not. I think, if a request to the device ist sucsessfully, you stop retreying. Or did you only send the request n times in the maner of fire and forget ? If you do fire and forget, that what I request to you is not posible. But if the device sands in any way an aknowlage back to an request, it should be posible eg. to return true by the call d.set_value if the device had aknowlaged and false if it has not aknowlaged over all retrys. If that will be posible, it makes sence do do this kind of return by all devices requsting calls.

Sorry for my terible english, it's not my native language.

jasonacox commented 2 months ago

Hi @Titriel - the commands wait for a response by default. You would need to set nowait=True for it to send only without waiting for a ACK response.

set_value(index, value, nowait=False) 

The set_value() function calls __send_receive(() which has logic to retry up to the socketRetryLimit amount and as long as the retry setting is set globally (default is True).

For your use case, you just need to make sure you get a valid response:

response = d.set_value("1",True)
# Should get eomething like: {'devId': 'abcdefg1234567', 'dps': {'1': True}, 't': 1712501649}
if "dps" in response:
    print("Updated")
else:
    print("Failed")

If you are curious, you can see how TinyTuya handles it in the core code here:

https://github.com/jasonacox/tinytuya/blob/8971b802d5e471776eab0bb2d7e922d2878f64d8/tinytuya/core.py#L1141-L1280

Titriel commented 2 months ago

Thank you very mutch. I check it out.

uzlonewolf commented 2 months ago

Unfortunately TinyTuya doesn't really handle command success/fail well. This is a good candidate for an enhancement. Let's take a step back and look at all the possible command results:

  1. Device completely ignores command and nothing is received
  2. Device completely ignores command but, by coincidence, an unrelated async update is received
  3. Device rejects command with an error message
  4. Device rejects command without an error message and nothing else is received
  5. Device rejects command without an error message and, by coincidence, an unrelated async update is received
  6. Device accepts command but nothing else is received
  7. Device accepts command and, either by coincidence or as a result of the command, an async update is received

The results for each might be unexpected: 1 will return an ERR_JSON object after the retry limit is reached. 2/5/7 will be counted as "success" and return that update. 3 will return an ERR_JSON about decoding the response. 4/6 will be counted as "success" and return None.

Instead of the above, we should probably be checking the retcode and immediately return an ERR_JSON if it's bad. Making sure we get a good retcode for the command we sent would be an additional check but brings up the issue of what to do with async updates received before the command response.

For now the only way to make sure we have success is to do something like:

d.set_value("1", True, nowait=True)

d.set_retry(False)
resp = d._send_receive(None, decode_response=False)
while resp and resp.cmd != [sent command]:
    resp = d._send_receive(None, decode_response=False)
d.set_retry(True)

if resp and resp.retcode == 0:
    print('Success!')
else:
    print('Failed!')

Unfortunately there's no good way of getting the exact command sent as it can be remapped based on the device quirks. Another TinyTuya enhancement could be to return the exact TuyaMessage sent when nowait=True.