Skip to content

Commit eba92e2

Browse files
authored
Merge pull request #306 from uzlonewolf/cloud
Fetch the device list a 2nd time to make sure we have the local key
2 parents 54d650f + 7ec77ea commit eba92e2

File tree

2 files changed

+86
-16
lines changed

2 files changed

+86
-16
lines changed

tinytuya/Cloud.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
getfunctions(deviceid)
1919
getproperties(deviceid)
2020
getdps(deviceid)
21-
sendcommand(deviceid, commands)
21+
sendcommand(deviceid, commands [, uri])
2222
getconnectstatus(deviceid)
2323
getdevicelog(deviceid, start=[now - 1 day], end=[now], evtype="1,2,3,4,5,6,7,8,9,10", size=100, params={})
2424
-> when start or end are negative, they are the number of days before "right now"
@@ -213,6 +213,9 @@ def _tuyaplatform(self, uri, action='GET', post=None, ver='v1.0', recursive=Fals
213213
"POST: URL=%s HEADERS=%s DATA=%s", url, headers, body,
214214
)
215215
response = requests.post(url, headers=headers, data=body)
216+
log.debug(
217+
"POST RESPONSE: code=%d text=%s token=%s", response.status_code, response.text, self.token
218+
)
216219

217220
# Check to see if token is expired
218221
if "token invalid" in response.text:
@@ -307,17 +310,48 @@ def cloudrequest(self, url, action=None, post=None, query=None):
307310
action = 'POST' if post else 'GET'
308311
return self._tuyaplatform(url, action=action, post=post, ver=None, query=query)
309312

310-
def _get_all_devices(self):
313+
# merge device list 'result2' into 'result1'
314+
# if result2 has a device which is not in result1 then it will be added
315+
# if result2 has a key which does not exist or is empty in result1 then that key will be copied over
316+
def _update_device_list( self, result1, result2 ):
317+
for new_device in result2:
318+
if 'id' not in new_device or not new_device['id']:
319+
continue
320+
found = False
321+
for existing_device in result1:
322+
if 'id' in existing_device and existing_device['id'] == new_device['id']:
323+
found = True
324+
for k in new_device:
325+
if k not in existing_device or not existing_device[k]:
326+
existing_device[k] = new_device[k]
327+
if not found:
328+
result1.append( new_device )
329+
330+
def _get_all_devices( self, uid=None, device_ids=None ):
311331
fetches = 0
312332
our_result = { 'result': [] }
313333
last_row_key = None
314334
has_more = True
315335
total = 0
316-
query = {'size':'50'}
317336

318-
while has_more:
337+
if uid:
338+
# get device list for specified user id
339+
query = {'page_size':'75', 'source_type': 'tuyaUser', 'source_id': uid}
340+
# API docu: https://developer.tuya.com/en/docs/cloud/dc413408fe?id=Kc09y2ons2i3b
341+
uri = '/v1.3/iot-03/devices'
342+
if device_ids:
343+
if isinstance( device_ids, tuple ) or isinstance( device_ids, list ):
344+
query['device_ids'] = ','.join(device_ids)
345+
else:
346+
query['device_ids'] = device_ids
347+
else:
348+
# get all devices
349+
query = {'size':'50'}
319350
# API docu: https://developer.tuya.com/en/docs/cloud/fc19523d18?id=Kakr4p8nq5xsc
320-
result = self.cloudrequest( '/v1.0/iot-01/associated-users/devices', query=query )
351+
uri = '/v1.0/iot-01/associated-users/devices'
352+
353+
while has_more:
354+
result = self.cloudrequest( uri, query=query )
321355
fetches += 1
322356
has_more = False
323357

@@ -330,7 +364,12 @@ def _get_all_devices(self):
330364
# format it the same as before, basically just moves result->devices into result
331365
for i in result:
332366
if i == 'result':
333-
our_result[i] += result[i]['devices']
367+
# by-user-id has the result in 'list' while all-devices has it in 'devices'
368+
if 'list' in result[i] and 'devices' not in result[i]:
369+
our_result[i] += result[i]['list']
370+
elif 'devices' in result[i]:
371+
our_result[i] += result[i]['devices']
372+
334373
if 'total' in result[i]: total = result[i]['total']
335374
if 'last_row_key' in result[i]:
336375
has_more = result[i]['has_more']
@@ -364,6 +403,17 @@ def getdevices(self, verbose=False):
364403
json_data = self._tuyaplatform(uri)
365404
else:
366405
json_data = self._get_all_devices()
406+
users = {}
407+
# loop through all devices and build a list of user IDs
408+
for dev in json_data['result']:
409+
if 'uid' in dev:
410+
users[dev['uid']] = True
411+
if users:
412+
# we have at least 1 user id, so fetch the device list again to make sure we have the local key
413+
# this also gets us the gateway_id for child devices
414+
for uid in users.keys():
415+
json_data2 = self._get_all_devices( uid=uid )
416+
self._update_device_list( json_data['result'], json_data2['result'] )
367417

