Skip to content

Commit ffcec47

Browse files
authored
Merge pull request #289 from uzlonewolf/cloud
Rework Cloud device list fetching
2 parents c334b74 + bddb973 commit ffcec47

File tree

3 files changed

+95
-30
lines changed

3 files changed

+95
-30
lines changed

tinytuya/Cloud.py

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def __init__(self, apiRegion=None, apiKey=None, apiSecret=None, apiDeviceID=None
8383
self.error = None
8484
self.new_sign_algorithm = new_sign_algorithm
8585
self.server_time_offset = 0
86+
self.use_old_device_list = False
8687

8788
if (not apiKey) or (not apiSecret):
8889
try:
@@ -93,7 +94,8 @@ def __init__(self, apiRegion=None, apiKey=None, apiSecret=None, apiDeviceID=None
9394
self.apiRegion = config['apiRegion']
9495
self.apiKey = config['apiKey']
9596
self.apiSecret = config['apiSecret']
96-
self.apiDeviceID = config['apiDeviceID']
97+
if 'apiDeviceID' in config:
98+
self.apiDeviceID = config['apiDeviceID']
9799
except:
98100
self.error = error_json(
99101
ERR_CLOUDKEY,
@@ -305,24 +307,63 @@ def cloudrequest(self, url, action=None, post=None, query=None):
305307
action = 'POST' if post else 'GET'
306308
return self._tuyaplatform(url, action=action, post=post, ver=None, query=query)
307309

310+
def _get_all_devices(self):
311+
fetches = 0
312+
our_result = { 'result': [] }
313+
last_row_key = None
314+
has_more = True
315+
total = 0
316+
query = {'size':'50'}
317+
318+
while has_more:
319+
# 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 )
321+
fetches += 1
322+
has_more = False
323+
324+
if type(result) == dict:
325+
log.debug( 'Cloud response:' )
326+
log.debug( json.dumps( result, indent=2 ) )
327+
else:
328+
log.debug( 'Cloud response: %r', result )
329+
330+
# format it the same as before, basically just moves result->devices into result
331+
for i in result:
332+
if i == 'result':
333+
our_result[i] += result[i]['devices']
334+
if 'total' in result[i]: total = result[i]['total']
335+
if 'last_row_key' in result[i]:
336+
has_more = result[i]['has_more']
337+
query['last_row_key'] = result[i]['last_row_key']
338+
else:
339+
our_result[i] = result[i]
340+
341+
our_result['fetches'] = fetches
342+
our_result['total'] = total
343+
344+
return our_result
345+
308346
def getdevices(self, verbose=False):
309347
"""
310348
Return dictionary of all devices.
311349
If verbose is true, return full Tuya device
312350
details.
313351
"""
314-
uid = self._getuid(self.apiDeviceID)
315-
if uid is None:
316-
return error_json(
317-
ERR_CLOUD,
318-
"Unable to get uid for device list"
319-
)
320-
elif isinstance( uid, dict):
321-
return uid
352+
if self.apiDeviceID and self.use_old_device_list:
353+
uid = self._getuid(self.apiDeviceID)
354+
if uid is None:
355+
return error_json(
356+
ERR_CLOUD,
357+
"Unable to get uid for device list"
358+
)
359+
elif isinstance( uid, dict):
360+
return uid
322361

323-
# Use UID to get list of all Devices for User
324-
uri = 'users/%s/devices' % uid
325-
json_data = self._tuyaplatform(uri)
362+
# Use UID to get list of all Devices for User
363+
uri = 'users/%s/devices' % uid
364+
json_data = self._tuyaplatform(uri)
365+
else:
366+
json_data = self._get_all_devices()
326367

327368
if verbose:
328369
return json_data
@@ -335,26 +376,41 @@ def getdevices(self, verbose=False):
335376
# Filter to only Name, ID and Key
336377
return self.filter_devices( json_data['result'] )
337378

379+
def _get_hw_addresses( self, maclist, devices ):
380+
while devices:
381+
# returns id, mac, uuid (and sn if available)
382+
uri = 'devices/factory-infos?device_ids=%s' % (",".join(devices[:50]))
383+
result = self._tuyaplatform(uri)
384+
log.debug( json.dumps( result, indent=2 ) )
385+
if 'result' in result:
386+
for dev in result['result']:
387+
if 'id' in dev:
388+
dev_id = dev['id']
389+
del dev['id']
390+
maclist[dev_id] = dev
391+
devices = devices[50:]
392+
338393
def filter_devices( self, devs, ip_list=None ):
339-
# Use Device ID to get MAC addresses
340-
uri = 'devices/factory-infos?device_ids=%s' % (",".join(i['id'] for i in devs))
341-
json_mac_data = self._tuyaplatform(uri)
394+
json_mac_data = {}
395+
# mutable json_mac_data will be modified
396+
self._get_hw_addresses( json_mac_data, [i['id'] for i in devs] )
342397

343398
tuyadevices = []
344399
icon_host = 'https://images.' + self.urlhost.split( '.', 1 )[1] + '/'
345400

346401
for i in devs:
347-
item = {}
348-
item['name'] = i['name'].strip()
349-
item['id'] = i['id']
350-
item['key'] = i['local_key']
351-
if 'mac' in i:
352-
item['mac'] = i['mac']
353-
else:
354-
try:
355-
item['mac'] = next((m['mac'] for m in json_mac_data['result'] if m['id'] == i['id']), "")
356-
except:
357-
pass
402+
dev_id = i['id']
403+
item = {
404+
'name': '' if 'name' not in i else i['name'].strip(),
405+
'id': dev_id,
406+
'key': '' if 'local_key' not in i else i['local_key'],
407+
'mac': '' if 'mac' not in i else i['mac']
408+
}
409+
410+
if dev_id in json_mac_data:
411+
for k in ('mac','uuid','sn'):
412+
if k in json_mac_data[dev_id]:
413+
item[k] = json_mac_data[dev_id][k]
358414

359415
if ip_list and 'mac' in item and item['mac'] in ip_list:
360416
item['ip'] = ip_list[item['mac']]

tinytuya/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
RAWFILE = 'tuya-raw.json'
120120
SNAPSHOTFILE = 'snapshot.json'
121121

122-
DEVICEFILE_SAVE_VALUES = ('category', 'product_name', 'product_id', 'biz_type', 'model', 'sub', 'icon', 'version', 'last_ip', 'uuid', 'node_id')
122+
DEVICEFILE_SAVE_VALUES = ('category', 'product_name', 'product_id', 'biz_type', 'model', 'sub', 'icon', 'version', 'last_ip', 'uuid', 'node_id', 'sn')
123123

124124
# Tuya Command Types
125125
# Reference: https://github.com/tuya/tuya-iotos-embeded-sdk-wifi-ble-bk7231n/blob/master/sdk/include/lan_protocol.h

tinytuya/wizard.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from __future__ import print_function
2626
import json
2727
from colorama import init
28+
from datetime import datetime
2829
import tinytuya
2930

3031
# Backward compatibility for python2
@@ -91,11 +92,12 @@ def wizard(color=True, retries=None, forcescan=False, nocloud=False):
9192
print('')
9293

9394
if (config['apiKey'] != '' and config['apiSecret'] != '' and
94-
config['apiRegion'] != '' and config['apiDeviceID'] != ''):
95+
config['apiRegion'] != ''):
9596
needconfigs = False
97+
apiDeviceID = '<None>' if not config['apiDeviceID'] else config['apiDeviceID']
9698
print(" " + subbold + "Existing settings:" + dim +
9799
"\n API Key=%s \n Secret=%s\n DeviceID=%s\n Region=%s" %
98-
(config['apiKey'], config['apiSecret'], config['apiDeviceID'],
100+
(config['apiKey'], config['apiSecret'], apiDeviceID,
99101
config['apiRegion']))
100102
print('')
101103
answer = input(subbold + ' Use existing credentials ' +
@@ -111,7 +113,7 @@ def wizard(color=True, retries=None, forcescan=False, nocloud=False):
111113
config['apiSecret'] = input(subbold + " Enter " + bold + "API Secret" + subbold +
112114
" from tuya.com: " + normal)
113115
config['apiDeviceID'] = input(subbold +
114-
" Enter " + bold + "any Device ID" + subbold +
116+
" (Optional) Enter " + bold + "any Device ID" + subbold +
115117
" currently registered in Tuya App (used to pull full list): " + normal)
116118
# TO DO - Determine apiRegion based on Device - for now, ask
117119
print("\n " + subbold + "Region List" + dim +
@@ -180,6 +182,13 @@ def wizard(color=True, retries=None, forcescan=False, nocloud=False):
180182
if not nocloud:
181183
# Save raw TuyaPlatform data to tuya-raw.json
182184
print(bold + "\n>> " + normal + "Saving raw TuyaPlatform response to " + RAWFILE)
185+
json_data['file'] = {
186+
'name': RAWFILE,
187+
'description': 'Full raw list of Tuya devices.',
188+
'account': cloud.apiKey,
189+
'date': datetime.now().isoformat(),
190+
'tinytuya': tinytuya.version
191+
}
183192
try:
184193
with open(RAWFILE, "w") as outfile:
185194
outfile.write(json.dumps(json_data, indent=4))

0 commit comments

Comments
 (0)