def check_data_frame(data, expected_prefix, encrypted=True): prefix = data[:15] suffix = data[-8:] if encrypted: payload_len = struct.unpack(">B",data[15:16])[0] # big-endian, unsigned char version = data[16:19] checksum = data[19:35] encrypted_json = data[35:-8] json_data = pytuya.AESCipher(LOCAL_KEY.encode(mock_byte_encoding)).decrypt(encrypted_json) else: json_data = data[16:-8].decode(mock_byte_encoding) frame_ok = True if prefix != pytuya.hex2bin(expected_prefix): frame_ok = False elif suffix[-4:] != pytuya.hex2bin("0000aa55"): # We only check for the trailing byte signature # We could extend the test to also check the CRC if we wanted. frame_ok = False elif encrypted: if payload_len != len(version) + len(checksum) + len(encrypted_json) + len(suffix): frame_ok = False elif version != b"3.1": frame_ok = False return json_data, frame_ok
def check_data_frame(data, expected_prefix, encrypted=True): prefix = data[:15] suffix = data[-8:] if encrypted: payload_len = struct.unpack( ">B", data[15:16])[0] # big-endian, unsigned char version = data[16:19] checksum = data[19:35] encrypted_json = data[35:-8] json_data = pytuya.AESCipher( LOCAL_KEY.encode('utf-8')).decrypt(encrypted_json) else: json_data = data[16:-8].decode('utf-8') frame_ok = True if prefix != pytuya.hex2bin(expected_prefix): frame_ok = False elif suffix != pytuya.hex2bin("000000000000aa55"): frame_ok = False elif encrypted: if payload_len != len(version) + len(checksum) + len( encrypted_json) + len(suffix): frame_ok = False elif version != b"3.1": frame_ok = False return json_data, frame_ok
def __update_status(self, Data): payload = Data[20:-8] if len(payload) == 0: Domoticz.Debug('Empty payload (probably a response to set)') return Domoticz.Debug('Got payload: ' + str(payload)) # try: if self.__version_id == 1: if payload.startswith(b'{'): # got plain text status response jsonstr = payload # sometimes thermostat send the same status payload twice (probably a bug) end = jsonstr.find(b'}}') if (end == -1): Domoticz.Error("Invalid payload received: " + str(Data)) return end = end + 2 jsonstr = jsonstr[:end] elif payload.startswith(pytuya.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 # remove version header payload = payload[len(pytuya.PROTOCOL_VERSION_BYTES_31):] # remove (what I'm guessing, but not confirmed is) 16-bytes of MD5 hexdigest of payload payload = payload[16:] cipher = pytuya.AESCipher(self.__device.local_key) # Payload is in base64 jsonstr = cipher.decrypt(payload) Domoticz.Debug('Decrypted result: ' + str(jsonstr)) else: Domoticz.Error( "Unknown payload, please try encrypted v3.3 protocol") return elif self.__version_id == 2: if payload.startswith(pytuya.PROTOCOL_VERSION_BYTES_33): # For 3.3 version protocol after setting dps there can be # a message that cannot be decrypted # Always starts with 33 2e 33 00 00 00 00 00 00 payload = payload[len(pytuya.PROTOCOL_VERSION_BYTES_33):] # remove (what I'm guessing, but not confirmed is) 16-bytes of MD5 hexdigest of payload payload = payload[16:] # the approach from v3.1 does not work # discard this payload for now return cipher = pytuya.AESCipher(self.__device.local_key) # Payload is in raw bytes, not base64 jsonstr = cipher.decrypt(payload, False) Domoticz.Debug('Decrypted result: ' + str(jsonstr)) else: Domoticz.Error('Unexpected status() payload=' + str(payload)) try: if not isinstance(jsonstr, str): jsonstr = jsonstr.decode() result = json.loads(jsonstr) Domoticz.Debug("Loaded: " + str(result['dps'])) except (JSONError, KeyError) as e: Domoticz.Error("Payload parse failed: " + str(jsonstr)) return # start = Data.find(b'{"devId') # if(start == -1): # # Domoticz.Error("Invalid payload received: " + str(Data)) # Domoticz.Debug("Got non dps response: " + # str(Data) + ", probably set response") # return # jsonstr = Data[start:] # end = jsonstr.find(b'}}') # if(end == -1): # Domoticz.Error("Invalid payload received: " + str(Data)) # return # end = end+2 # jsonstr = jsonstr[:end] # try: # result = json.loads(jsonstr) # Domoticz.Debug("Loaded: " + str(result['dps'])) # except (JSONError, KeyError) as e: # Domoticz.Error("Payload parse failed: " + jsonstr) # return if result['devId'] != self.__devID: Domoticz.Error("Invalid payload received for " + result['devId']) return if ((type(result['dps']) is dict) == False): Domoticz.Error("Invalid dps block: " + jsonstr) return try: if result['dps']['1']: UpdateDevice(self.__control_device, 1, "On") else: UpdateDevice(self.__control_device, 0, "Off") except KeyError: pass try: current_temp = str(round(result['dps']['2'] / 2, 1)) # TODO check which value shoud be set UpdateDevice(self.__thermostat_device, 0, current_temp) except KeyError: pass try: if result['dps']['4'] == "1": UpdateDevice(self.__mode_device, 10, "10") else: UpdateDevice(self.__mode_device, 20, "20") except KeyError: pass try: if result['dps']['6']: UpdateDevice(self.__lock_device, 20, "20") else: UpdateDevice(self.__lock_device, 10, "10") except KeyError: pass try: if result['dps']['5']: UpdateDevice(self.__eco_device, 20, "20") else: UpdateDevice(self.__eco_device, 10, "10") except KeyError: pass # builtin sensor reading try: current_temp = str(round(result['dps']['3'] / 2, 1)) UpdateDevice(self.__temp_device, 0, current_temp) except KeyError: pass # External sensor present try: current_temp = str(round(result['dps']['102'] / 2, 1)) UpdateDevice(self.__external_temp_device, 0, current_temp) except KeyError: pass