Updating the status of TuyaDevice using the response of the set_dps() call

This commit is contained in:
rospogrigio
2020-09-28 14:56:55 +02:00
committed by rospogrigio
parent d9c6d66bb7
commit 3fc19da259
2 changed files with 67 additions and 6 deletions

View File

@@ -89,13 +89,18 @@ class TuyaDevice:
raise ConnectionError("Failed to update status .")
def set_dps(self, state, dps_index):
# _LOGGER.info("running def set_dps from cover")
"""Change the Tuya switch status and clear the cache."""
self._cached_status = ""
self._cached_status_time = 0
# _LOGGER.info("running def set_dps from TuyaDevice")
# No need to clear the cache here: let's just update the status of the changed dps as returned by the interface (see 5 lines below)
# self._cached_status = ""
# self._cached_status_time = 0
for i in range(5):
try:
return self._interface.set_dps(state, dps_index)
result = self._interface.set_dps(state, dps_index)
self._cached_status["dps"].update(result["dps"])
# NOW WE SHOULD TRIGGER status_updated FOR ALL ENTITIES INVOLVED IN result["dps"] :
#for dp in result["dps"]:
# have status_updated() called....
return
except Exception:
print(
"Failed to set status of device [{}]".format(
@@ -116,7 +121,7 @@ class TuyaDevice:
self._lock.acquire()
try:
now = time()
if not self._cached_status or now - self._cached_status_time > 15:
if not self._cached_status or now - self._cached_status_time > 10:
sleep(0.5)
self._cached_status = self.__get_status()
self._cached_status_time = time()

View File

@@ -253,6 +253,62 @@ class TuyaInterface:
"""
return self.exchange(SET, {str(dps_index): value})
def _decode_received_data(self, data, is_status):
"""Return device status."""
"""is_status may be True (result of a status request) or False (result of a set_dps request)"""
result = data[20:-8] # hard coded offsets
if self.dev_type != "type_0a":
result = result[15:]
elif not is_status:
result = result[15:]
log.debug("Decrypting %r :", result)
# result = data[data.find('{'):data.rfind('}')+1] # naive marker search,
# hope neither { nor } occur in header/footer
# print('result %r' % result)
if result.startswith(b"{"):
# this is the regular expected code path
if not isinstance(result, str):
result = result.decode()
result = json.loads(result)
elif result.startswith(PROTOCOL_VERSION_BYTES_31):
# got an encrypted payload, happens occasionally
# expect resulting json to look similar to:
# {"devId":"ID","dps":{"1":true,"2":0},"t":EPOCH_SECS,"s":3_DIGIT_NUM}
# NOTE dps.2 may or may not be present
result = result[len(PROTOCOL_VERSION_BYTES_31) :] # remove version header
# remove (what I'm guessing, but not confirmed is) 16-bytes of MD5
# hexdigest of payload
result = result[16:]
cipher = AESCipher(self.local_key)
result = cipher.decrypt(result)
#print("decrypted result=[{}]".format(result))
log.info("decrypted result=%r", result)
if not isinstance(result, str):
result = result.decode()
result = json.loads(result)
elif self.version == 3.3:
# results of a set_dps request must have a further offset
cipher = AESCipher(self.local_key)
result = cipher.decrypt(result, False)
log.debug("decrypted result=%r", result)
if "data unvalid" in result:
self.dev_type = "type_0d"
log.info(
"'data unvalid' error detected: switching to dev_type %r",
self.dev_type,
)
return self.status()
if not isinstance(result, str):
result = result.decode()
result = json.loads(result)
else:
log.error("Unexpected status() payload=%r", result)
return result
def detect_available_dps(self):
"""Return which datapoints are supported by the device."""
# type_0d devices need a sort of bruteforce querying in order to detect the