jasonacox / tinytuya

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

Wizard doesn't pull local_key for some devices #305

Closed jasonacox closed 1 year ago

jasonacox commented 1 year ago

Problem

Oh, and for some reason that first hub I got didn't want to show the local key via the wizard. I had to get it via the "pretend to be the app" method.

The hub that I just picked up, eMylo 5.0 Bluetooth&3.0 ZigBee Gateway 3 In 1 (v3.4 device, Firmware: Main Module: v1.4.8 ZigBee Module: v2.4.1), has the same issue. Wizard didn't pick up the local key (not in tuya-raw), but I was able to get it the "app" way as you mention. I looked at the "result" raw from the cloud.getdevices() call and there is no local_key.

jasonacox commented 1 year ago

Solution 1

I was able to get the local_key using the https://openapi.tuyaus.com/v1.1/iot-03/devices/{DeviceID} call.

import tinytuya
c = tinytuya.Cloud()

# fetch device with local_key
device = c._tuyaplatform(ver='v1.1', uri='iot-03/devices/xxxxxx')

We could add a loop to Wizard to try to poll the individual devices that are missing their local_key. I also wonder if there is an updated v1.1 way to fetch all devices (currently using /v1.0/iot-01/associated-users/devices).

Solution 2

Using the Tuya IoT Cloud "API Explorer" I'm able to get the local_key for my gw device, but getdevices() is not getting it. The "API Explorer" curl call that works from the Explorer does show the local_key:

curl --request GET "https://openapi.tuyaus.com/v1.0/users/{USER}/devices"
--header "sign_method: HMAC-SHA256"
--header "client_id: {API_KEY}"
--header "t: 1679268929387"
--header "mode: cors"
--header "Content-Type: application/json"
--header "sign: {SIGNATURE}"
--header "access_token: {TOKEN}"

As @uzlonewolf pointed out:

getdevices() uses /v1.0/iot-01/associated-users/devices which is under the API Explorer as "Smart Home Device System" -> "Device Management" -> "Batch query for the list of associated App user dimension devices" and for me is not returning the local key either.

Originally posted by @uzlonewolf in https://github.com/jasonacox/tinytuya/issues/302#issuecomment-1475467857

The Wizard was recently updated to start using /v1.0/iot-01/associated-users/devices. This allowed us to bypass asking the user for an exmaple DeviceID. However, if I switch back to using the old method which calls /users/{USER}/devices, I get all of the local_keys.

import tinytuya 
c = tinytuya.Cloud()
example_id = 'xxxxx'
uid = c._getuid(example_id)
uri = 'users/%s/devices' % uid
json_data = c._tuyaplatform(uri)

The json_data payload contains all the devices and all the local_keys, including the one in my Problem example above.

Solution "Both Cakes?"

The old code is still in the Wizard (actually Cloud.py) but is disabled with use_old_device_list=False. My suggestion is to update the logic:

  1. Add the option of prompting the user for a DeviceID (not required). Update getdevices() to look to see if apiDeviceID has a deviceID and if so, use Solution 2 method to pull the devices.
  2. If apiDeviceID is empty, use the new method to pull the list of devices. However, add a loop through the devices to detect any that are missing a local_key and run _tuyaplatform(ver='v1.1', uri='iot-03/devices/{deviceID}') to fetch the missing key.
  3. One could argue that we only need to do the 2nd step above to get to the full outcome. :)
jasonacox commented 1 year ago

Ok, as usual, I may be way-over-thinking this... I was able to fix it with one line change:

image
uzlonewolf commented 1 year ago

I was planning on coding up a slightly different solution: use /v1.0/iot-01/associated-users/devices as we're doing now, and if any are missing their local keys then dump those IDs using one of the other calls. I'm still going through the list of URIs to try and find a good one to use.

jasonacox commented 1 year ago

Makes sense... please continue. My "easy fix" solution has a problem. The problem with needing the exampleID is that if you ever remove that device (which I have), suddenly Wizard stops working. The associated-user method seems cleaner (with the obvious exception of it having a defect of not always sending all local_kesy). I do like the workaround of looping through and fixing the missing keys.

bnazari commented 1 year ago

I've been digging as well. Looks like the only way you can get a UID is by having a DeviceID, which gets us back to the same place.

uzlonewolf commented 1 year ago

I was hoping to use the "Get the information about multiple devices" API call (/v1.0/iot-03/devices?device_ids=a,b,c,d) but it's giving me a "permission denied" no matter what device IDs I use. As of right now I'm leaning towards using /v1.0/iot-01/associated-users/devices to get a list of user IDs, and then using the newer "Get Device List" /v1.3/iot-03/devices?source_id=<user>&source_type=tuyaUser to get the actual device details. As a bonus this list also includes a "gateway_id" telling you what the parent device is.

bnazari commented 1 year ago

Not sure if this is helpful, but in the token?grant_type=1, there is a UID that is presented. But somehow that isn't the same UID that the devices produce.

uzlonewolf commented 1 year ago

Yeah, I noticed that earlier, but it's been rejected everywhere I've tried it. As I have multiple Smart Life accounts linked to my Developer account I have multiple uid's listed in the device list.

grep '"uid"' tuya-raw.json | sort | uniq
            "uid": "az1652######M",
            "uid": "az1676######r",