mijiaAPI 1.3.10__tar.gz → 1.3.12__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.10 → mijiaapi-1.3.12}/PKG-INFO +1 -1
- mijiaapi-1.3.12/mijiaAPI/__main__.py +27 -0
- mijiaapi-1.3.12/mijiaAPI/apis.py +186 -0
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/mijiaAPI/devices.py +130 -50
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/mijiaAPI/login.py +78 -22
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/pyproject.toml +2 -2
- mijiaapi-1.3.10/mijiaAPI/apis.py +0 -171
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/LICENSE +0 -0
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/README.md +0 -0
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/mijiaAPI/__init__.py +0 -0
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/mijiaAPI/code.py +0 -0
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/mijiaAPI/logger.py +0 -0
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/mijiaAPI/urls.py +0 -0
- {mijiaapi-1.3.10 → mijiaapi-1.3.12}/mijiaAPI/utils.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .login import mijiaLogin
|
|
5
|
+
|
|
6
|
+
def parse_args(args):
|
|
7
|
+
parser = argparse.ArgumentParser(description="Mijia API CLI")
|
|
8
|
+
parser.add_argument(
|
|
9
|
+
'-i', '--login',
|
|
10
|
+
action='store_true',
|
|
11
|
+
help="Login by QR code and save cookies to file",
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
'-c', '--cookie_path',
|
|
15
|
+
type=str,
|
|
16
|
+
help="Path to the cookies file",
|
|
17
|
+
)
|
|
18
|
+
return parser.parse_args(args)
|
|
19
|
+
|
|
20
|
+
def main(args):
|
|
21
|
+
args = parse_args(args)
|
|
22
|
+
if args.login:
|
|
23
|
+
api = mijiaLogin(save_path=args.cookie_path)
|
|
24
|
+
auth = api.QRlogin()
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
main(sys.argv[1:])
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import requests
|
|
3
|
+
import requests.cookies
|
|
4
|
+
|
|
5
|
+
from .utils import defaultUA, post_data, PostDataError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class mijiaAPI(object):
|
|
9
|
+
def __init__(self, auth_data: dict):
|
|
10
|
+
"""
|
|
11
|
+
初始化mijiaAPI对象。
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
auth_data (dict): 包含授权信息的字典,必须包含'userId'、'deviceId'、'ssecurity'和'serviceToken'。
|
|
15
|
+
|
|
16
|
+
Raises:
|
|
17
|
+
Exception: 当授权数据不完整时抛出异常。
|
|
18
|
+
"""
|
|
19
|
+
if any(k not in auth_data for k in ['userId', 'deviceId', 'ssecurity', 'serviceToken']):
|
|
20
|
+
raise Exception('Invalid authorize data')
|
|
21
|
+
self.userId = auth_data['userId']
|
|
22
|
+
self.ssecurity = auth_data['ssecurity']
|
|
23
|
+
self.session = requests.Session()
|
|
24
|
+
self.session.headers.update({
|
|
25
|
+
'User-Agent': defaultUA,
|
|
26
|
+
'x-xiaomi-protocal-flag-cli': 'PROTOCAL-HTTP2',
|
|
27
|
+
'Cookie': f'PassportDeviceId={auth_data["deviceId"]};'
|
|
28
|
+
f'userId={auth_data["userId"]};'
|
|
29
|
+
f'serviceToken={auth_data["serviceToken"]};',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _post_process(data: dict) -> Union[list, bool]:
|
|
34
|
+
if data['code'] != 0:
|
|
35
|
+
raise Exception(f'Failed to get data, {data["message"]}')
|
|
36
|
+
return data['result']
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def available(self) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
检查API是否可用。
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
bool: API可用返回True,否则返回False。
|
|
45
|
+
"""
|
|
46
|
+
uri = '/home/device_list'
|
|
47
|
+
data = {"getVirtualModel": False, "getHuamiDevices": 0}
|
|
48
|
+
try:
|
|
49
|
+
post_data(self.session, self.ssecurity, uri, data)
|
|
50
|
+
return True
|
|
51
|
+
except PostDataError:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def get_devices_list(self) -> dict:
|
|
55
|
+
"""
|
|
56
|
+
获取设备列表。
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
dict: 设备列表。
|
|
60
|
+
"""
|
|
61
|
+
uri = '/home/device_list'
|
|
62
|
+
data = {"getVirtualModel": False, "getHuamiDevices": 0}
|
|
63
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
64
|
+
|
|
65
|
+
def get_homes_list(self) -> list:
|
|
66
|
+
"""
|
|
67
|
+
获取家庭列表。
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
list: 家庭列表,包括房间信息。
|
|
71
|
+
"""
|
|
72
|
+
uri = '/v2/homeroom/gethome'
|
|
73
|
+
data = {"fg": False, "fetch_share": True, "fetch_share_dev": True, "limit": 300, "app_ver": 7}
|
|
74
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
75
|
+
|
|
76
|
+
def get_scenes_list(self, home_id: str) -> list:
|
|
77
|
+
"""
|
|
78
|
+
获取场景列表。
|
|
79
|
+
|
|
80
|
+
在米家APP中通过"添加 -> 手动控制"设置。
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
home_id (str): 家庭ID,从get_homes_list获取。
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
list: 场景列表。
|
|
87
|
+
"""
|
|
88
|
+
uri = '/appgateway/miot/appsceneservice/AppSceneService/GetSceneList'
|
|
89
|
+
data = {"home_id": home_id}
|
|
90
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
91
|
+
|
|
92
|
+
def run_scene(self, scene_id: str) -> bool:
|
|
93
|
+
"""
|
|
94
|
+
运行场景。
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
scene_id (str): 场景ID,从get_scenes_list获取。
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
bool: 操作结果。
|
|
101
|
+
"""
|
|
102
|
+
uri = '/appgateway/miot/appsceneservice/AppSceneService/RunScene'
|
|
103
|
+
data = {"scene_id": scene_id, "trigger_key": "user.click"}
|
|
104
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
105
|
+
|
|
106
|
+
def get_consumable_items(self, home_id: str) -> list:
|
|
107
|
+
"""
|
|
108
|
+
获取耗材列表。
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
home_id (str): 家庭ID,从get_homes_list获取。
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
list: 耗材列表。
|
|
115
|
+
"""
|
|
116
|
+
uri = '/v2/home/standard_consumable_items'
|
|
117
|
+
data = {"home_id": int(home_id), "owner_id": self.userId}
|
|
118
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
119
|
+
|
|
120
|
+
def get_devices_prop(self, data: list) -> list:
|
|
121
|
+
"""
|
|
122
|
+
获取设备属性。
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
data (list): 设备属性请求列表,每项为包含以下键的字典:
|
|
126
|
+
- did: 设备ID,从get_devices_list获取
|
|
127
|
+
- siid: 服务ID,从 https://home.miot-spec.com/spec/{model} 获取,model从get_devices_list获取
|
|
128
|
+
- piid: 属性ID,从 https://home.miot-spec.com/spec/{model} 获取,model从get_devices_list获取
|
|
129
|
+
|
|
130
|
+
示例(yeelink.light.lamp4):
|
|
131
|
+
[
|
|
132
|
+
{"did": "1234567890", "siid": 2, "piid": 2}, # 获取亮度
|
|
133
|
+
{"did": "1234567890", "siid": 2, "piid": 3}, # 获取色温
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
list: 设备属性信息列表。
|
|
138
|
+
"""
|
|
139
|
+
uri = '/miotspec/prop/get'
|
|
140
|
+
data = {"params": data}
|
|
141
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
142
|
+
|
|
143
|
+
def set_devices_prop(self, data: list) -> list:
|
|
144
|
+
"""
|
|
145
|
+
设置设备属性。
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
data (list): 设备属性设置列表,每项为包含以下键的字典:
|
|
149
|
+
- did: 设备ID,从get_devices_list获取
|
|
150
|
+
- siid: 服务ID,从 https://home.miot-spec.com/spec/{model} 获取,model从get_devices_list获取
|
|
151
|
+
- piid: 属性ID,从 https://home.miot-spec.com/spec/{model} 获取,model从get_devices_list获取
|
|
152
|
+
- value: 要设置的值
|
|
153
|
+
|
|
154
|
+
示例(yeelink.light.lamp4):
|
|
155
|
+
[
|
|
156
|
+
{"did": "1234567890", "siid": 2, "piid": 2, "value": 50} # 设置亮度为50%
|
|
157
|
+
{"did": "1234567890", "siid": 2, "piid": 3, "value": 2700} # 设置色温为2700K
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
list: 操作结果。
|
|
162
|
+
"""
|
|
163
|
+
uri = '/miotspec/prop/set'
|
|
164
|
+
data = {"params": data}
|
|
165
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
166
|
+
|
|
167
|
+
def run_action(self, data: dict) -> dict:
|
|
168
|
+
"""
|
|
169
|
+
执行设备动作。
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
data (dict): 动作请求,包含以下键:
|
|
173
|
+
- did: 设备ID,从get_devices_list获取
|
|
174
|
+
- siid: 服务ID,从 https://home.miot-spec.com/spec/{model} 获取,model从get_devices_list获取
|
|
175
|
+
- aiid: 动作ID,从 https://home.miot-spec.com/spec/{model} 获取,model从get_devices_list获取
|
|
176
|
+
- value: 参数列表
|
|
177
|
+
|
|
178
|
+
示例(xiaomi.feeder.pi2001):
|
|
179
|
+
{"did": "1234567890", "siid": 2, "aiid": 1, "value": [2]}, # 远程喂食2份
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
dict: 操作结果。
|
|
183
|
+
"""
|
|
184
|
+
uri = '/miotspec/action'
|
|
185
|
+
data = {"params": data}
|
|
186
|
+
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
@@ -11,8 +11,13 @@ from .logger import logger
|
|
|
11
11
|
class DevProp(object):
|
|
12
12
|
def __init__(self, prop_dict: dict):
|
|
13
13
|
"""
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
初始化属性对象。
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
prop_dict (dict): 属性字典。
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: 如果属性类型不受支持。
|
|
16
21
|
"""
|
|
17
22
|
self.name = prop_dict['name']
|
|
18
23
|
self.desc = prop_dict['description']
|
|
@@ -27,8 +32,10 @@ class DevProp(object):
|
|
|
27
32
|
|
|
28
33
|
def __str__(self):
|
|
29
34
|
"""
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
返回属性的字符串表示。
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
str: 属性的名称、描述、类型、读写权限、单位和范围。
|
|
32
39
|
"""
|
|
33
40
|
lines = [
|
|
34
41
|
f" {self.name}: {self.desc}",
|
|
@@ -44,11 +51,23 @@ class DevProp(object):
|
|
|
44
51
|
|
|
45
52
|
class DevAction(object):
|
|
46
53
|
def __init__(self, act_dict: dict):
|
|
54
|
+
"""
|
|
55
|
+
初始化动作对象。
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
act_dict (dict): 动作字典。
|
|
59
|
+
"""
|
|
47
60
|
self.name = act_dict['name']
|
|
48
61
|
self.desc = act_dict['description']
|
|
49
62
|
self.method = act_dict['method']
|
|
50
63
|
|
|
51
64
|
def __str__(self):
|
|
65
|
+
"""
|
|
66
|
+
返回动作的字符串表示。
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
str: 动作的名称和描述。
|
|
70
|
+
"""
|
|
52
71
|
return f' {self.name}: {self.desc}'
|
|
53
72
|
|
|
54
73
|
|
|
@@ -62,16 +81,29 @@ class mijiaDevices(object):
|
|
|
62
81
|
sleep_time: Optional[Union[int, float]] = 0.5
|
|
63
82
|
):
|
|
64
83
|
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
:
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
初始化设备对象。
|
|
85
|
+
|
|
86
|
+
如果未提供设备信息,则根据设备名称获取设备信息。如果两者均未提供,则抛出异常。
|
|
87
|
+
如果同时提供了设备信息和设备名称,则以设备信息为准。
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
api (mijiaAPI): 米家API对象。
|
|
91
|
+
dev_info (dict, optional): 设备信息字典,从get_device_info获取。默认为None。
|
|
92
|
+
dev_name (str, optional): 设备名称,从get_devices_list获取。默认为None。
|
|
93
|
+
did (str, optional): 设备ID,如未指定,则需要在调用get/set时指定。默认为None。
|
|
94
|
+
sleep_time ([int, float], optional): 调用设备属性的间隔时间。默认为0.5秒。
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
RuntimeError: 如果dev_info和dev_name都未提供。
|
|
98
|
+
ValueError: 如果找不到指定设备或找到多个同名设备。
|
|
99
|
+
|
|
100
|
+
Note:
|
|
101
|
+
- 如果同时提供了dev_info和dev_name,则以dev_info为准。
|
|
102
|
+
- 如果只提供了dev_name,则根据名称自动获取设备信息。
|
|
103
|
+
- 如果只提供了dev_info,则直接使用该信息。
|
|
73
104
|
"""
|
|
74
|
-
|
|
105
|
+
if dev_info is None and dev_name is None:
|
|
106
|
+
raise RuntimeError("Either 'dev_info' or 'dev_name' must be provided.")
|
|
75
107
|
if dev_info is not None and dev_name is not None:
|
|
76
108
|
logger.warning("Both 'dev_info' and 'dev_name' provided. Using 'dev_info' for initialization.")
|
|
77
109
|
|
|
@@ -106,8 +138,10 @@ class mijiaDevices(object):
|
|
|
106
138
|
|
|
107
139
|
def __str__(self) -> str:
|
|
108
140
|
"""
|
|
109
|
-
|
|
110
|
-
|
|
141
|
+
返回设备的字符串表示。
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
str: 设备的属性和动作列表。
|
|
111
145
|
"""
|
|
112
146
|
prop_list_str = '\n'.join(filter(None, (str(v) for k, v in self.prop_list.items() if '_' not in k)))
|
|
113
147
|
action_list_str = '\n'.join(map(str, self.action_list.values()))
|
|
@@ -115,13 +149,21 @@ class mijiaDevices(object):
|
|
|
115
149
|
f"Properties:\n{prop_list_str if prop_list_str else 'No properties available'}\n"
|
|
116
150
|
f"Actions:\n{action_list_str if action_list_str else 'No actions available'}")
|
|
117
151
|
|
|
118
|
-
def set(self, name: str, did: str, value: Union[bool, int]) ->
|
|
152
|
+
def set(self, name: str, did: str, value: Union[bool, int, float, str]) -> bool:
|
|
119
153
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
:
|
|
123
|
-
|
|
124
|
-
|
|
154
|
+
设置设备的属性值。
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
name (str): 属性名称。
|
|
158
|
+
did (str): 设备ID。
|
|
159
|
+
value (Union[bool, int, float, str]): 属性值。
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
bool: 执行结果(True/False)。
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ValueError: 如果属性不存在、属性为只读或值无效。
|
|
166
|
+
RuntimeError: 如果设置属性失败。
|
|
125
167
|
"""
|
|
126
168
|
if name not in self.prop_list:
|
|
127
169
|
raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
|
|
@@ -170,13 +212,20 @@ class mijiaDevices(object):
|
|
|
170
212
|
sleep(self.sleep_time)
|
|
171
213
|
return result['code'] == 0
|
|
172
214
|
|
|
173
|
-
def set_v2(self, name: str, value: Union[bool, int], did: Optional[str] = None) ->
|
|
215
|
+
def set_v2(self, name: str, value: Union[bool, int, float, str], did: Optional[str] = None) -> bool:
|
|
174
216
|
"""
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
:
|
|
178
|
-
|
|
179
|
-
|
|
217
|
+
设置设备的属性值(v2版本,需在实例化时指定did或在调用时提供)。
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
name (str): 属性名称。
|
|
221
|
+
value (Union[bool, int, float, str]): 属性值。
|
|
222
|
+
did (str, optional): 设备ID。如未指定,则使用实例化时的did。默认为None。
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
bool: 执行结果(True/False)。
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
ValueError: 如果未指定设备ID。
|
|
180
229
|
"""
|
|
181
230
|
if did is not None:
|
|
182
231
|
return self.set(name, did, value)
|
|
@@ -185,12 +234,20 @@ class mijiaDevices(object):
|
|
|
185
234
|
else:
|
|
186
235
|
raise ValueError('Please specify the did')
|
|
187
236
|
|
|
188
|
-
def get(self, name: str, did: Optional[str] = None) -> Union[bool, int]:
|
|
237
|
+
def get(self, name: str, did: Optional[str] = None) -> Union[bool, int, float, str]:
|
|
189
238
|
"""
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
:
|
|
193
|
-
|
|
239
|
+
获取设备的属性值。
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
name (str): 属性名称。
|
|
243
|
+
did (str, optional): 设备ID。如未指定,则使用实例化时的did。默认为None。
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Union[bool, int, float, str]: 属性值。
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
ValueError: 如果未指定设备ID、属性不存在或属性为只写。
|
|
250
|
+
RuntimeError: 如果获取属性失败。
|
|
194
251
|
"""
|
|
195
252
|
if did is None:
|
|
196
253
|
did = self.did
|
|
@@ -213,12 +270,16 @@ class mijiaDevices(object):
|
|
|
213
270
|
sleep(self.sleep_time)
|
|
214
271
|
return result['value']
|
|
215
272
|
|
|
216
|
-
def __setattr__(self, name: str, value: Union[bool, int]) -> None:
|
|
273
|
+
def __setattr__(self, name: str, value: Union[bool, int, float, str]) -> None:
|
|
217
274
|
"""
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
:
|
|
221
|
-
|
|
275
|
+
设置设备的属性值(通过对象属性方式,需在实例化时指定did)。
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
name (str): 属性名称。
|
|
279
|
+
value (Union[bool, int, float, str]): 属性值。
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
RuntimeError: 如果设置属性失败。
|
|
222
283
|
"""
|
|
223
284
|
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
224
285
|
if not self.set_v2(name, value):
|
|
@@ -226,11 +287,15 @@ class mijiaDevices(object):
|
|
|
226
287
|
else:
|
|
227
288
|
super().__setattr__(name, value)
|
|
228
289
|
|
|
229
|
-
def __getattr__(self, name: str) -> Union[bool, int]:
|
|
290
|
+
def __getattr__(self, name: str) -> Union[bool, int, float, str]:
|
|
230
291
|
"""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
:
|
|
292
|
+
获取设备的属性值(通过对象属性方式,需在实例化时指定did)。
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
name (str): 属性名称。
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Union[bool, int, float, str]: 属性值。
|
|
234
299
|
"""
|
|
235
300
|
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
236
301
|
return self.get(name)
|
|
@@ -245,12 +310,20 @@ class mijiaDevices(object):
|
|
|
245
310
|
**kwargs
|
|
246
311
|
) -> bool:
|
|
247
312
|
"""
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
313
|
+
运行设备动作。
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
name (str): 动作名称。
|
|
317
|
+
did (Optional[str], optional): 设备ID。如未指定,则使用实例化时的did。默认为None。
|
|
318
|
+
value (Optional[Union[list, tuple]], optional): 动作参数。如动作不需要参数,则不需指定。默认为None。
|
|
319
|
+
**kwargs: 其它动作参数,如智能音箱的in参数[run_action('execute-text-directive', _in=['空调调至26度', True])]。
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
bool: 执行结果(True/False)。
|
|
323
|
+
|
|
324
|
+
Raises:
|
|
325
|
+
ValueError: 如果未指定设备ID、动作不存在或参数无效。
|
|
326
|
+
RuntimeError: 如果运行动作失败。
|
|
254
327
|
"""
|
|
255
328
|
if did is None:
|
|
256
329
|
did = self.did
|
|
@@ -283,9 +356,16 @@ class mijiaDevices(object):
|
|
|
283
356
|
|
|
284
357
|
def get_device_info(device_model: str) -> dict:
|
|
285
358
|
"""
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
:
|
|
359
|
+
获取设备信息,用于初始化mijiaDevices对象。
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
device_model (str): 设备型号,从get_devices_list获取。
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
dict: 设备信息字典。
|
|
366
|
+
|
|
367
|
+
Raises:
|
|
368
|
+
RuntimeError: 如果获取设备信息失败。
|
|
289
369
|
"""
|
|
290
370
|
response = requests.get(deviceURL + device_model)
|
|
291
371
|
if response.status_code != 200:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
from typing import Optional
|
|
1
2
|
import hashlib
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
4
5
|
import random
|
|
5
6
|
import string
|
|
6
|
-
import sys
|
|
7
7
|
import time
|
|
8
8
|
from urllib import parse
|
|
9
9
|
|
|
@@ -17,15 +17,27 @@ from .logger import logger
|
|
|
17
17
|
|
|
18
18
|
class LoginError(Exception):
|
|
19
19
|
def __init__(self, code: int, message: str):
|
|
20
|
+
"""
|
|
21
|
+
初始化登录错误异常。
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
code (int): 错误代码。
|
|
25
|
+
message (str): 错误消息。
|
|
26
|
+
"""
|
|
20
27
|
self.code = code
|
|
21
28
|
self.message = message
|
|
22
29
|
super().__init__(f'Error code: {code}, message: {message}')
|
|
23
30
|
|
|
24
31
|
|
|
25
32
|
class mijiaLogin(object):
|
|
26
|
-
def __init__(self,
|
|
33
|
+
def __init__(self, save_path: Optional[str] = None):
|
|
34
|
+
"""
|
|
35
|
+
初始化米家登录对象。
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
save_path (str, optional): 认证数据保存路径。默认为None。
|
|
39
|
+
"""
|
|
27
40
|
self.auth_data = None
|
|
28
|
-
self.save_auth = save_auth
|
|
29
41
|
self.save_path = save_path
|
|
30
42
|
|
|
31
43
|
self.deviceId = ''.join(random.sample(string.digits + string.ascii_letters, 16))
|
|
@@ -39,6 +51,15 @@ class mijiaLogin(object):
|
|
|
39
51
|
})
|
|
40
52
|
|
|
41
53
|
def _get_index(self) -> dict[str, str]:
|
|
54
|
+
"""
|
|
55
|
+
获取索引页数据。
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
dict[str, str]: 包含设备ID和其他必要参数的字典。
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
LoginError: 请求索引页失败时抛出。
|
|
62
|
+
"""
|
|
42
63
|
ret = self.session.get(msgURL)
|
|
43
64
|
if ret.status_code != 200:
|
|
44
65
|
raise LoginError(ret.status_code, f'Failed to get index page, {ret.text}')
|
|
@@ -51,6 +72,18 @@ class mijiaLogin(object):
|
|
|
51
72
|
return data
|
|
52
73
|
|
|
53
74
|
def _get_account(self, user_id: str) -> dict[str, str]:
|
|
75
|
+
"""
|
|
76
|
+
获取账户信息。
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
user_id (str): 用户ID。
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
dict[str, str]: 包含账户信息的字典。
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
LoginError: 获取账户信息失败时抛出。
|
|
86
|
+
"""
|
|
54
87
|
ret = self.session.get(accountURL + str(user_id))
|
|
55
88
|
if ret.status_code != 200:
|
|
56
89
|
raise LoginError(ret.status_code, f'Failed to get account page, {ret.text}')
|
|
@@ -62,9 +95,18 @@ class mijiaLogin(object):
|
|
|
62
95
|
return data
|
|
63
96
|
|
|
64
97
|
def _save_auth(self) -> None:
|
|
65
|
-
|
|
98
|
+
"""
|
|
99
|
+
保存认证数据到文件。
|
|
100
|
+
|
|
101
|
+
如果设置了保存路径且有认证数据,则将其以JSON格式保存到指定路径。
|
|
102
|
+
"""
|
|
103
|
+
if self.save_path is not None and self.auth_data is not None:
|
|
66
104
|
if not os.path.isabs(self.save_path):
|
|
67
105
|
self.save_path = os.path.abspath(self.save_path)
|
|
106
|
+
if os.path.exists(self.save_path) and not os.path.isfile(self.save_path):
|
|
107
|
+
raise ValueError(f'Path [{self.save_path}] is not a file')
|
|
108
|
+
if not os.path.exists(os.path.dirname(self.save_path)):
|
|
109
|
+
os.makedirs(os.path.dirname(self.save_path))
|
|
68
110
|
with open(self.save_path, 'w') as f:
|
|
69
111
|
json.dump(self.auth_data, f, indent=2)
|
|
70
112
|
logger.info(f'Auth data saved to [{self.save_path}]')
|
|
@@ -72,15 +114,18 @@ class mijiaLogin(object):
|
|
|
72
114
|
logger.info('Auth data not saved')
|
|
73
115
|
|
|
74
116
|
def login(self, username: str, password: str) -> dict:
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
117
|
+
"""
|
|
118
|
+
使用用户名和密码登录。
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
username (str): 小米账户用户名(邮箱/手机号/小米ID)。
|
|
122
|
+
password (str): 小米账户密码。
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
dict: 授权数据,包含userId、ssecurity、deviceId和serviceToken。
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
LoginError: 登录失败时抛出。
|
|
84
129
|
"""
|
|
85
130
|
logger.warning('There is a high probability of verification code with account and password. Please try other login methods')
|
|
86
131
|
data = self._get_index()
|
|
@@ -116,12 +161,18 @@ class mijiaLogin(object):
|
|
|
116
161
|
self.auth_data = auth_data
|
|
117
162
|
self.auth_data.update(self._get_account(auth_data['userId']))
|
|
118
163
|
|
|
119
|
-
|
|
120
|
-
self._save_auth()
|
|
164
|
+
self._save_auth()
|
|
121
165
|
return auth_data
|
|
122
166
|
|
|
123
167
|
@staticmethod
|
|
124
168
|
def _print_qr(loginurl: str, box_size: int = 10) -> None:
|
|
169
|
+
"""
|
|
170
|
+
打印并保存二维码。
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
loginurl (str): 包含登录信息的URL。
|
|
174
|
+
box_size (int, optional): 二维码大小。默认为10。
|
|
175
|
+
"""
|
|
125
176
|
logger.info('Scan the QR code below with Mi Home app')
|
|
126
177
|
qr = QRCode(border=1, box_size=box_size)
|
|
127
178
|
qr.add_data(loginurl)
|
|
@@ -136,11 +187,14 @@ class mijiaLogin(object):
|
|
|
136
187
|
'Or just use the qr.png file in the current directory.')
|
|
137
188
|
|
|
138
189
|
def QRlogin(self) -> dict:
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
190
|
+
"""
|
|
191
|
+
使用二维码登录。
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
dict: 授权数据,包含userId、ssecurity、deviceId和serviceToken。
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
LoginError: 登录失败时抛出。
|
|
144
198
|
"""
|
|
145
199
|
data = self._get_index()
|
|
146
200
|
location = data['location']
|
|
@@ -191,10 +245,12 @@ class mijiaLogin(object):
|
|
|
191
245
|
self.auth_data = auth_data
|
|
192
246
|
self.auth_data.update(self._get_account(auth_data['userId']))
|
|
193
247
|
|
|
194
|
-
|
|
195
|
-
self._save_auth()
|
|
248
|
+
self._save_auth()
|
|
196
249
|
return auth_data
|
|
197
250
|
|
|
198
251
|
def __del__(self):
|
|
252
|
+
"""
|
|
253
|
+
析构函数,清理生成的二维码文件。
|
|
254
|
+
"""
|
|
199
255
|
if os.path.exists('qr.png'):
|
|
200
256
|
os.remove('qr.png')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mijiaAPI"
|
|
3
|
-
version = "1.3.
|
|
3
|
+
version = "1.3.12"
|
|
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.12"
|
|
16
16
|
description = "A Python API for Xiaomi Mijia"
|
|
17
17
|
authors = ["Do1e <dpj.email@qq.com>"]
|
|
18
18
|
license = "GPLv3"
|
mijiaapi-1.3.10/mijiaAPI/apis.py
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
import requests
|
|
3
|
-
import requests.cookies
|
|
4
|
-
|
|
5
|
-
from .code import ERROR_CODE
|
|
6
|
-
from .utils import defaultUA, post_data, PostDataError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class mijiaAPI(object):
|
|
10
|
-
def __init__(self, auth_data: dict):
|
|
11
|
-
if any(k not in auth_data for k in ['userId', 'deviceId', 'ssecurity', 'serviceToken']):
|
|
12
|
-
raise Exception('Invalid authorize data')
|
|
13
|
-
self.userId = auth_data['userId']
|
|
14
|
-
self.ssecurity = auth_data['ssecurity']
|
|
15
|
-
self.session = requests.Session()
|
|
16
|
-
self.session.headers.update({
|
|
17
|
-
'User-Agent': defaultUA,
|
|
18
|
-
'x-xiaomi-protocal-flag-cli': 'PROTOCAL-HTTP2',
|
|
19
|
-
'Cookie': f'PassportDeviceId={auth_data["deviceId"]};'
|
|
20
|
-
f'userId={auth_data["userId"]};'
|
|
21
|
-
f'serviceToken={auth_data["serviceToken"]};',
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
@staticmethod
|
|
25
|
-
def _post_process(data: dict) -> Union[list, bool]:
|
|
26
|
-
if data['code'] != 0:
|
|
27
|
-
raise Exception(f'Failed to get data, {data["message"]}')
|
|
28
|
-
return data['result']
|
|
29
|
-
|
|
30
|
-
@property
|
|
31
|
-
def available(self) -> bool:
|
|
32
|
-
"""check if the API is available"""
|
|
33
|
-
uri = '/home/device_list'
|
|
34
|
-
data = {"getVirtualModel": False, "getHuamiDevices": 0}
|
|
35
|
-
try:
|
|
36
|
-
post_data(self.session, self.ssecurity, uri, data)
|
|
37
|
-
return True
|
|
38
|
-
except PostDataError:
|
|
39
|
-
return False
|
|
40
|
-
|
|
41
|
-
def get_devices_list(self) -> dict:
|
|
42
|
-
"""get devices list
|
|
43
|
-
mijiaAPI.get_devices_list() -> dict
|
|
44
|
-
-------
|
|
45
|
-
@return
|
|
46
|
-
dict, devices list
|
|
47
|
-
"""
|
|
48
|
-
uri = '/home/device_list'
|
|
49
|
-
data = {"getVirtualModel": False, "getHuamiDevices": 0}
|
|
50
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
51
|
-
|
|
52
|
-
def get_homes_list(self) -> list:
|
|
53
|
-
"""get homes list
|
|
54
|
-
mijiaAPI.get_homes_list() -> list
|
|
55
|
-
-------
|
|
56
|
-
@return
|
|
57
|
-
list, homes list, including rooms
|
|
58
|
-
"""
|
|
59
|
-
uri = '/v2/homeroom/gethome'
|
|
60
|
-
data = {"fg": False, "fetch_share": True, "fetch_share_dev": True, "limit": 300, "app_ver": 7}
|
|
61
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
62
|
-
|
|
63
|
-
def get_scenes_list(self, home_id: str) -> list:
|
|
64
|
-
"""get scenes list
|
|
65
|
-
set it in Mi Home APP -> Add -> Manual controls
|
|
66
|
-
mijiaAPI.get_scenes_list(home_id: str) -> list
|
|
67
|
-
-------
|
|
68
|
-
@param
|
|
69
|
-
home_id: str, room id, get from get_homes_list
|
|
70
|
-
-------
|
|
71
|
-
@return
|
|
72
|
-
list, scenes list
|
|
73
|
-
"""
|
|
74
|
-
uri = '/appgateway/miot/appsceneservice/AppSceneService/GetSceneList'
|
|
75
|
-
data = {"home_id": home_id}
|
|
76
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
77
|
-
|
|
78
|
-
def run_scene(self, scene_id: str) -> bool:
|
|
79
|
-
"""run scene
|
|
80
|
-
mijiaAPI.run_scene(scene_id: str) -> bool
|
|
81
|
-
-------
|
|
82
|
-
@param
|
|
83
|
-
scene_id: str, scene id, get from get_scenes_list
|
|
84
|
-
-------
|
|
85
|
-
@return
|
|
86
|
-
dict, result
|
|
87
|
-
"""
|
|
88
|
-
uri = '/appgateway/miot/appsceneservice/AppSceneService/RunScene'
|
|
89
|
-
data = {"scene_id": scene_id, "trigger_key": "user.click"}
|
|
90
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
91
|
-
|
|
92
|
-
def get_consumable_items(self, home_id: str) -> list:
|
|
93
|
-
"""get consumable items
|
|
94
|
-
mijiaAPI.get_consumable_items(did: str) -> list
|
|
95
|
-
-------
|
|
96
|
-
@param
|
|
97
|
-
home_id: str, room id, get from get_homes_list
|
|
98
|
-
-------
|
|
99
|
-
@return
|
|
100
|
-
list, consumable items
|
|
101
|
-
"""
|
|
102
|
-
uri = '/v2/home/standard_consumable_items'
|
|
103
|
-
data = {"home_id": int(home_id), "owner_id": self.userId}
|
|
104
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
105
|
-
|
|
106
|
-
def get_devices_prop(self, data: list) -> list:
|
|
107
|
-
"""get devices properties
|
|
108
|
-
mijiaAPI.get_devices_prop(data: list) -> list
|
|
109
|
-
-------
|
|
110
|
-
@param
|
|
111
|
-
data: list of dict
|
|
112
|
-
dict keys:
|
|
113
|
-
- did: str, device id, get from get_devices_list
|
|
114
|
-
- siid: str, service id, get from https://home.miot-spec.com/spec/{model}, model from get_devices_list
|
|
115
|
-
- piid: str, property id, get from https://home.miot-spec.com/spec/{model}, model from get_devices_list
|
|
116
|
-
model yeelink.light.lamp4 as an example:
|
|
117
|
-
[
|
|
118
|
-
{"did": "1234567890", "siid": 2, "piid": 2}, # get the brightness
|
|
119
|
-
{"did": "1234567890", "siid": 2, "piid": 3}, # get the color temperature
|
|
120
|
-
]
|
|
121
|
-
-------
|
|
122
|
-
@return
|
|
123
|
-
list, device properties
|
|
124
|
-
"""
|
|
125
|
-
uri = '/miotspec/prop/get'
|
|
126
|
-
data = {"params": data}
|
|
127
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
128
|
-
|
|
129
|
-
def set_devices_prop(self, data: list) -> list:
|
|
130
|
-
"""set devices properties
|
|
131
|
-
mijiaAPI.set_devices_prop(data: list) -> list
|
|
132
|
-
-------
|
|
133
|
-
@param
|
|
134
|
-
data: list of dict
|
|
135
|
-
dict keys:
|
|
136
|
-
- did: str, device id, get from get_devices_list
|
|
137
|
-
- siid: str, service id, get from https://home.miot-spec.com/spec/{model}, model from get_devices_list
|
|
138
|
-
- piid: str, property id, get from https://home.miot-spec.com/spec/{model}, model from get_devices_list
|
|
139
|
-
- value: str, value to set
|
|
140
|
-
model yeelink.light.lamp4 as an example:
|
|
141
|
-
[
|
|
142
|
-
{"did": "1234567890", "siid": 2, "piid": 2, "value": 50} # set the brightness to 50%
|
|
143
|
-
{"did": "1234567890", "siid": 2, "piid": 3, "value": 2700} # set the color temperature to 2700K
|
|
144
|
-
]
|
|
145
|
-
-------
|
|
146
|
-
@return
|
|
147
|
-
dict, result
|
|
148
|
-
"""
|
|
149
|
-
uri = '/miotspec/prop/set'
|
|
150
|
-
data = {"params": data}
|
|
151
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
152
|
-
|
|
153
|
-
def run_action(self, data: dict) -> dict:
|
|
154
|
-
"""run action
|
|
155
|
-
mijiaAPI.run_action(data: dict) -> dict
|
|
156
|
-
@param
|
|
157
|
-
data: dict
|
|
158
|
-
dict keys:
|
|
159
|
-
- did: str, device id, get from get_devices_list
|
|
160
|
-
- siid: str, service id, get from https://home.miot-spec.com/spec/{model}, model from get_devices_list
|
|
161
|
-
- aiid: str, action id, get from https://home.miot-spec.com/spec/{model}, model from get_devices_list
|
|
162
|
-
- value: list, value to list
|
|
163
|
-
model xiaomi.feeder.pi2001 as an example:
|
|
164
|
-
{"did": "1234567890", "siid": 2, "aiid": 1, "value": [2]}, # Remote feeding (2 servings) of food
|
|
165
|
-
-------
|
|
166
|
-
@return
|
|
167
|
-
dict, result
|
|
168
|
-
"""
|
|
169
|
-
uri = '/miotspec/action'
|
|
170
|
-
data = {"params": data}
|
|
171
|
-
return self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|