mijiaAPI 1.3.9__tar.gz → 1.3.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/PKG-INFO +2 -2
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/README.md +1 -1
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/mijiaAPI/devices.py +353 -353
- mijiaapi-1.3.10/mijiaAPI/logger.py +37 -0
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/mijiaAPI/login.py +9 -10
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/mijiaAPI/urls.py +7 -7
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/pyproject.toml +2 -2
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/LICENSE +0 -0
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/mijiaAPI/__init__.py +0 -0
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/mijiaAPI/apis.py +0 -0
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/mijiaAPI/code.py +0 -0
- {mijiaapi-1.3.9 → mijiaapi-1.3.10}/mijiaAPI/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mijiaAPI
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.10
|
|
4
4
|
Summary: A Python API for Xiaomi Mijia
|
|
5
5
|
License: GPLv3
|
|
6
6
|
Author: Do1e
|
|
@@ -63,7 +63,7 @@ pip install mijiaAPI
|
|
|
63
63
|
|
|
64
64
|
### 针对设备的封装
|
|
65
65
|
|
|
66
|
-
最简单的使用方法是直接使用自然语言调用小爱音箱,参见[demos/
|
|
66
|
+
最简单的使用方法是直接使用自然语言调用小爱音箱,参见[demos/test_devices_wifispeaker.py](demos/test_devices_wifispeaker.py)
|
|
67
67
|
|
|
68
68
|
`mijiaDevices`:使用`mijiaAPI`和设备属性字典初始化,以便更方便地调用设备属性
|
|
69
69
|
* `__init__(api: mijiaAPI, dev_info: dict. did: str = None, sleep_time: float = 0.5)`:初始化,`dev_info`为设备属性,参考[demos/dev_info_example](demos/dev_info_example),`sleep_time`为每次调用设备属性的间隔时间(注:设置属性后立刻获取属性会不符合预期,需要延迟一段时间)
|
|
@@ -39,7 +39,7 @@ pip install mijiaAPI
|
|
|
39
39
|
|
|
40
40
|
### 针对设备的封装
|
|
41
41
|
|
|
42
|
-
最简单的使用方法是直接使用自然语言调用小爱音箱,参见[demos/
|
|
42
|
+
最简单的使用方法是直接使用自然语言调用小爱音箱,参见[demos/test_devices_wifispeaker.py](demos/test_devices_wifispeaker.py)
|
|
43
43
|
|
|
44
44
|
`mijiaDevices`:使用`mijiaAPI`和设备属性字典初始化,以便更方便地调用设备属性
|
|
45
45
|
* `__init__(api: mijiaAPI, dev_info: dict. did: str = None, sleep_time: float = 0.5)`:初始化,`dev_info`为设备属性,参考[demos/dev_info_example](demos/dev_info_example),`sleep_time`为每次调用设备属性的间隔时间(注:设置属性后立刻获取属性会不符合预期,需要延迟一段时间)
|
|
@@ -1,353 +1,353 @@
|
|
|
1
|
-
from typing import Union, Optional
|
|
2
|
-
import json
|
|
3
|
-
import re
|
|
4
|
-
import requests
|
|
5
|
-
from time import sleep
|
|
6
|
-
from .apis import mijiaAPI
|
|
7
|
-
from .code import ERROR_CODE
|
|
8
|
-
from .urls import deviceURL
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class DevProp(object):
|
|
12
|
-
def __init__(self, prop_dict: dict):
|
|
13
|
-
"""
|
|
14
|
-
Initialize the property object 初始化属性对象
|
|
15
|
-
:param prop_dict:
|
|
16
|
-
"""
|
|
17
|
-
self.name = prop_dict['name']
|
|
18
|
-
self.desc = prop_dict['description']
|
|
19
|
-
self.type = prop_dict['type']
|
|
20
|
-
if self.type not in ['bool', 'int', 'uint', 'float', 'string']:
|
|
21
|
-
raise ValueError(f'Unsupported type: {self.type}, available types: bool, int, uint, float, string')
|
|
22
|
-
self.rw = prop_dict['rw']
|
|
23
|
-
self.unit = prop_dict['unit']
|
|
24
|
-
self.range = prop_dict['range']
|
|
25
|
-
self.value_list = prop_dict.get('value-list', None)
|
|
26
|
-
self.method = prop_dict['method']
|
|
27
|
-
|
|
28
|
-
def __str__(self):
|
|
29
|
-
"""
|
|
30
|
-
String representation of the property 返回属性的名称、描述、类型、读写权限、单位和范围
|
|
31
|
-
:return:
|
|
32
|
-
"""
|
|
33
|
-
lines = [
|
|
34
|
-
f" {self.name}: {self.desc}",
|
|
35
|
-
f" valuetype: {self.type}, rw: {self.rw}, unit: {self.unit}, range: {self.range}"
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
if self.value_list:
|
|
39
|
-
value_lines = [f" {item['value']}: {item['description']}" for item in self.value_list]
|
|
40
|
-
lines.extend(value_lines)
|
|
41
|
-
|
|
42
|
-
return '\n'.join(lines)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class DevAction(object):
|
|
46
|
-
def __init__(self, act_dict: dict):
|
|
47
|
-
self.name = act_dict['name']
|
|
48
|
-
self.desc = act_dict['description']
|
|
49
|
-
self.method = act_dict['method']
|
|
50
|
-
|
|
51
|
-
def __str__(self):
|
|
52
|
-
return f' {self.name}: {self.desc}'
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class mijiaDevices(object):
|
|
56
|
-
def __init__(
|
|
57
|
-
self,
|
|
58
|
-
api: mijiaAPI,
|
|
59
|
-
dev_info: Optional[dict] = None,
|
|
60
|
-
dev_name: Optional[str] = None,
|
|
61
|
-
did: Optional[str] = None,
|
|
62
|
-
sleep_time: Optional[Union[int, float]] = 0.5
|
|
63
|
-
):
|
|
64
|
-
"""
|
|
65
|
-
Initialize the device object 初始化设备对象
|
|
66
|
-
若未提供设备信息,则根据设备名称获取设备信息,若两者均未提供,则抛出异常
|
|
67
|
-
若提供了设备信息的同时提供了设备名称,则以设备信息为准
|
|
68
|
-
:param api: 米家API对象
|
|
69
|
-
:param dev_info: 设备信息字典(可选)
|
|
70
|
-
:param dev_name: 设备名称(可选)
|
|
71
|
-
:param did: 设备ID(可选)
|
|
72
|
-
:param sleep_time: 调用设备属性的间隔时间(可选,默认0.5秒)
|
|
73
|
-
"""
|
|
74
|
-
assert dev_info is not None or dev_name is not None, "Either 'dev_info' or 'dev_name' must be provided."
|
|
75
|
-
if dev_info is not None and dev_name is not None:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.api = api
|
|
79
|
-
if dev_info is None:
|
|
80
|
-
devices_list = self.api.get_devices_list()
|
|
81
|
-
matches = [device for device in devices_list.get('list', []) if device['name'] == dev_name]
|
|
82
|
-
if not matches:
|
|
83
|
-
raise ValueError(f"Device {dev_name} not found")
|
|
84
|
-
elif len(matches) > 1:
|
|
85
|
-
raise ValueError(f"Multiple devices named {dev_name} found")
|
|
86
|
-
else:
|
|
87
|
-
dev_info = get_device_info(matches[0]['model'])
|
|
88
|
-
did = matches[0]['did']
|
|
89
|
-
self.name = dev_info['name']
|
|
90
|
-
self.model = dev_info['model']
|
|
91
|
-
|
|
92
|
-
self.prop_list = {}
|
|
93
|
-
for prop in dev_info.get('properties', []):
|
|
94
|
-
prop_obj = DevProp(prop)
|
|
95
|
-
name = prop['name']
|
|
96
|
-
self.prop_list[name] = prop_obj
|
|
97
|
-
if '-' in name:
|
|
98
|
-
self.prop_list[name.replace('-', '_')] = prop_obj
|
|
99
|
-
|
|
100
|
-
self.action_list = {
|
|
101
|
-
act['name']: DevAction(act)
|
|
102
|
-
for act in dev_info.get('actions', [])
|
|
103
|
-
}
|
|
104
|
-
self.did = did
|
|
105
|
-
self.sleep_time = sleep_time
|
|
106
|
-
|
|
107
|
-
def __str__(self) -> str:
|
|
108
|
-
"""
|
|
109
|
-
String representation of the device 返回设备的属性和动作列表
|
|
110
|
-
:return: 设备的属性和动作列表
|
|
111
|
-
"""
|
|
112
|
-
prop_list_str = '\n'.join(filter(None, (str(v) for k, v in self.prop_list.items() if '_' not in k)))
|
|
113
|
-
action_list_str = '\n'.join(map(str, self.action_list.values()))
|
|
114
|
-
return (f"{self.name} ({self.model})\n"
|
|
115
|
-
f"Properties:\n{prop_list_str if prop_list_str else 'No properties available'}\n"
|
|
116
|
-
f"Actions:\n{action_list_str if action_list_str else 'No actions available'}")
|
|
117
|
-
|
|
118
|
-
def set(self, name: str, did: str, value: Union[bool, int]) -> Union[bool, int]:
|
|
119
|
-
"""
|
|
120
|
-
Set property value 设置设备的属性值
|
|
121
|
-
:param name: 属性名称
|
|
122
|
-
:param did: 设备ID
|
|
123
|
-
:param value: 属性值
|
|
124
|
-
:return: 执行结果(True/False)
|
|
125
|
-
"""
|
|
126
|
-
if name not in self.prop_list:
|
|
127
|
-
raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
|
|
128
|
-
prop = self.prop_list[name]
|
|
129
|
-
if 'w' not in prop.rw:
|
|
130
|
-
raise ValueError(f'Property {name} is read-only')
|
|
131
|
-
if prop.value_list:
|
|
132
|
-
if value not in [item['value'] for item in prop.value_list]:
|
|
133
|
-
raise ValueError(f'Invalid value: {value}, should be in {prop.value_list}')
|
|
134
|
-
if prop.type == 'bool':
|
|
135
|
-
if not isinstance(value, bool):
|
|
136
|
-
raise ValueError(f'Invalid value for bool: {value}, should be True or False')
|
|
137
|
-
elif prop.type in ['int', 'uint']:
|
|
138
|
-
value = int(value)
|
|
139
|
-
if prop.range:
|
|
140
|
-
if value < prop.range[0] or value > prop.range[1]:
|
|
141
|
-
raise ValueError(f'Value out of range: {value}, should be in range {prop.range[:2]}')
|
|
142
|
-
if len(prop.range) >= 3 and prop.range[2] != 1:
|
|
143
|
-
if (value - prop.range[0]) % prop.range[2] != 0:
|
|
144
|
-
raise ValueError(
|
|
145
|
-
f'Invalid value: {value}, should be in range {prop.range[:2]} with step {prop.range[2]}')
|
|
146
|
-
elif prop.type == 'float':
|
|
147
|
-
value = float(value)
|
|
148
|
-
if prop.range:
|
|
149
|
-
if value < prop.range[0] or value > prop.range[1]:
|
|
150
|
-
raise ValueError(f'Value out of range: {value}, should be in range {prop.range[:2]}')
|
|
151
|
-
if len(prop.range) >= 3 and isinstance(prop.range[2], int):
|
|
152
|
-
if int(value - prop.range[0]) % prop.range[2] != 0:
|
|
153
|
-
raise ValueError(
|
|
154
|
-
f'Invalid value: {value}, should be in range {prop.range[:2]} with step {prop.range[2]}')
|
|
155
|
-
elif prop.type == 'string':
|
|
156
|
-
if not isinstance(value, str):
|
|
157
|
-
raise ValueError(f'Invalid value for string: {value}, should be a string')
|
|
158
|
-
else:
|
|
159
|
-
raise ValueError(f'Unsupported type: {prop.type}, available types: bool, int, uint, float, string')
|
|
160
|
-
method = prop.method.copy()
|
|
161
|
-
method['did'] = did
|
|
162
|
-
method['value'] = value
|
|
163
|
-
result = self.api.set_devices_prop([method])[0]
|
|
164
|
-
if result['code'] != 0:
|
|
165
|
-
raise RuntimeError(
|
|
166
|
-
f"Failed to set property: {name}, "
|
|
167
|
-
f"code: {result['code']}, "
|
|
168
|
-
f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
|
|
169
|
-
)
|
|
170
|
-
sleep(self.sleep_time)
|
|
171
|
-
return result['code'] == 0
|
|
172
|
-
|
|
173
|
-
def set_v2(self, name: str, value: Union[bool, int], did: Optional[str] = None) -> Union[bool, int]:
|
|
174
|
-
"""
|
|
175
|
-
Set property value_v2 设置设备的属性值_v2(需在实例化时指定did)
|
|
176
|
-
:param name: 属性名称
|
|
177
|
-
:param value: 属性值
|
|
178
|
-
:param did: 设备ID(若未指定,则使用实例化时的did)
|
|
179
|
-
:return: 执行结果(True/False)
|
|
180
|
-
"""
|
|
181
|
-
if did is not None:
|
|
182
|
-
return self.set(name, did, value)
|
|
183
|
-
elif self.did is not None:
|
|
184
|
-
return self.set(name, self.did, value)
|
|
185
|
-
else:
|
|
186
|
-
raise ValueError('Please specify the did')
|
|
187
|
-
|
|
188
|
-
def get(self, name: str, did: Optional[str] = None) -> Union[bool, int]:
|
|
189
|
-
"""
|
|
190
|
-
Get property value 获取设备的属性值
|
|
191
|
-
:param name: 属性名称
|
|
192
|
-
:param did: 设备ID(若未指定,则使用实例化时的did)
|
|
193
|
-
:return: 属性值
|
|
194
|
-
"""
|
|
195
|
-
if did is None:
|
|
196
|
-
did = self.did
|
|
197
|
-
if did is None:
|
|
198
|
-
raise ValueError('Please specify the did')
|
|
199
|
-
if name not in self.prop_list:
|
|
200
|
-
raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
|
|
201
|
-
prop = self.prop_list[name]
|
|
202
|
-
if 'r' not in prop.rw:
|
|
203
|
-
raise ValueError(f'Property {name} is write-only')
|
|
204
|
-
method = prop.method.copy()
|
|
205
|
-
method['did'] = did
|
|
206
|
-
result = self.api.get_devices_prop([method])[0]
|
|
207
|
-
if result['code'] != 0:
|
|
208
|
-
raise RuntimeError(
|
|
209
|
-
f"Failed to get property: {name}, "
|
|
210
|
-
f"code: {result['code']}, "
|
|
211
|
-
f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
|
|
212
|
-
)
|
|
213
|
-
sleep(self.sleep_time)
|
|
214
|
-
return result['value']
|
|
215
|
-
|
|
216
|
-
def __setattr__(self, name: str, value: Union[bool, int]) -> None:
|
|
217
|
-
"""
|
|
218
|
-
Set property value 设置设备的属性值(需在实例化时指定did)
|
|
219
|
-
:param name: 属性名称
|
|
220
|
-
:param value: 属性值
|
|
221
|
-
:return: None
|
|
222
|
-
"""
|
|
223
|
-
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
224
|
-
if not self.set_v2(name, value):
|
|
225
|
-
raise RuntimeError(f'Failed to set property: {name}')
|
|
226
|
-
else:
|
|
227
|
-
super().__setattr__(name, value)
|
|
228
|
-
|
|
229
|
-
def __getattr__(self, name: str) -> Union[bool, int]:
|
|
230
|
-
"""
|
|
231
|
-
Get property value 获取设备的属性值(需在实例化时指定did)
|
|
232
|
-
:param name: 属性名称
|
|
233
|
-
:return: 属性值
|
|
234
|
-
"""
|
|
235
|
-
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
236
|
-
return self.get(name)
|
|
237
|
-
else:
|
|
238
|
-
return super().__getattr__(name)
|
|
239
|
-
|
|
240
|
-
def run_action(
|
|
241
|
-
self,
|
|
242
|
-
name: str,
|
|
243
|
-
did: Optional[str] = None,
|
|
244
|
-
value: Optional[Union[list, tuple]] = None,
|
|
245
|
-
**kwargs
|
|
246
|
-
) -> bool:
|
|
247
|
-
"""
|
|
248
|
-
Run action 运行设备动作
|
|
249
|
-
:param name: 动作名称
|
|
250
|
-
:param did: 设备ID(若未指定,则使用实例化时的did)
|
|
251
|
-
:param value: 动作参数(若动作不需要参数,则不需要指定)
|
|
252
|
-
:param kwargs: 其它动作参数(若动作不需要参数,则不需要指定)
|
|
253
|
-
:return: 执行结果(True/False)
|
|
254
|
-
"""
|
|
255
|
-
if did is None:
|
|
256
|
-
did = self.did
|
|
257
|
-
if did is None:
|
|
258
|
-
raise ValueError('Please specify the did')
|
|
259
|
-
if name not in self.action_list:
|
|
260
|
-
raise ValueError(f'Unsupported action: {name}, available actions: {list(self.action_list.keys())}')
|
|
261
|
-
act = self.action_list[name]
|
|
262
|
-
method = act.method.copy()
|
|
263
|
-
method['did'] = did
|
|
264
|
-
if value is not None:
|
|
265
|
-
method['value'] = value
|
|
266
|
-
if kwargs:
|
|
267
|
-
for k, v in kwargs.items():
|
|
268
|
-
if k.startswith("_"):
|
|
269
|
-
k = k[1:]
|
|
270
|
-
if k in method:
|
|
271
|
-
raise ValueError(f'Invalid argument: {k}. Do not use arguments in ({", ".join(method.keys())})')
|
|
272
|
-
method[k] = v
|
|
273
|
-
result = self.api.run_action(method)
|
|
274
|
-
if result['code'] != 0:
|
|
275
|
-
raise RuntimeError(
|
|
276
|
-
f"Failed to run action: {name}, "
|
|
277
|
-
f"code: {result['code']}, "
|
|
278
|
-
f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
|
|
279
|
-
)
|
|
280
|
-
sleep(self.sleep_time)
|
|
281
|
-
return result['code'] == 0
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def get_device_info(device_model: str) -> dict:
|
|
285
|
-
"""
|
|
286
|
-
Get device info 获取设备信息
|
|
287
|
-
:param device_model: 设备型号
|
|
288
|
-
:return: 设备信息字典
|
|
289
|
-
"""
|
|
290
|
-
response = requests.get(deviceURL + device_model)
|
|
291
|
-
if response.status_code != 200:
|
|
292
|
-
raise RuntimeError(f'Failed to get device info')
|
|
293
|
-
content = re.search(r'data-page="(.*?)">', response.text)
|
|
294
|
-
if content is None:
|
|
295
|
-
raise RuntimeError(f'Failed to get device info')
|
|
296
|
-
content = content.group(1)
|
|
297
|
-
content = json.loads(content.replace('"', '"'))
|
|
298
|
-
|
|
299
|
-
result = {
|
|
300
|
-
'name': content['props']['product']['name'],
|
|
301
|
-
'model': content['props']['product']['model'],
|
|
302
|
-
'properties': [],
|
|
303
|
-
'actions': []
|
|
304
|
-
}
|
|
305
|
-
services = content['props']['spec']['services']
|
|
306
|
-
|
|
307
|
-
properties_name = []
|
|
308
|
-
actions_name = []
|
|
309
|
-
for siid in services:
|
|
310
|
-
if 'properties' in services[siid]:
|
|
311
|
-
for piid in services[siid]['properties']:
|
|
312
|
-
prop = services[siid]['properties'][piid]
|
|
313
|
-
if prop['format'].startswith('int'):
|
|
314
|
-
prop_type = 'int'
|
|
315
|
-
elif prop['format'].startswith('uint'):
|
|
316
|
-
prop_type = 'uint'
|
|
317
|
-
else:
|
|
318
|
-
prop_type = prop['format']
|
|
319
|
-
item = {
|
|
320
|
-
'name': prop['name'],
|
|
321
|
-
'description': prop['description'],
|
|
322
|
-
'type': prop_type,
|
|
323
|
-
'rw': ''.join([
|
|
324
|
-
'r' if 'read' in prop['access'] else '',
|
|
325
|
-
'w' if 'write' in prop['access'] else ''
|
|
326
|
-
]),
|
|
327
|
-
'unit': prop.get('unit', None),
|
|
328
|
-
'range': prop.get('value-range', None),
|
|
329
|
-
'value-list': prop.get('value-list', None),
|
|
330
|
-
'method': {
|
|
331
|
-
'siid': int(siid),
|
|
332
|
-
'piid': int(piid)
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
if item['name'] in properties_name:
|
|
336
|
-
item["name"] = f'{services[siid]["name"]}-{item["name"]}'
|
|
337
|
-
properties_name.append(item['name'])
|
|
338
|
-
result['properties'].append({k: None if v == 'none' else v for k, v in item.items()})
|
|
339
|
-
if 'actions' in services[siid]:
|
|
340
|
-
for aiid in services[siid]['actions']:
|
|
341
|
-
act = services[siid]['actions'][aiid]
|
|
342
|
-
if act['name'] in actions_name:
|
|
343
|
-
act['name'] = f'{services[siid]["name"]}-{act["name"]}'
|
|
344
|
-
actions_name.append(act['name'])
|
|
345
|
-
result['actions'].append({
|
|
346
|
-
'name': act['name'],
|
|
347
|
-
'description': act['description'],
|
|
348
|
-
'method': {
|
|
349
|
-
'siid': int(siid),
|
|
350
|
-
'aiid': int(aiid)
|
|
351
|
-
}
|
|
352
|
-
})
|
|
353
|
-
return result
|
|
1
|
+
from typing import Union, Optional
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import requests
|
|
5
|
+
from time import sleep
|
|
6
|
+
from .apis import mijiaAPI
|
|
7
|
+
from .code import ERROR_CODE
|
|
8
|
+
from .urls import deviceURL
|
|
9
|
+
from .logger import logger
|
|
10
|
+
|
|
11
|
+
class DevProp(object):
|
|
12
|
+
def __init__(self, prop_dict: dict):
|
|
13
|
+
"""
|
|
14
|
+
Initialize the property object 初始化属性对象
|
|
15
|
+
:param prop_dict:
|
|
16
|
+
"""
|
|
17
|
+
self.name = prop_dict['name']
|
|
18
|
+
self.desc = prop_dict['description']
|
|
19
|
+
self.type = prop_dict['type']
|
|
20
|
+
if self.type not in ['bool', 'int', 'uint', 'float', 'string']:
|
|
21
|
+
raise ValueError(f'Unsupported type: {self.type}, available types: bool, int, uint, float, string')
|
|
22
|
+
self.rw = prop_dict['rw']
|
|
23
|
+
self.unit = prop_dict['unit']
|
|
24
|
+
self.range = prop_dict['range']
|
|
25
|
+
self.value_list = prop_dict.get('value-list', None)
|
|
26
|
+
self.method = prop_dict['method']
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
"""
|
|
30
|
+
String representation of the property 返回属性的名称、描述、类型、读写权限、单位和范围
|
|
31
|
+
:return:
|
|
32
|
+
"""
|
|
33
|
+
lines = [
|
|
34
|
+
f" {self.name}: {self.desc}",
|
|
35
|
+
f" valuetype: {self.type}, rw: {self.rw}, unit: {self.unit}, range: {self.range}"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
if self.value_list:
|
|
39
|
+
value_lines = [f" {item['value']}: {item['description']}" for item in self.value_list]
|
|
40
|
+
lines.extend(value_lines)
|
|
41
|
+
|
|
42
|
+
return '\n'.join(lines)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DevAction(object):
|
|
46
|
+
def __init__(self, act_dict: dict):
|
|
47
|
+
self.name = act_dict['name']
|
|
48
|
+
self.desc = act_dict['description']
|
|
49
|
+
self.method = act_dict['method']
|
|
50
|
+
|
|
51
|
+
def __str__(self):
|
|
52
|
+
return f' {self.name}: {self.desc}'
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class mijiaDevices(object):
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
api: mijiaAPI,
|
|
59
|
+
dev_info: Optional[dict] = None,
|
|
60
|
+
dev_name: Optional[str] = None,
|
|
61
|
+
did: Optional[str] = None,
|
|
62
|
+
sleep_time: Optional[Union[int, float]] = 0.5
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Initialize the device object 初始化设备对象
|
|
66
|
+
若未提供设备信息,则根据设备名称获取设备信息,若两者均未提供,则抛出异常
|
|
67
|
+
若提供了设备信息的同时提供了设备名称,则以设备信息为准
|
|
68
|
+
:param api: 米家API对象
|
|
69
|
+
:param dev_info: 设备信息字典(可选)
|
|
70
|
+
:param dev_name: 设备名称(可选)
|
|
71
|
+
:param did: 设备ID(可选)
|
|
72
|
+
:param sleep_time: 调用设备属性的间隔时间(可选,默认0.5秒)
|
|
73
|
+
"""
|
|
74
|
+
assert dev_info is not None or dev_name is not None, "Either 'dev_info' or 'dev_name' must be provided."
|
|
75
|
+
if dev_info is not None and dev_name is not None:
|
|
76
|
+
logger.warning("Both 'dev_info' and 'dev_name' provided. Using 'dev_info' for initialization.")
|
|
77
|
+
|
|
78
|
+
self.api = api
|
|
79
|
+
if dev_info is None:
|
|
80
|
+
devices_list = self.api.get_devices_list()
|
|
81
|
+
matches = [device for device in devices_list.get('list', []) if device['name'] == dev_name]
|
|
82
|
+
if not matches:
|
|
83
|
+
raise ValueError(f"Device {dev_name} not found")
|
|
84
|
+
elif len(matches) > 1:
|
|
85
|
+
raise ValueError(f"Multiple devices named {dev_name} found")
|
|
86
|
+
else:
|
|
87
|
+
dev_info = get_device_info(matches[0]['model'])
|
|
88
|
+
did = matches[0]['did']
|
|
89
|
+
self.name = dev_info['name']
|
|
90
|
+
self.model = dev_info['model']
|
|
91
|
+
|
|
92
|
+
self.prop_list = {}
|
|
93
|
+
for prop in dev_info.get('properties', []):
|
|
94
|
+
prop_obj = DevProp(prop)
|
|
95
|
+
name = prop['name']
|
|
96
|
+
self.prop_list[name] = prop_obj
|
|
97
|
+
if '-' in name:
|
|
98
|
+
self.prop_list[name.replace('-', '_')] = prop_obj
|
|
99
|
+
|
|
100
|
+
self.action_list = {
|
|
101
|
+
act['name']: DevAction(act)
|
|
102
|
+
for act in dev_info.get('actions', [])
|
|
103
|
+
}
|
|
104
|
+
self.did = did
|
|
105
|
+
self.sleep_time = sleep_time
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
"""
|
|
109
|
+
String representation of the device 返回设备的属性和动作列表
|
|
110
|
+
:return: 设备的属性和动作列表
|
|
111
|
+
"""
|
|
112
|
+
prop_list_str = '\n'.join(filter(None, (str(v) for k, v in self.prop_list.items() if '_' not in k)))
|
|
113
|
+
action_list_str = '\n'.join(map(str, self.action_list.values()))
|
|
114
|
+
return (f"{self.name} ({self.model})\n"
|
|
115
|
+
f"Properties:\n{prop_list_str if prop_list_str else 'No properties available'}\n"
|
|
116
|
+
f"Actions:\n{action_list_str if action_list_str else 'No actions available'}")
|
|
117
|
+
|
|
118
|
+
def set(self, name: str, did: str, value: Union[bool, int]) -> Union[bool, int]:
|
|
119
|
+
"""
|
|
120
|
+
Set property value 设置设备的属性值
|
|
121
|
+
:param name: 属性名称
|
|
122
|
+
:param did: 设备ID
|
|
123
|
+
:param value: 属性值
|
|
124
|
+
:return: 执行结果(True/False)
|
|
125
|
+
"""
|
|
126
|
+
if name not in self.prop_list:
|
|
127
|
+
raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
|
|
128
|
+
prop = self.prop_list[name]
|
|
129
|
+
if 'w' not in prop.rw:
|
|
130
|
+
raise ValueError(f'Property {name} is read-only')
|
|
131
|
+
if prop.value_list:
|
|
132
|
+
if value not in [item['value'] for item in prop.value_list]:
|
|
133
|
+
raise ValueError(f'Invalid value: {value}, should be in {prop.value_list}')
|
|
134
|
+
if prop.type == 'bool':
|
|
135
|
+
if not isinstance(value, bool):
|
|
136
|
+
raise ValueError(f'Invalid value for bool: {value}, should be True or False')
|
|
137
|
+
elif prop.type in ['int', 'uint']:
|
|
138
|
+
value = int(value)
|
|
139
|
+
if prop.range:
|
|
140
|
+
if value < prop.range[0] or value > prop.range[1]:
|
|
141
|
+
raise ValueError(f'Value out of range: {value}, should be in range {prop.range[:2]}')
|
|
142
|
+
if len(prop.range) >= 3 and prop.range[2] != 1:
|
|
143
|
+
if (value - prop.range[0]) % prop.range[2] != 0:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f'Invalid value: {value}, should be in range {prop.range[:2]} with step {prop.range[2]}')
|
|
146
|
+
elif prop.type == 'float':
|
|
147
|
+
value = float(value)
|
|
148
|
+
if prop.range:
|
|
149
|
+
if value < prop.range[0] or value > prop.range[1]:
|
|
150
|
+
raise ValueError(f'Value out of range: {value}, should be in range {prop.range[:2]}')
|
|
151
|
+
if len(prop.range) >= 3 and isinstance(prop.range[2], int):
|
|
152
|
+
if int(value - prop.range[0]) % prop.range[2] != 0:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f'Invalid value: {value}, should be in range {prop.range[:2]} with step {prop.range[2]}')
|
|
155
|
+
elif prop.type == 'string':
|
|
156
|
+
if not isinstance(value, str):
|
|
157
|
+
raise ValueError(f'Invalid value for string: {value}, should be a string')
|
|
158
|
+
else:
|
|
159
|
+
raise ValueError(f'Unsupported type: {prop.type}, available types: bool, int, uint, float, string')
|
|
160
|
+
method = prop.method.copy()
|
|
161
|
+
method['did'] = did
|
|
162
|
+
method['value'] = value
|
|
163
|
+
result = self.api.set_devices_prop([method])[0]
|
|
164
|
+
if result['code'] != 0:
|
|
165
|
+
raise RuntimeError(
|
|
166
|
+
f"Failed to set property: {name}, "
|
|
167
|
+
f"code: {result['code']}, "
|
|
168
|
+
f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
|
|
169
|
+
)
|
|
170
|
+
sleep(self.sleep_time)
|
|
171
|
+
return result['code'] == 0
|
|
172
|
+
|
|
173
|
+
def set_v2(self, name: str, value: Union[bool, int], did: Optional[str] = None) -> Union[bool, int]:
|
|
174
|
+
"""
|
|
175
|
+
Set property value_v2 设置设备的属性值_v2(需在实例化时指定did)
|
|
176
|
+
:param name: 属性名称
|
|
177
|
+
:param value: 属性值
|
|
178
|
+
:param did: 设备ID(若未指定,则使用实例化时的did)
|
|
179
|
+
:return: 执行结果(True/False)
|
|
180
|
+
"""
|
|
181
|
+
if did is not None:
|
|
182
|
+
return self.set(name, did, value)
|
|
183
|
+
elif self.did is not None:
|
|
184
|
+
return self.set(name, self.did, value)
|
|
185
|
+
else:
|
|
186
|
+
raise ValueError('Please specify the did')
|
|
187
|
+
|
|
188
|
+
def get(self, name: str, did: Optional[str] = None) -> Union[bool, int]:
|
|
189
|
+
"""
|
|
190
|
+
Get property value 获取设备的属性值
|
|
191
|
+
:param name: 属性名称
|
|
192
|
+
:param did: 设备ID(若未指定,则使用实例化时的did)
|
|
193
|
+
:return: 属性值
|
|
194
|
+
"""
|
|
195
|
+
if did is None:
|
|
196
|
+
did = self.did
|
|
197
|
+
if did is None:
|
|
198
|
+
raise ValueError('Please specify the did')
|
|
199
|
+
if name not in self.prop_list:
|
|
200
|
+
raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
|
|
201
|
+
prop = self.prop_list[name]
|
|
202
|
+
if 'r' not in prop.rw:
|
|
203
|
+
raise ValueError(f'Property {name} is write-only')
|
|
204
|
+
method = prop.method.copy()
|
|
205
|
+
method['did'] = did
|
|
206
|
+
result = self.api.get_devices_prop([method])[0]
|
|
207
|
+
if result['code'] != 0:
|
|
208
|
+
raise RuntimeError(
|
|
209
|
+
f"Failed to get property: {name}, "
|
|
210
|
+
f"code: {result['code']}, "
|
|
211
|
+
f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
|
|
212
|
+
)
|
|
213
|
+
sleep(self.sleep_time)
|
|
214
|
+
return result['value']
|
|
215
|
+
|
|
216
|
+
def __setattr__(self, name: str, value: Union[bool, int]) -> None:
|
|
217
|
+
"""
|
|
218
|
+
Set property value 设置设备的属性值(需在实例化时指定did)
|
|
219
|
+
:param name: 属性名称
|
|
220
|
+
:param value: 属性值
|
|
221
|
+
:return: None
|
|
222
|
+
"""
|
|
223
|
+
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
224
|
+
if not self.set_v2(name, value):
|
|
225
|
+
raise RuntimeError(f'Failed to set property: {name}')
|
|
226
|
+
else:
|
|
227
|
+
super().__setattr__(name, value)
|
|
228
|
+
|
|
229
|
+
def __getattr__(self, name: str) -> Union[bool, int]:
|
|
230
|
+
"""
|
|
231
|
+
Get property value 获取设备的属性值(需在实例化时指定did)
|
|
232
|
+
:param name: 属性名称
|
|
233
|
+
:return: 属性值
|
|
234
|
+
"""
|
|
235
|
+
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
236
|
+
return self.get(name)
|
|
237
|
+
else:
|
|
238
|
+
return super().__getattr__(name)
|
|
239
|
+
|
|
240
|
+
def run_action(
|
|
241
|
+
self,
|
|
242
|
+
name: str,
|
|
243
|
+
did: Optional[str] = None,
|
|
244
|
+
value: Optional[Union[list, tuple]] = None,
|
|
245
|
+
**kwargs
|
|
246
|
+
) -> bool:
|
|
247
|
+
"""
|
|
248
|
+
Run action 运行设备动作
|
|
249
|
+
:param name: 动作名称
|
|
250
|
+
:param did: 设备ID(若未指定,则使用实例化时的did)
|
|
251
|
+
:param value: 动作参数(若动作不需要参数,则不需要指定)
|
|
252
|
+
:param kwargs: 其它动作参数(若动作不需要参数,则不需要指定)
|
|
253
|
+
:return: 执行结果(True/False)
|
|
254
|
+
"""
|
|
255
|
+
if did is None:
|
|
256
|
+
did = self.did
|
|
257
|
+
if did is None:
|
|
258
|
+
raise ValueError('Please specify the did')
|
|
259
|
+
if name not in self.action_list:
|
|
260
|
+
raise ValueError(f'Unsupported action: {name}, available actions: {list(self.action_list.keys())}')
|
|
261
|
+
act = self.action_list[name]
|
|
262
|
+
method = act.method.copy()
|
|
263
|
+
method['did'] = did
|
|
264
|
+
if value is not None:
|
|
265
|
+
method['value'] = value
|
|
266
|
+
if kwargs:
|
|
267
|
+
for k, v in kwargs.items():
|
|
268
|
+
if k.startswith("_"):
|
|
269
|
+
k = k[1:]
|
|
270
|
+
if k in method:
|
|
271
|
+
raise ValueError(f'Invalid argument: {k}. Do not use arguments in ({", ".join(method.keys())})')
|
|
272
|
+
method[k] = v
|
|
273
|
+
result = self.api.run_action(method)
|
|
274
|
+
if result['code'] != 0:
|
|
275
|
+
raise RuntimeError(
|
|
276
|
+
f"Failed to run action: {name}, "
|
|
277
|
+
f"code: {result['code']}, "
|
|
278
|
+
f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
|
|
279
|
+
)
|
|
280
|
+
sleep(self.sleep_time)
|
|
281
|
+
return result['code'] == 0
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def get_device_info(device_model: str) -> dict:
|
|
285
|
+
"""
|
|
286
|
+
Get device info 获取设备信息
|
|
287
|
+
:param device_model: 设备型号
|
|
288
|
+
:return: 设备信息字典
|
|
289
|
+
"""
|
|
290
|
+
response = requests.get(deviceURL + device_model)
|
|
291
|
+
if response.status_code != 200:
|
|
292
|
+
raise RuntimeError(f'Failed to get device info')
|
|
293
|
+
content = re.search(r'data-page="(.*?)">', response.text)
|
|
294
|
+
if content is None:
|
|
295
|
+
raise RuntimeError(f'Failed to get device info')
|
|
296
|
+
content = content.group(1)
|
|
297
|
+
content = json.loads(content.replace('"', '"'))
|
|
298
|
+
|
|
299
|
+
result = {
|
|
300
|
+
'name': content['props']['product']['name'],
|
|
301
|
+
'model': content['props']['product']['model'],
|
|
302
|
+
'properties': [],
|
|
303
|
+
'actions': []
|
|
304
|
+
}
|
|
305
|
+
services = content['props']['spec']['services']
|
|
306
|
+
|
|
307
|
+
properties_name = []
|
|
308
|
+
actions_name = []
|
|
309
|
+
for siid in services:
|
|
310
|
+
if 'properties' in services[siid]:
|
|
311
|
+
for piid in services[siid]['properties']:
|
|
312
|
+
prop = services[siid]['properties'][piid]
|
|
313
|
+
if prop['format'].startswith('int'):
|
|
314
|
+
prop_type = 'int'
|
|
315
|
+
elif prop['format'].startswith('uint'):
|
|
316
|
+
prop_type = 'uint'
|
|
317
|
+
else:
|
|
318
|
+
prop_type = prop['format']
|
|
319
|
+
item = {
|
|
320
|
+
'name': prop['name'],
|
|
321
|
+
'description': prop['description'],
|
|
322
|
+
'type': prop_type,
|
|
323
|
+
'rw': ''.join([
|
|
324
|
+
'r' if 'read' in prop['access'] else '',
|
|
325
|
+
'w' if 'write' in prop['access'] else ''
|
|
326
|
+
]),
|
|
327
|
+
'unit': prop.get('unit', None),
|
|
328
|
+
'range': prop.get('value-range', None),
|
|
329
|
+
'value-list': prop.get('value-list', None),
|
|
330
|
+
'method': {
|
|
331
|
+
'siid': int(siid),
|
|
332
|
+
'piid': int(piid)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if item['name'] in properties_name:
|
|
336
|
+
item["name"] = f'{services[siid]["name"]}-{item["name"]}'
|
|
337
|
+
properties_name.append(item['name'])
|
|
338
|
+
result['properties'].append({k: None if v == 'none' else v for k, v in item.items()})
|
|
339
|
+
if 'actions' in services[siid]:
|
|
340
|
+
for aiid in services[siid]['actions']:
|
|
341
|
+
act = services[siid]['actions'][aiid]
|
|
342
|
+
if act['name'] in actions_name:
|
|
343
|
+
act['name'] = f'{services[siid]["name"]}-{act["name"]}'
|
|
344
|
+
actions_name.append(act['name'])
|
|
345
|
+
result['actions'].append({
|
|
346
|
+
'name': act['name'],
|
|
347
|
+
'description': act['description'],
|
|
348
|
+
'method': {
|
|
349
|
+
'siid': int(siid),
|
|
350
|
+
'aiid': int(aiid)
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
return result
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
class ColorFormatter(logging.Formatter):
|
|
5
|
+
COLORS = {
|
|
6
|
+
'DEBUG': '\033[36m', # 青色
|
|
7
|
+
'INFO': '\033[32m', # 绿色
|
|
8
|
+
'WARNING': '\033[33m', # 黄色
|
|
9
|
+
'ERROR': '\033[31m', # 红色
|
|
10
|
+
'CRITICAL': '\033[1;31m', # 加粗红色
|
|
11
|
+
'RESET': '\033[0m', # 重置颜色
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def __init__(self, fmt=None, datefmt=None, style='%'):
|
|
15
|
+
super().__init__(fmt, datefmt, style)
|
|
16
|
+
self.use_colors = sys.stdout.isatty()
|
|
17
|
+
|
|
18
|
+
def format(self, record):
|
|
19
|
+
log_message = super().format(record)
|
|
20
|
+
if self.use_colors:
|
|
21
|
+
color_code = self.COLORS.get(record.levelname, self.COLORS['RESET'])
|
|
22
|
+
return f"{color_code}{log_message}{self.COLORS['RESET']}"
|
|
23
|
+
return log_message
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger('mijiaAPI')
|
|
26
|
+
logger.setLevel(logging.INFO)
|
|
27
|
+
|
|
28
|
+
console_handler = logging.StreamHandler()
|
|
29
|
+
console_handler.setLevel(logging.INFO)
|
|
30
|
+
|
|
31
|
+
formatter = ColorFormatter(
|
|
32
|
+
'%(asctime)s - %(name)s - %(levelname)s: %(message)s',
|
|
33
|
+
datefmt='%Y-%m-%d %H:%M:%S',
|
|
34
|
+
)
|
|
35
|
+
console_handler.setFormatter(formatter)
|
|
36
|
+
|
|
37
|
+
logger.addHandler(console_handler)
|
|
@@ -12,6 +12,7 @@ import requests
|
|
|
12
12
|
|
|
13
13
|
from .urls import msgURL, loginURL, qrURL, accountURL
|
|
14
14
|
from .utils import defaultUA
|
|
15
|
+
from .logger import logger
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class LoginError(Exception):
|
|
@@ -66,9 +67,9 @@ class mijiaLogin(object):
|
|
|
66
67
|
self.save_path = os.path.abspath(self.save_path)
|
|
67
68
|
with open(self.save_path, 'w') as f:
|
|
68
69
|
json.dump(self.auth_data, f, indent=2)
|
|
69
|
-
|
|
70
|
+
logger.info(f'Auth data saved to [{self.save_path}]')
|
|
70
71
|
else:
|
|
71
|
-
|
|
72
|
+
logger.info('Auth data not saved')
|
|
72
73
|
|
|
73
74
|
def login(self, username: str, password: str) -> dict:
|
|
74
75
|
"""login with username and password
|
|
@@ -81,11 +82,7 @@ class mijiaLogin(object):
|
|
|
81
82
|
@return
|
|
82
83
|
dict, data for authorization, including userId, ssecurity, deviceId, serviceToken
|
|
83
84
|
"""
|
|
84
|
-
|
|
85
|
-
if sys.stdout.isatty():
|
|
86
|
-
print(f'\033[33;1m{warning_msg}\033[0m')
|
|
87
|
-
else:
|
|
88
|
-
print(warning_msg)
|
|
85
|
+
logger.warning('There is a high probability of verification code with account and password. Please try other login methods')
|
|
89
86
|
data = self._get_index()
|
|
90
87
|
post_data = {
|
|
91
88
|
'qs': data['qs'],
|
|
@@ -125,7 +122,7 @@ class mijiaLogin(object):
|
|
|
125
122
|
|
|
126
123
|
@staticmethod
|
|
127
124
|
def _print_qr(loginurl: str, box_size: int = 10) -> None:
|
|
128
|
-
|
|
125
|
+
logger.info('Scan the QR code below with Mi Home app')
|
|
129
126
|
qr = QRCode(border=1, box_size=box_size)
|
|
130
127
|
qr.add_data(loginurl)
|
|
131
128
|
qr.make_image().save('qr.png')
|
|
@@ -133,8 +130,10 @@ class mijiaLogin(object):
|
|
|
133
130
|
qr.print_ascii(invert=True, tty=True)
|
|
134
131
|
except OSError:
|
|
135
132
|
qr.print_ascii(invert=True, tty=False)
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
logger.info('If the QR code can not be scanned, '
|
|
134
|
+
'please change the font of the terminal, '
|
|
135
|
+
'such as "Maple Mono", "Fira Code", etc.\n'
|
|
136
|
+
'Or just use the qr.png file in the current directory.')
|
|
138
137
|
|
|
139
138
|
def QRlogin(self) -> dict:
|
|
140
139
|
"""login with QR code
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
sid = 'xiaomiio'
|
|
2
|
-
msgURL = f'https://account.xiaomi.com/pass/serviceLogin?sid={sid}&_json=true'
|
|
3
|
-
loginURL = 'https://account.xiaomi.com/pass/serviceLoginAuth2'
|
|
4
|
-
qrURL = 'https://account.xiaomi.com/longPolling/loginUrl'
|
|
5
|
-
apiURL = 'https://api.io.mi.com/app'
|
|
6
|
-
deviceURL = 'https://home.miot-spec.com/spec/'
|
|
7
|
-
accountURL = 'https://account.xiaomi.com/pass2/profile/home?bizFlag=&userId='
|
|
1
|
+
sid = 'xiaomiio'
|
|
2
|
+
msgURL = f'https://account.xiaomi.com/pass/serviceLogin?sid={sid}&_json=true'
|
|
3
|
+
loginURL = 'https://account.xiaomi.com/pass/serviceLoginAuth2'
|
|
4
|
+
qrURL = 'https://account.xiaomi.com/longPolling/loginUrl'
|
|
5
|
+
apiURL = 'https://api.io.mi.com/app'
|
|
6
|
+
deviceURL = 'https://home.miot-spec.com/spec/'
|
|
7
|
+
accountURL = 'https://account.xiaomi.com/pass2/profile/home?bizFlag=&userId='
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mijiaAPI"
|
|
3
|
-
version = "1.3.
|
|
3
|
+
version = "1.3.10"
|
|
4
4
|
description = "A Python API for Xiaomi Mijia"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.9,<4.0"
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
|
|
13
13
|
[tool.poetry]
|
|
14
14
|
name = "mijiaAPI"
|
|
15
|
-
version = "1.3.
|
|
15
|
+
version = "1.3.10"
|
|
16
16
|
description = "A Python API for Xiaomi Mijia"
|
|
17
17
|
authors = ["Do1e <dpj.email@qq.com>"]
|
|
18
18
|
license = "GPLv3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|