368418
if verbose:
369419
return json_data
@@ -422,6 +472,10 @@ def filter_devices( self, devs, ip_list=None ):
422472
else:
423473
item[k] = i[k]
424474

475+
if 'gateway_id' in i:
476+
k = 'gateway_id'
477+
item[k] = i[k]
478+
425479
tuyadevices.append(item)
426480

427481
return tuyadevices

tinytuya/wizard.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
TCPTIMEOUT = tinytuya.TCPTIMEOUT # Seconds to wait for socket open for scanning
4949
TCPPORT = tinytuya.TCPPORT # Tuya TCP Local Port
5050

51-
def wizard(color=True, retries=None, forcescan=False, nocloud=False):
51+
def wizard(color=True, retries=None, forcescan=False, nocloud=False, quicklist=False):
5252
"""
5353
TinyTuya Setup Wizard Tuya based WiFi smart devices
5454
@@ -94,16 +94,18 @@ def wizard(color=True, retries=None, forcescan=False, nocloud=False):
9494
if (config['apiKey'] != '' and config['apiSecret'] != '' and
9595
config['apiRegion'] != ''):
9696
needconfigs = False
97-
apiDeviceID = '<None>' if not config['apiDeviceID'] else config['apiDeviceID']
97+
apiDeviceID = '<None>' if ('apiDeviceID' not in config or not config['apiDeviceID']) else config['apiDeviceID']
9898
print(" " + subbold + "Existing settings:" + dim +
9999
"\n API Key=%s \n Secret=%s\n DeviceID=%s\n Region=%s" %
100100
(config['apiKey'], config['apiSecret'], apiDeviceID,
101101
config['apiRegion']))
102102
print('')
103-
answer = input(subbold + ' Use existing credentials ' +
104-
normal + '(Y/n): ')
105-
if answer[0:1].lower() == 'n':
103+
if quicklist:
106104
needconfigs = True
105+
else:
106+
answer = input(subbold + ' Use existing credentials ' + normal + '(Y/n): ')
107+
if answer[0:1].lower() == 'n':
108+
needconfigs = True
107109

108110
if needconfigs:
109111
# Ask user for config settings
@@ -157,17 +159,28 @@ def wizard(color=True, retries=None, forcescan=False, nocloud=False):
157159
# Filter to only Name, ID and Key, IP and mac-address
158160
tuyadevices = cloud.filter_devices( json_data['result'] )
159161

160-
# The device list does not tell us which device is the parent for a sub-device, so we need to try and figure it out
162+
# The device list does not (always) tell us which device is the parent for a sub-device, so we need to try and figure it out
161163
# The only link between parent and child appears to be the local key
164+
165+
# Result:
162166
# if 'parent' not in device: device is not a sub-device
163167
# if 'parent' in device: device is a sub-device
164168
# if device['parent'] == '': device is a sub-device with an unknown parent
165169
# else: device['parent'] == device_id of parent
166170
for dev in tuyadevices:
171+
if 'gateway_id' in dev:
172+
# if the Cloud gave us the parent then just use that
173+
if dev['gateway_id']:
174+
dev['parent'] = dev['gateway_id']
175+
del dev['gateway_id']
176+
167177
if 'sub' in dev and dev['sub']:
168-
if 'parent' not in dev:
169-
# Set 'parent' to an empty string in case we can't find it
170-
dev['parent'] = ''
178+
# no parent from cloud, try to find it via the local key
179+
if 'parent' in dev and dev['parent']:
180+
continue
181+
182+
# Set 'parent' to an empty string in case we can't find it
183+
dev['parent'] = ''
171184

172185
# Only try to find the parent if the device has a local key
173186
if 'key' in dev and dev['key']:
@@ -213,7 +226,10 @@ def wizard(color=True, retries=None, forcescan=False, nocloud=False):
213226
print('\n\n' + bold + 'Unable to save raw file' + dim )
214227

215228
# Find out if we should poll all devices
216-
answer = input(subbold + '\nPoll local devices? ' + normal + '(Y/n): ')
229+
if quicklist:
230+
answer = 'n'
231+
else:
232+
answer = input(subbold + '\nPoll local devices? ' + normal + '(Y/n): ')
217233
if answer.lower().find('n') < 0:
218234
result = tinytuya.scanner.poll_and_display( tuyadevices, color=color, scantime=retries, snapshot=True, forcescan=forcescan )
219235
iplist = {}

0 commit comments

Comments
 (0)