Example #1
0
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
Example #2
0
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