mijiaAPI 1.5.0__tar.gz → 2.0.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mijiaAPI
3
- Version: 1.5.0
3
+ Version: 2.0.1
4
4
  Summary: A Python API for Xiaomi Mijia
5
5
  License: GPLv3
6
6
  Author: Do1e
@@ -30,6 +30,12 @@ Description-Content-Type: text/markdown
30
30
  [![PyPI](https://img.shields.io/badge/PyPI-mijiaAPI-blue)](https://pypi.org/project/mijiaAPI/)
31
31
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-green.svg)](https://opensource.org/licenses/GPL-3.0)
32
32
 
33
+ ## ⚠️ 重要提醒
34
+
35
+ **自 v1.5.0 版本以来,本项目包含多项破坏性变更!**
36
+
37
+ 如果您正在从旧版本升级,请务必查看 [CHANGELOG.md](CHANGELOG.md) 以了解详细的变更内容和迁移指南。
38
+
33
39
  ## 安装
34
40
 
35
41
  ### 从 PyPI 安装(推荐)
@@ -91,15 +97,18 @@ yay -S python-mijia-api
91
97
 
92
98
  #### 设备与场景获取与控制:
93
99
 
100
+ 下述方法可参考 [demos/test_apis.py](demos/test_apis.py) 中的示例。
101
+
94
102
  * `get_devices_list() -> list`:获取设备列表
95
103
  * `get_homes_list() -> list`:获取家庭列表(包含房间信息)
96
104
  * `get_scenes_list(home_id: str) -> list`:获取手动场景列表
97
105
  - 在米家 App 中通过 **米家→添加→手动控制** 设置
98
106
  * `run_scene(scene_id: str) -> bool`:运行指定场景
99
- * `get_consumable_items(home_id: str) -> list`:获取设备的耗材信息
107
+ * `get_consumable_items(home_id: str, owner_id: Optional[int] = None) -> list`:获取设备的耗材信息,如果是共享家庭,需要额外指定 `owner_id` 参数
100
108
  * `get_devices_prop(data: list) -> list`:获取设备属性
101
109
  * `set_devices_prop(data: list) -> list`:设置设备属性
102
110
  * `run_action(data: dict) -> dict`:执行设备的特定动作
111
+ * `get_statistics(data: dict) -> list`:获取设备的统计信息,如空调每个月的耗电量,参考 [demos/test_get_statistics.py](demos/test_get_statistics.py)
103
112
 
104
113
  设备属性和动作的相关参数(`siid`, `piid`, `aiid`)可以从 [米家产品库](https://home.miot-spec.com) 查询:
105
114
  * 访问 `https://home.miot-spec.com/spec/{model}`(`model` 在设备列表中获取)
@@ -145,9 +154,9 @@ mijiaDevice(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: str
145
154
 
146
155
  #### 使用方法控制:
147
156
 
148
- * `set(name: str, did: str, value: Union[bool, int]) -> bool`:设置设备属性
149
- * `get(name: str, did: str) -> Union[bool, int, float, str]`:获取设备属性
150
- * `run_action(name: str, did: str = None, value: Any = None, **kwargs) -> bool`:执行设备动作
157
+ * `set(name: str, value: Union[bool, int, float, str], did: Optional[str] = None) -> bool`:设置设备属性
158
+ * `get(name: str, did: Optional[str] = None) -> Union[bool, int, float, str]`:获取设备属性
159
+ * `run_action(name: str, did: Optional[str] = None, value: Optional[Union[list, tuple]] = None, **kwargs) -> bool`:执行设备动作
151
160
 
152
161
  #### 属性样式访问:
153
162
 
@@ -255,6 +264,23 @@ mijiaAPI --run 明天天气如何
255
264
  mijiaAPI --run 打开台灯并将亮度调至最大 --quiet
256
265
  ```
257
266
 
267
+ ## 常见问题
268
+
269
+ ### 账号密码登录失败
270
+
271
+ 现在登录似乎100%遇到验证码,建议使用扫码登录。
272
+
273
+ ### XXX设备的XXX如何获取/设置
274
+
275
+ 我拥有的设备有限,无法保证能解答这类问题,但也欢迎提交 [issue](https://github.com/Do1e/mijia-api/issues),可能需要你将设备共享给我进行抓包或者自行抓包给我提供请求和响应,提供har文件的话注意自行删除cookie等敏感信息。
276
+
277
+ ### 如何抓包
278
+
279
+ 小米官方给了一个[抓包教程](https://iot.mi.com/new/doc/accesses/direct-access/extension-development/troubleshooting/packet_capture),我没试过,不确定是否能行,如果抓包成功数据是加密的,可以使用 [demos/decrypt.py](demos/decrypt.py) 解密。
280
+
281
+ 我自己的解决方案是使用一个获取了root的手机,安装 [reqable](https://reqable.com/zh-CN/) 进行抓包,导出 HAR 文件后使用 [demos/decrypt_har.py](demos/decrypt_har.py) 解密。
282
+
283
+
258
284
  ## 致谢
259
285
 
260
286
  * [janzlan/mijia-api](https://gitee.com/janzlan/mijia-api/tree/master)
@@ -6,6 +6,12 @@
6
6
  [![PyPI](https://img.shields.io/badge/PyPI-mijiaAPI-blue)](https://pypi.org/project/mijiaAPI/)
7
7
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-green.svg)](https://opensource.org/licenses/GPL-3.0)
8
8
 
9
+ ## ⚠️ 重要提醒
10
+
11
+ **自 v1.5.0 版本以来,本项目包含多项破坏性变更!**
12
+
13
+ 如果您正在从旧版本升级,请务必查看 [CHANGELOG.md](CHANGELOG.md) 以了解详细的变更内容和迁移指南。
14
+
9
15
  ## 安装
10
16
 
11
17
  ### 从 PyPI 安装(推荐)
@@ -67,15 +73,18 @@ yay -S python-mijia-api
67
73
 
68
74
  #### 设备与场景获取与控制:
69
75
 
76
+ 下述方法可参考 [demos/test_apis.py](demos/test_apis.py) 中的示例。
77
+
70
78
  * `get_devices_list() -> list`:获取设备列表
71
79
  * `get_homes_list() -> list`:获取家庭列表(包含房间信息)
72
80
  * `get_scenes_list(home_id: str) -> list`:获取手动场景列表
73
81
  - 在米家 App 中通过 **米家→添加→手动控制** 设置
74
82
  * `run_scene(scene_id: str) -> bool`:运行指定场景
75
- * `get_consumable_items(home_id: str) -> list`:获取设备的耗材信息
83
+ * `get_consumable_items(home_id: str, owner_id: Optional[int] = None) -> list`:获取设备的耗材信息,如果是共享家庭,需要额外指定 `owner_id` 参数
76
84
  * `get_devices_prop(data: list) -> list`:获取设备属性
77
85
  * `set_devices_prop(data: list) -> list`:设置设备属性
78
86
  * `run_action(data: dict) -> dict`:执行设备的特定动作
87
+ * `get_statistics(data: dict) -> list`:获取设备的统计信息,如空调每个月的耗电量,参考 [demos/test_get_statistics.py](demos/test_get_statistics.py)
79
88
 
80
89
  设备属性和动作的相关参数(`siid`, `piid`, `aiid`)可以从 [米家产品库](https://home.miot-spec.com) 查询:
81
90
  * 访问 `https://home.miot-spec.com/spec/{model}`(`model` 在设备列表中获取)
@@ -121,9 +130,9 @@ mijiaDevice(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: str
121
130
 
122
131
  #### 使用方法控制:
123
132
 
124
- * `set(name: str, did: str, value: Union[bool, int]) -> bool`:设置设备属性
125
- * `get(name: str, did: str) -> Union[bool, int, float, str]`:获取设备属性
126
- * `run_action(name: str, did: str = None, value: Any = None, **kwargs) -> bool`:执行设备动作
133
+ * `set(name: str, value: Union[bool, int, float, str], did: Optional[str] = None) -> bool`:设置设备属性
134
+ * `get(name: str, did: Optional[str] = None) -> Union[bool, int, float, str]`:获取设备属性
135
+ * `run_action(name: str, did: Optional[str] = None, value: Optional[Union[list, tuple]] = None, **kwargs) -> bool`:执行设备动作
127
136
 
128
137
  #### 属性样式访问:
129
138
 
@@ -231,6 +240,23 @@ mijiaAPI --run 明天天气如何
231
240
  mijiaAPI --run 打开台灯并将亮度调至最大 --quiet
232
241
  ```
233
242
 
243
+ ## 常见问题
244
+
245
+ ### 账号密码登录失败
246
+
247
+ 现在登录似乎100%遇到验证码,建议使用扫码登录。
248
+
249
+ ### XXX设备的XXX如何获取/设置
250
+
251
+ 我拥有的设备有限,无法保证能解答这类问题,但也欢迎提交 [issue](https://github.com/Do1e/mijia-api/issues),可能需要你将设备共享给我进行抓包或者自行抓包给我提供请求和响应,提供har文件的话注意自行删除cookie等敏感信息。
252
+
253
+ ### 如何抓包
254
+
255
+ 小米官方给了一个[抓包教程](https://iot.mi.com/new/doc/accesses/direct-access/extension-development/troubleshooting/packet_capture),我没试过,不确定是否能行,如果抓包成功数据是加密的,可以使用 [demos/decrypt.py](demos/decrypt.py) 解密。
256
+
257
+ 我自己的解决方案是使用一个获取了root的手机,安装 [reqable](https://reqable.com/zh-CN/) 进行抓包,导出 HAR 文件后使用 [demos/decrypt_har.py](demos/decrypt_har.py) 解密。
258
+
259
+
234
260
  ## 致谢
235
261
 
236
262
  * [janzlan/mijia-api](https://gitee.com/janzlan/mijia-api/tree/master)
@@ -0,0 +1,3 @@
1
+ from .login import mijiaLogin
2
+ from .apis import mijiaAPI
3
+ from .devices import mijiaDevice, get_device_info
@@ -133,7 +133,7 @@ def init_api(auth_path: str) -> mijiaAPI:
133
133
  auth = json.load(f)
134
134
  api = mijiaAPI(auth_data=auth)
135
135
  if not api.available:
136
- raise ValueError("Saved auth is no longer valid")
136
+ raise ValueError("认证信息已过期")
137
137
  except (json.JSONDecodeError, ValueError):
138
138
  api = mijiaLogin(save_path=auth_path)
139
139
  auth = api.QRlogin()
@@ -146,12 +146,8 @@ def init_api(auth_path: str) -> mijiaAPI:
146
146
 
147
147
  def get_devices_list(api: mijiaAPI, verbose: bool = True) -> dict:
148
148
  devices = api.get_devices_list()
149
- if 'list' in devices:
150
- devices = devices['list']
151
- else:
152
- devices = []
153
149
  if verbose:
154
- print("Devices:")
150
+ print("设备列表:")
155
151
  for device in devices:
156
152
  print(f" - {device['name']}\n"
157
153
  f" did: {device['did']}\n"
@@ -165,19 +161,15 @@ def get_homes_list(api: mijiaAPI, verbose: bool = True, device_mapping: Optional
165
161
  if device_mapping is None:
166
162
  device_mapping = get_devices_list(api, verbose=False)
167
163
  homes = api.get_homes_list()
168
- if 'homelist' in homes:
169
- homes = homes['homelist']
170
- else:
171
- homes = []
172
164
  if verbose:
173
- print("Homes:")
165
+ print("家庭列表:")
174
166
  for home in homes:
175
167
  print(f" - {home['name']}\n"
176
- f" id: {home['id']}\n"
177
- f" address: {home['address']}\n"
178
- f" room count: {len(home['roomlist'])}\n"
179
- f" create time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(home['create_time']))}")
180
- print(" Rooms:")
168
+ f" ID: {home['id']}\n"
169
+ f" 地址: {home['address']}\n"
170
+ f" 房间数量: {len(home['roomlist'])}\n"
171
+ f" 创建时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(home['create_time']))}")
172
+ print( " 房间列表:")
181
173
  for room in home['roomlist']:
182
174
  devices_name = []
183
175
  if room['dids']:
@@ -188,9 +180,9 @@ def get_homes_list(api: mijiaAPI, verbose: bool = True, device_mapping: Optional
188
180
  devices_name.append(did)
189
181
  dids = ', '.join(devices_name)
190
182
  print(f" - {room['name']}\n"
191
- f" id: {room['id']}\n"
192
- f" dids: {dids}\n"
193
- f" create time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(room['create_time']))}")
183
+ f" ID: {room['id']}\n"
184
+ f" 设备列表: {dids}\n"
185
+ f" 创建时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(room['create_time']))}")
194
186
  home_mapping = {home['id']: home for home in homes}
195
187
  return home_mapping
196
188
 
@@ -200,16 +192,12 @@ def get_scenes_list(api: mijiaAPI, verbose: bool = True, home_mapping: Optional[
200
192
  scene_mapping = {}
201
193
  for home_id, home in home_mapping.items():
202
194
  scenes = api.get_scenes_list(home_id)
203
- if 'scene_info_list' in scenes:
204
- scenes = scenes['scene_info_list']
205
- else:
206
- scenes = []
207
195
  if scenes and verbose:
208
- print(f"Scenes in {home['name']} ({home_id}):")
196
+ print(f"{home['name']} ({home_id}) 中的场景:")
209
197
  for scene in scenes:
210
198
  print(f" - {scene['name']}\n"
211
- f" id: {scene['scene_id']}\n"
212
- f" create time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(scene['create_time'])))}\n"
199
+ f" ID: {scene['scene_id']}\n"
200
+ f" 创建时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(scene['create_time'])))}\n"
213
201
  f" update time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(scene['update_time'])))}")
214
202
  scene_mapping.update({scene['scene_id']: scene for scene in scenes})
215
203
  return scene_mapping
@@ -218,15 +206,12 @@ def get_consumable_items(api: mijiaAPI, home_mapping: Optional[dict] = None):
218
206
  if home_mapping is None:
219
207
  home_mapping = get_homes_list(api, verbose=False)
220
208
  for home_id, home in home_mapping.items():
221
- items = api.get_consumable_items(home_id)
222
- if 'items' in items:
223
- items = items['items'][0]['consumes_data']
224
- else:
225
- items = []
226
- print(f"Consumable items in {home['name']} ({home_id}):")
209
+ items = api.get_consumable_items(home_id, home['uid'])
210
+ print(f"{home['name']} ({home_id}) 中的耗材:")
227
211
  for item in items:
228
- print(f" - {item['details'][0]['description']} in {item['name']}({item['did']})\n"
229
- f" value: {item['details'][0]['value']}")
212
+ for consumes_data in item['consumes_data']:
213
+ print(f" - {consumes_data['details'][0]['description']} 在 {consumes_data['name']}({consumes_data['did']})\n"
214
+ f" 值: {consumes_data['details'][0]['value']}")
230
215
 
231
216
  def run_scene(api: mijiaAPI, scene_id: str, scene_mapping: Optional[dict] = None) -> bool:
232
217
  if scene_mapping is None:
@@ -240,15 +225,15 @@ def run_scene(api: mijiaAPI, scene_id: str, scene_mapping: Optional[dict] = None
240
225
  found = True
241
226
  break
242
227
  if not found:
243
- print(f"Scene {scene_name_to_find} not found")
228
+ print(f"场景 {scene_name_to_find} 未找到")
244
229
  return False
245
230
  scene_name = scene_mapping[scene_id]['name']
246
231
  ret = api.run_scene(scene_id)
247
232
  if ret:
248
- print(f"Scene {scene_name}({scene_id}) run successfully")
233
+ print(f"场景 {scene_name}({scene_id}) 运行成功")
249
234
  return True
250
235
  else:
251
- print(f"Failed to run scene {scene_name}({scene_id})")
236
+ print(f"运行场景 {scene_name}({scene_id}) 失败")
252
237
  return False
253
238
 
254
239
  def get(args):
@@ -256,17 +241,17 @@ def get(args):
256
241
  device = mijiaDevice(api, dev_name=args.dev_name)
257
242
  value = device.get(args.prop_name)
258
243
  unit = device.prop_list[args.prop_name].unit
259
- print(f"The {args.prop_name} of {args.dev_name} is {value} {unit if unit else ''}")
244
+ print(f"{args.dev_name} {args.prop_name} 值为 {value} {unit if unit else ''}")
260
245
 
261
246
  def set(args):
262
247
  api = init_api(args.auth_path)
263
248
  device = mijiaDevice(api, dev_name=args.dev_name)
264
- ret = device.set_v2(args.prop_name, args.value)
249
+ ret = device.set(args.prop_name, args.value)
265
250
  unit = device.prop_list[args.prop_name].unit
266
251
  if ret:
267
- print(f"The {args.prop_name} of {args.dev_name} is set to {args.value} {unit if unit else ''}")
252
+ print(f"{args.dev_name} {args.prop_name} 值已设置为 {args.value} {unit if unit else ''}")
268
253
  else:
269
- print(f"Failed to set the {args.prop_name} of {args.dev_name} to {args.value}")
254
+ print(f"设置 {args.dev_name} {args.prop_name} 值为 {args.value} 失败")
270
255
 
271
256
 
272
257
  def main(args):
@@ -310,7 +295,7 @@ def main(args):
310
295
  wifispeaker = mijiaDevice(api, dev_name=device['name'])
311
296
  break
312
297
  if wifispeaker is None:
313
- raise ValueError("No wifispeaker found")
298
+ raise ValueError("未找到小爱音箱设备")
314
299
  else:
315
300
  wifispeaker = mijiaDevice(api, dev_name=args.wifispeaker_name)
316
301
  wifispeaker.run_action('execute-text-directive', _in=[args.run, args.quiet])
@@ -1,10 +1,11 @@
1
1
  from datetime import datetime
2
- from typing import Union
2
+ from typing import Union, Optional
3
3
 
4
4
  import requests
5
5
  import requests.cookies
6
6
 
7
- from .utils import defaultUA, post_data, PostDataError
7
+ from .consts import defaultUA
8
+ from .utils import post_data
8
9
 
9
10
 
10
11
  class mijiaAPI(object):
@@ -19,7 +20,7 @@ class mijiaAPI(object):
19
20
  Exception: 当授权数据不完整时抛出异常。
20
21
  """
21
22
  if any(k not in auth_data for k in ['userId', 'deviceId', 'ssecurity', 'serviceToken']):
22
- raise Exception('Invalid authorize data')
23
+ raise Exception('授权数据无效')
23
24
  self.userId = auth_data['userId']
24
25
  self.ssecurity = auth_data['ssecurity']
25
26
  self.session = requests.Session()
@@ -35,7 +36,7 @@ class mijiaAPI(object):
35
36
  @staticmethod
36
37
  def _post_process(data: dict) -> Union[list, bool]:
37
38
  if data['code'] != 0:
38
- raise Exception(f'Failed to get data, {data["message"]}')
39
+ raise Exception(f'获取数据失败, {data["message"]}')
39
40
  return data['result']
40
41
 
41
42
  @property
@@ -53,16 +54,38 @@ class mijiaAPI(object):
53
54
  return True
54
55
  return False
55
56
 
56
- def get_devices_list(self) -> dict:
57
+ def get_devices_list(self) -> list:
57
58
  """
58
59
  获取设备列表。
59
60
 
60
61
  Returns:
61
62
  dict: 设备列表。
62
63
  """
63
- uri = '/home/device_list'
64
- data = {"getVirtualModel": False, "getHuamiDevices": 0}
65
- return self._post_process(post_data(self.session, self.ssecurity, uri, data))
64
+ uri = '/home/home_device_list'
65
+ home_list = self.get_homes_list()
66
+ devices = []
67
+ for home in home_list:
68
+ start_did = ''
69
+ has_more = True
70
+ while has_more:
71
+ data = {
72
+ "home_owner": home['uid'],
73
+ "home_id": int(home['id']),
74
+ "limit": 200,
75
+ "start_did": start_did,
76
+ "get_split_device": True,
77
+ "support_smart_home": True,
78
+ "get_cariot_device": True,
79
+ "get_third_device": True
80
+ }
81
+ ret = self._post_process(post_data(self.session, self.ssecurity, uri, data))
82
+ if ret and ret.get('device_info'):
83
+ devices.extend(ret['device_info'])
84
+ start_did = ret.get('max_did', '')
85
+ has_more = ret.get('has_more', False) and start_did != ''
86
+ else:
87
+ has_more = False
88
+ return devices
66
89
 
67
90
  def get_homes_list(self) -> list:
68
91
  """
@@ -71,9 +94,9 @@ class mijiaAPI(object):
71
94
  Returns:
72
95
  list: 家庭列表,包括房间信息。
73
96
  """
74
- uri = '/v2/homeroom/gethome'
75
- data = {"fg": False, "fetch_share": True, "fetch_share_dev": True, "limit": 300, "app_ver": 7}
76
- return self._post_process(post_data(self.session, self.ssecurity, uri, data))
97
+ uri = '/v2/homeroom/gethome_merged'
98
+ data = {"fg": True, "fetch_share": True, "fetch_share_dev": True, "limit": 300, "app_ver": 7}
99
+ return self._post_process(post_data(self.session, self.ssecurity, uri, data))['homelist']
77
100
 
78
101
  def get_scenes_list(self, home_id: str) -> list:
79
102
  """
@@ -89,7 +112,10 @@ class mijiaAPI(object):
89
112
  """
90
113
  uri = '/appgateway/miot/appsceneservice/AppSceneService/GetSceneList'
91
114
  data = {"home_id": home_id}
92
- return self._post_process(post_data(self.session, self.ssecurity, uri, data))
115
+ ret = self._post_process(post_data(self.session, self.ssecurity, uri, data))
116
+ if ret and 'scene_info_list' in ret:
117
+ return ret['scene_info_list']
118
+ return []
93
119
 
94
120
  def run_scene(self, scene_id: str) -> bool:
95
121
  """
@@ -105,19 +131,23 @@ class mijiaAPI(object):
105
131
  data = {"scene_id": scene_id, "trigger_key": "user.click"}
106
132
  return self._post_process(post_data(self.session, self.ssecurity, uri, data))
107
133
 
108
- def get_consumable_items(self, home_id: str) -> list:
134
+ def get_consumable_items(self, home_id: str, owner_id: Optional[int] = None) -> list:
109
135
  """
110
136
  获取耗材列表。
111
137
 
112
138
  Args:
113
139
  home_id (str): 家庭ID,从get_homes_list获取。
140
+ owner_id (str, optional): 用户ID,默认为None,如果`home_id`为共享家庭,则需要提供owner_id。
114
141
 
115
142
  Returns:
116
143
  list: 耗材列表。
117
144
  """
118
145
  uri = '/v2/home/standard_consumable_items'
119
- data = {"home_id": int(home_id), "owner_id": self.userId}
120
- return self._post_process(post_data(self.session, self.ssecurity, uri, data))
146
+ data = {"home_id": int(home_id), "owner_id": int(owner_id) if owner_id else self.userId}
147
+ ret = self._post_process(post_data(self.session, self.ssecurity, uri, data))
148
+ if ret and 'items' in ret:
149
+ return ret['items']
150
+ return []
121
151
 
122
152
  def get_devices_prop(self, data: list) -> list:
123
153
  """
@@ -155,7 +185,7 @@ class mijiaAPI(object):
155
185
 
156
186
  示例(yeelink.light.lamp4):
157
187
  [
158
- {"did": "1234567890", "siid": 2, "piid": 2, "value": 50} # 设置亮度为50%
188
+ {"did": "1234567890", "siid": 2, "piid": 2, "value": 50}, # 设置亮度为50%
159
189
  {"did": "1234567890", "siid": 2, "piid": 3, "value": 2700} # 设置色温为2700K
160
190
  ]
161
191
 
@@ -178,7 +208,7 @@ class mijiaAPI(object):
178
208
  - value: 参数列表
179
209
 
180
210
  示例(xiaomi.feeder.pi2001):
181
- {"did": "1234567890", "siid": 2, "aiid": 1, "value": [2]}, # 远程喂食2份
211
+ {"did": "1234567890", "siid": 2, "aiid": 1, "value": [2]} # 远程喂食2份
182
212
 
183
213
  Returns:
184
214
  dict: 操作结果。
@@ -186,3 +216,36 @@ class mijiaAPI(object):
186
216
  uri = '/miotspec/action'
187
217
  data = {"params": data}
188
218
  return self._post_process(post_data(self.session, self.ssecurity, uri, data))
219
+
220
+ def get_statistics(self, data: dict) -> list:
221
+ """
222
+ 获取设备的统计信息。
223
+
224
+ Args:
225
+ data (dict): 请求参数,包含以下键:
226
+ - did: 设备ID,从get_devices_list获取
227
+ - key: siid.piid,表示要获取统计数据的属性
228
+ - data_type: 统计类型,可选值包括:
229
+ - 'stat_hour_v3': 按小时统计
230
+ - 'stat_day_v3': 按天统计
231
+ - 'stat_week_v3': 按周统计
232
+ - 'stat_month_v3': 按月统计
233
+ - limit: 返回的最大条目数,可选参数
234
+ - time_start: 开始时间戳,单位为秒
235
+ - time_end: 结束时间戳,单位为秒
236
+
237
+ 示例(lumi.acpartner.mcn04 的 power-consumption):
238
+ {
239
+ "did": "1234567890",
240
+ "key": "7.1",
241
+ "data_type": "stat_month_v3",
242
+ "limit": 24,
243
+ "time_start": 1685548800,
244
+ "time_end": 1750694400,
245
+ } # 2023-06-01 00:00:00 到 2025-06-24 00:00:00 的月度统计数据
246
+
247
+ Returns:
248
+ list: 统计信息列表。
249
+ """
250
+ uri = '/v2/user/statistics'
251
+ return self._post_process(post_data(self.session, self.ssecurity, uri, data))
@@ -5,3 +5,5 @@ qrURL = 'https://account.xiaomi.com/longPolling/loginUrl'
5
5
  apiURL = 'https://api.io.mi.com/app'
6
6
  deviceURL = 'https://home.miot-spec.com/spec/'
7
7
  accountURL = 'https://account.xiaomi.com/pass2/profile/home?bizFlag=&userId='
8
+
9
+ defaultUA = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36 Edg/126.0.0.0'
@@ -6,7 +6,7 @@ import requests
6
6
  from time import sleep
7
7
  from .apis import mijiaAPI
8
8
  from .code import ERROR_CODE
9
- from .urls import deviceURL
9
+ from .consts import deviceURL
10
10
  from .logger import get_logger
11
11
 
12
12
  logger = get_logger(__name__)
@@ -26,7 +26,7 @@ class DevProp(object):
26
26
  self.desc = prop_dict['description']
27
27
  self.type = prop_dict['type']
28
28
  if self.type not in ['bool', 'int', 'uint', 'float', 'string']:
29
- raise ValueError(f'Unsupported type: {self.type}, available types: bool, int, uint, float, string')
29
+ raise ValueError(f'不支持的类型: {self.type}, 可选类型: bool, int, uint, float, string')
30
30
  self.rw = prop_dict['rw']
31
31
  self.unit = prop_dict['unit']
32
32
  self.range = prop_dict['range']
@@ -106,18 +106,18 @@ class mijiaDevice(object):
106
106
  - 如果只提供了dev_info,则直接使用该信息。
107
107
  """
108
108
  if dev_info is None and dev_name is None:
109
- raise RuntimeError("Either 'dev_info' or 'dev_name' must be provided.")
109
+ raise RuntimeError("必须提供 'dev_info' 'dev_name' 中的一个参数。")
110
110
  if dev_info is not None and dev_name is not None:
111
- logger.warning("Both 'dev_info' and 'dev_name' provided. Using 'dev_info' for initialization.")
111
+ logger.warning("同时提供了 'dev_info' 'dev_name'。将使用 'dev_info' 进行初始化。")
112
112
 
113
113
  self.api = api
114
114
  if dev_info is None:
115
115
  devices_list = self.api.get_devices_list()
116
- matches = [device for device in devices_list.get('list', []) if device['name'] == dev_name]
116
+ matches = [device for device in devices_list if device['name'] == dev_name]
117
117
  if not matches:
118
- raise ValueError(f"Device {dev_name} not found")
118
+ raise ValueError(f"未找到设备 {dev_name}")
119
119
  elif len(matches) > 1:
120
- raise ValueError(f"Multiple devices named {dev_name} found")
120
+ raise ValueError(f"找到多个名为 {dev_name} 的设备")
121
121
  else:
122
122
  dev_info = get_device_info(matches[0]['model'])
123
123
  did = matches[0]['did']
@@ -152,14 +152,14 @@ class mijiaDevice(object):
152
152
  f"Properties:\n{prop_list_str if prop_list_str else 'No properties available'}\n"
153
153
  f"Actions:\n{action_list_str if action_list_str else 'No actions available'}")
154
154
 
155
- def set(self, name: str, did: str, value: Union[bool, int, float, str]) -> bool:
155
+ def set(self, name: str, value: Union[bool, int, float, str], did: Optional[str] = None) -> bool:
156
156
  """
157
157
  设置设备的属性值。
158
158
 
159
159
  Args:
160
160
  name (str): 属性名称。
161
- did (str): 设备ID。
162
161
  value (Union[bool, int, float, str]): 属性值。
162
+ did (str, optional): 设备ID。如未指定,则使用实例化时的did。默认为None。
163
163
 
164
164
  Returns:
165
165
  bool: 执行结果(True/False)。
@@ -168,14 +168,18 @@ class mijiaDevice(object):
168
168
  ValueError: 如果属性不存在、属性为只读或值无效。
169
169
  RuntimeError: 如果设置属性失败。
170
170
  """
171
+ if did is None:
172
+ did = self.did
173
+ if did is None:
174
+ raise ValueError('请指定设备ID (did)')
171
175
  if name not in self.prop_list:
172
- raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
176
+ raise ValueError(f'不支持的属性: {name}, 可用属性: {list(self.prop_list.keys())}')
173
177
  prop = self.prop_list[name]
174
178
  if 'w' not in prop.rw:
175
- raise ValueError(f'Property {name} can not be written')
179
+ raise ValueError(f'属性 {name} 不可写入')
176
180
  if prop.value_list:
177
181
  if value not in [item['value'] for item in prop.value_list]:
178
- raise ValueError(f'Invalid value: {value}, should be in {prop.value_list}')
182
+ raise ValueError(f'无效值: {value}, 请使用 {prop.value_list}')
179
183
  if prop.type == 'bool':
180
184
  if isinstance(value, str):
181
185
  if value.lower() == 'true':
@@ -185,75 +189,53 @@ class mijiaDevice(object):
185
189
  elif value in ['0', '1']:
186
190
  value = bool(int(value))
187
191
  else:
188
- raise ValueError(f'Invalid value for bool: {value}, should be True or False')
192
+ raise ValueError(f'无效布尔值: {value}')
189
193
  elif isinstance(value, int):
190
194
  if value == 0:
191
195
  value = False
192
196
  elif value == 1:
193
197
  value = True
194
198
  else:
195
- raise ValueError(f'Invalid value for bool: {value}, should be True or False')
199
+ raise ValueError(f'无效布尔值: {value}')
196
200
  elif not isinstance(value, bool):
197
- raise ValueError(f'Invalid value for bool: {value}, should be True or False')
201
+ raise ValueError(f'无效布尔值: {value}')
198
202
  elif prop.type in ['int', 'uint']:
199
203
  value = int(value)
200
204
  if prop.range:
201
205
  if value < prop.range[0] or value > prop.range[1]:
202
- raise ValueError(f'Value out of range: {value}, should be in range {prop.range[:2]}')
206
+ raise ValueError(f'{value} 超出数值范围, 应该在 {prop.range[:2]} 之间')
203
207
  if len(prop.range) >= 3 and prop.range[2] != 1:
204
208
  if (value - prop.range[0]) % prop.range[2] != 0:
205
209
  raise ValueError(
206
- f'Invalid value: {value}, should be in range {prop.range[:2]} with step {prop.range[2]}')
210
+ f'无效的值: {value}, 应该在范围 {prop.range[:2]} 内且步长为 {prop.range[2]}')
207
211
  elif prop.type == 'float':
208
212
  value = float(value)
209
213
  if prop.range:
210
214
  if value < prop.range[0] or value > prop.range[1]:
211
- raise ValueError(f'Value out of range: {value}, should be in range {prop.range[:2]}')
215
+ raise ValueError(f'{value} 超出数值范围, 应该在 {prop.range[:2]} 之间')
212
216
  if len(prop.range) >= 3 and isinstance(prop.range[2], int):
213
217
  if int(value - prop.range[0]) % prop.range[2] != 0:
214
218
  raise ValueError(
215
- f'Invalid value: {value}, should be in range {prop.range[:2]} with step {prop.range[2]}')
219
+ f'无效的值: {value}, 应该在范围 {prop.range[:2]} 内且步长为 {prop.range[2]}')
216
220
  elif prop.type == 'string':
217
221
  if not isinstance(value, str):
218
- raise ValueError(f'Invalid value for string: {value}, should be a string')
222
+ raise ValueError(f'无效字符串值: {value}')
219
223
  else:
220
- raise ValueError(f'Unsupported type: {prop.type}, available types: bool, int, uint, float, string')
224
+ raise ValueError(f'不支持的类型: {prop.type}, 可用类型: bool, int, uint, float, string')
221
225
  method = prop.method.copy()
222
226
  method['did'] = did
223
227
  method['value'] = value
224
228
  result = self.api.set_devices_prop([method])[0]
225
229
  if result['code'] != 0:
226
230
  raise RuntimeError(
227
- f"Failed to set property: {name}, "
228
- f"code: {result['code']}, "
229
- f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
231
+ f"设置属性 {name} 失败, "
232
+ f"错误码: {result['code']}, "
233
+ f"错误信息: {ERROR_CODE.get(str(result['code']), '未知错误')}"
230
234
  )
231
235
  sleep(self.sleep_time)
232
- logger.debug(f"Set property: {self.name} -> {name}, value: {value}, result: {result}")
236
+ logger.debug(f"设置属性: {self.name} -> {name}, 值: {value}, 结果: {result}")
233
237
  return result['code'] == 0
234
238
 
235
- def set_v2(self, name: str, value: Union[bool, int, float, str], did: Optional[str] = None) -> bool:
236
- """
237
- 设置设备的属性值(v2版本,需在实例化时指定did或在调用时提供)。
238
-
239
- Args:
240
- name (str): 属性名称。
241
- value (Union[bool, int, float, str]): 属性值。
242
- did (str, optional): 设备ID。如未指定,则使用实例化时的did。默认为None。
243
-
244
- Returns:
245
- bool: 执行结果(True/False)。
246
-
247
- Raises:
248
- ValueError: 如果未指定设备ID。
249
- """
250
- if did is not None:
251
- return self.set(name, did, value)
252
- elif self.did is not None:
253
- return self.set(name, self.did, value)
254
- else:
255
- raise ValueError('Please specify the did')
256
-
257
239
  def get(self, name: str, did: Optional[str] = None) -> Union[bool, int, float, str]:
258
240
  """
259
241
  获取设备的属性值。
@@ -272,23 +254,23 @@ class mijiaDevice(object):
272
254
  if did is None:
273
255
  did = self.did
274
256
  if did is None:
275
- raise ValueError('Please specify the did')
257
+ raise ValueError('请指定设备ID (did)')
276
258
  if name not in self.prop_list:
277
- raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
259
+ raise ValueError(f'不支持的属性: {name}, 可用属性: {list(self.prop_list.keys())}')
278
260
  prop = self.prop_list[name]
279
261
  if 'r' not in prop.rw:
280
- raise ValueError(f'Property {name} can not be read')
262
+ raise ValueError(f'属性 {name} 不可读取')
281
263
  method = prop.method.copy()
282
264
  method['did'] = did
283
265
  result = self.api.get_devices_prop([method])[0]
284
266
  if result['code'] != 0:
285
267
  raise RuntimeError(
286
- f"Failed to get property: {name}, "
287
- f"code: {result['code']}, "
288
- f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
268
+ f"获取属性 {name} 失败, "
269
+ f"错误码: {result['code']}, "
270
+ f"错误信息: {ERROR_CODE.get(str(result['code']), '未知错误')}"
289
271
  )
290
272
  sleep(self.sleep_time)
291
- logger.debug(f"Get property: {self.name} -> {name}, result: {result}")
273
+ logger.debug(f"获取属性: {self.name} -> {name}, 结果: {result}")
292
274
  return result['value']
293
275
 
294
276
  def __setattr__(self, name: str, value: Union[bool, int, float, str]) -> None:
@@ -303,8 +285,8 @@ class mijiaDevice(object):
303
285
  RuntimeError: 如果设置属性失败。
304
286
  """
305
287
  if 'prop_list' in self.__dict__ and name in self.prop_list:
306
- if not self.set_v2(name, value):
307
- raise RuntimeError(f'Failed to set property: {name}')
288
+ if not self.set(name, value):
289
+ raise RuntimeError(f'设置属性 {name} 失败')
308
290
  else:
309
291
  super().__setattr__(name, value)
310
292
 
@@ -349,9 +331,9 @@ class mijiaDevice(object):
349
331
  if did is None:
350
332
  did = self.did
351
333
  if did is None:
352
- raise ValueError('Please specify the did')
334
+ raise ValueError('请指定设备ID (did)')
353
335
  if name not in self.action_list:
354
- raise ValueError(f'Unsupported action: {name}, available actions: {list(self.action_list.keys())}')
336
+ raise ValueError(f'不支持的动作: {name}, 可用动作: {list(self.action_list.keys())}')
355
337
  act = self.action_list[name]
356
338
  method = act.method.copy()
357
339
  method['did'] = did
@@ -362,55 +344,20 @@ class mijiaDevice(object):
362
344
  if k.startswith("_"):
363
345
  k = k[1:]
364
346
  if k in method:
365
- raise ValueError(f'Invalid argument: {k}. Do not use arguments in ({", ".join(method.keys())})')
347
+ raise ValueError(f'无效的参数: {k}. 请勿使用以下参数 ({", ".join(method.keys())})')
366
348
  method[k] = v
367
349
  result = self.api.run_action(method)
368
350
  if result['code'] != 0:
369
351
  raise RuntimeError(
370
- f"Failed to run action: {name}, "
371
- f"code: {result['code']}, "
372
- f"message: {ERROR_CODE.get(str(result['code']), 'Unknown error')}"
352
+ f"执行动作 {name} 失败, "
353
+ f"错误码: {result['code']}, "
354
+ f"错误信息: {ERROR_CODE.get(str(result['code']), '未知错误')}"
373
355
  )
374
356
  sleep(self.sleep_time)
375
- logger.debug(f"Run action: {self.name} -> {name}, result: {result}")
357
+ logger.debug(f"执行动作: {self.name} -> {name}, 结果: {result}")
376
358
  return result['code'] == 0
377
359
 
378
360
 
379
- class mijiaDevices(mijiaDevice):
380
- def __init__(
381
- self,
382
- api: mijiaAPI,
383
- dev_info: Optional[dict] = None,
384
- dev_name: Optional[str] = None,
385
- did: Optional[str] = None,
386
- sleep_time: Optional[Union[int, float]] = 0.5
387
- ):
388
- """
389
- 初始化设备对象。
390
-
391
- 如果未提供设备信息,则根据设备名称获取设备信息。如果两者均未提供,则抛出异常。
392
- 如果同时提供了设备信息和设备名称,则以设备信息为准。
393
-
394
- Args:
395
- api (mijiaAPI): 米家API对象。
396
- dev_info (dict, optional): 设备信息字典,从get_device_info获取。默认为None。
397
- dev_name (str, optional): 设备名称,从get_devices_list获取。默认为None。
398
- did (str, optional): 设备ID,如未指定,则需要在调用get/set时指定。默认为None。
399
- sleep_time ([int, float], optional): 调用设备属性的间隔时间。默认为0.5秒。
400
-
401
- Raises:
402
- RuntimeError: 如果dev_info和dev_name都未提供。
403
- ValueError: 如果找不到指定设备或找到多个同名设备。
404
-
405
- Note:
406
- - 如果同时提供了dev_info和dev_name,则以dev_info为准。
407
- - 如果只提供了dev_name,则根据名称自动获取设备信息。
408
- - 如果只提供了dev_info,则直接使用该信息。
409
- """
410
- super().__init__(api, dev_info, dev_name, did, sleep_time)
411
- logger.warning("`mijiaDevices` will be deprecated in future versions, use `mijiaDevice` instead.")
412
-
413
-
414
361
  def get_device_info(device_model: str, cache_path: Optional[str] = os.path.join(os.path.expanduser("~"), ".config/mijia-api")) -> dict:
415
362
  """
416
363
  获取设备信息,用于初始化mijiaDevice对象。
@@ -432,10 +379,10 @@ def get_device_info(device_model: str, cache_path: Optional[str] = os.path.join(
432
379
  return json.load(f)
433
380
  response = requests.get(deviceURL + device_model)
434
381
  if response.status_code != 200:
435
- raise RuntimeError(f'Failed to get device info')
382
+ raise RuntimeError(f'获取设备信息失败')
436
383
  content = re.search(r'data-page="(.*?)">', response.text)
437
384
  if content is None:
438
- raise RuntimeError(f'Failed to get device info')
385
+ raise RuntimeError(f'获取设备信息失败')
439
386
  content = content.group(1)
440
387
  content = json.loads(content.replace('&quot;', '"'))
441
388
 
@@ -13,8 +13,7 @@ import requests
13
13
  from qrcode import QRCode
14
14
 
15
15
  from .logger import get_logger
16
- from .urls import msgURL, loginURL, qrURL, accountURL
17
- from .utils import defaultUA
16
+ from .consts import msgURL, loginURL, qrURL, accountURL, defaultUA
18
17
 
19
18
  logger = get_logger(__name__)
20
19
 
@@ -65,7 +64,7 @@ class mijiaLogin(object):
65
64
  """
66
65
  ret = self.session.get(msgURL)
67
66
  if ret.status_code != 200:
68
- raise LoginError(ret.status_code, f'Failed to get index page, {ret.text}')
67
+ raise LoginError(ret.status_code, f'获取索引页失败, {ret.text}')
69
68
  ret_data = json.loads(ret.text[11:])
70
69
  data = {'deviceId': self.deviceId}
71
70
  data.update({
@@ -90,7 +89,7 @@ class mijiaLogin(object):
90
89
  try:
91
90
  ret = self.session.get(accountURL + str(user_id))
92
91
  if ret.status_code != 200:
93
- raise LoginError(ret.status_code, f'Failed to get account page, {ret.text}')
92
+ raise LoginError(ret.status_code, f'获取账户页面失败, {ret.text}')
94
93
  data = json.loads(ret.text[11:])['data']
95
94
  except (KeyError, json.JSONDecodeError) as e:
96
95
  data = {}
@@ -117,11 +116,13 @@ class mijiaLogin(object):
117
116
  ]
118
117
 
119
118
  if not gmt_time_keys:
120
- raise LoginError(-1, 'No GMT time keys found in the data')
119
+ raise LoginError(-1, '在cookie中未找到GMT时间键')
121
120
  parsed_times = [datetime.strptime(k, '%d-%b-%Y %H:%M:%S GMT') for k in gmt_time_keys]
122
121
  latest_utc_time = max(parsed_times)
123
122
  china_time = latest_utc_time + timedelta(hours=8)
124
123
 
124
+ # [FIXME] 实测此处的过期时间并不准确,实际过期时间可能大于此处获取的时间
125
+ # cookie 中唯一用到的 serviceToken 并无过期时间
125
126
  return china_time
126
127
 
127
128
  def _save_auth(self) -> None:
@@ -134,14 +135,14 @@ class mijiaLogin(object):
134
135
  if not os.path.isabs(self.save_path):
135
136
  self.save_path = os.path.abspath(self.save_path)
136
137
  if os.path.exists(self.save_path) and not os.path.isfile(self.save_path):
137
- raise ValueError(f'Path [{self.save_path}] is not a file')
138
+ raise ValueError(f'[{self.save_path}] 不是文件')
138
139
  if not os.path.exists(os.path.dirname(self.save_path)):
139
140
  os.makedirs(os.path.dirname(self.save_path))
140
141
  with open(self.save_path, 'w') as f:
141
142
  json.dump(self.auth_data, f, indent=2)
142
- logger.info(f'Auth data saved to [{self.save_path}]')
143
+ logger.info(f'认证文件已保存到 [{self.save_path}]')
143
144
  else:
144
- logger.info('Auth data not saved')
145
+ logger.info('认证文件未保存')
145
146
 
146
147
  def login(self, username: str, password: str) -> dict:
147
148
  """
@@ -157,7 +158,7 @@ class mijiaLogin(object):
157
158
  Raises:
158
159
  LoginError: 登录失败时抛出。
159
160
  """
160
- logger.warning('There is a high probability of verification code with account and password. Please try `QRlogin` method.')
161
+ logger.warning('使用账号密码登录很可能需要验证码。请尝试使用 `QRlogin` 方法。')
161
162
  data = self._get_index()
162
163
  post_data = {
163
164
  'qs': data['qs'],
@@ -170,17 +171,17 @@ class mijiaLogin(object):
170
171
  }
171
172
  ret = self.session.post(loginURL, data=post_data)
172
173
  if ret.status_code != 200:
173
- raise LoginError(ret.status_code, f'Failed to post login page, {ret.text}')
174
+ raise LoginError(ret.status_code, f'登录页面提交失败, {ret.text}')
174
175
  ret_data = json.loads(ret.text[11:])
175
176
  if ret_data['code'] != 0:
176
177
  raise LoginError(ret_data['code'], ret_data['desc'])
177
178
  if 'location' not in ret_data:
178
- raise LoginError(-1, 'Failed to get location')
179
+ raise LoginError(-1, '获取跳转位置失败')
179
180
  if 'notificationUrl' in ret_data:
180
- raise LoginError(-1, 'Verification code required, please try `QRlogin` method')
181
+ raise LoginError(-1, '需要验证码,请尝试使用 `QRlogin` 方法')
181
182
  ret = self.session.get(ret_data['location'])
182
183
  if ret.status_code != 200:
183
- raise LoginError(ret.status_code, f'Failed to get location, {ret.text}')
184
+ raise LoginError(ret.status_code, f'获取跳转位置失败, {ret.text}')
184
185
  cookies = self.session.cookies.get_dict()
185
186
 
186
187
  self.auth_data = {
@@ -188,6 +189,7 @@ class mijiaLogin(object):
188
189
  'ssecurity': ret_data['ssecurity'],
189
190
  'deviceId': data['deviceId'],
190
191
  'serviceToken': cookies['serviceToken'],
192
+ 'cUserId': cookies['cUserId'],
191
193
  'expireTime': self._extract_latest_gmt_datetime(cookies).strftime('%Y-%m-%d %H:%M:%S'),
192
194
  'account_info': self._get_account_info(ret_data['userId'])
193
195
  }
@@ -204,7 +206,7 @@ class mijiaLogin(object):
204
206
  loginurl (str): 包含登录信息的URL。
205
207
  box_size (int, optional): 二维码大小。默认为10。
206
208
  """
207
- logger.info('Scan the QR code below with Mi Home app')
209
+ logger.info('请使用米家APP扫描下方二维码')
208
210
  qr = QRCode(border=1, box_size=box_size)
209
211
  qr.add_data(loginurl)
210
212
  qr.make_image().save('qr.png')
@@ -212,10 +214,10 @@ class mijiaLogin(object):
212
214
  qr.print_ascii(invert=True, tty=True)
213
215
  except OSError:
214
216
  qr.print_ascii(invert=True, tty=False)
215
- logger.info('If the QR code can not be scanned, '
216
- 'please change the font of the terminal, '
217
- 'such as "Maple Mono", "Fira Code", etc.\n'
218
- 'Or just use the qr.png file in the current directory.')
217
+ logger.info('如果无法扫描二维码,'
218
+ '请更改终端字体,'
219
+ '"Maple Mono""Fira Code"等。\n'
220
+ '或者直接使用当前目录下的qr.png文件。')
219
221
 
220
222
  def QRlogin(self) -> dict:
221
223
  """
@@ -248,7 +250,7 @@ class mijiaLogin(object):
248
250
  url = qrURL + '?' + parse.urlencode(params)
249
251
  ret = self.session.get(url)
250
252
  if ret.status_code != 200:
251
- raise LoginError(ret.status_code, f'Failed to get QR code URL, {ret.text}')
253
+ raise LoginError(ret.status_code, f'获取二维码URL失败, {ret.text}')
252
254
  ret_data = json.loads(ret.text[11:])
253
255
  if ret_data['code'] != 0:
254
256
  raise LoginError(ret_data['code'], ret_data['desc'])
@@ -257,15 +259,15 @@ class mijiaLogin(object):
257
259
  try:
258
260
  ret = self.session.get(ret_data['lp'], timeout=60, headers={'Connection': 'keep-alive'})
259
261
  except requests.exceptions.Timeout:
260
- raise LoginError(-1, 'Timeout, please try again')
262
+ raise LoginError(-1, '超时,请重试')
261
263
  if ret.status_code != 200:
262
- raise LoginError(ret.status_code, f'Failed to wait for login, {ret.text}')
264
+ raise LoginError(ret.status_code, f'等待登录失败, {ret.text}')
263
265
  ret_data = json.loads(ret.text[11:])
264
266
  if ret_data['code'] != 0:
265
267
  raise LoginError(ret_data['code'], ret_data['desc'])
266
268
  ret = self.session.get(ret_data['location'])
267
269
  if ret.status_code != 200:
268
- raise LoginError(ret.status_code, f'Failed to get location, {ret.text}')
270
+ raise LoginError(ret.status_code, f'获取跳转位置失败, {ret.text}')
269
271
  cookies = self.session.cookies.get_dict()
270
272
 
271
273
  self.auth_data = {
@@ -273,6 +275,7 @@ class mijiaLogin(object):
273
275
  'ssecurity': ret_data['ssecurity'],
274
276
  'deviceId': data['deviceId'],
275
277
  'serviceToken': cookies['serviceToken'],
278
+ 'cUserId': cookies['cUserId'],
276
279
  'expireTime': self._extract_latest_gmt_datetime(cookies).strftime('%Y-%m-%d %H:%M:%S'),
277
280
  'account_info': self._get_account_info(ret_data['userId'])
278
281
  }
@@ -6,9 +6,8 @@ import string
6
6
 
7
7
  import requests
8
8
 
9
- from .urls import apiURL
9
+ from .consts import apiURL
10
10
 
11
- defaultUA = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36 Edg/126.0.0.0'
12
11
 
13
12
  class PostDataError(Exception):
14
13
  def __init__(self, code: int, message: str):
@@ -38,6 +37,6 @@ def post_data(session: requests.Session, ssecurity: str, uri: str, data: dict) -
38
37
  post_data = {'_nonce': nonce, 'data': data, 'signature': signature}
39
38
  ret = session.post(apiURL + uri, data=post_data)
40
39
  if ret.status_code != 200:
41
- raise PostDataError(ret.status_code, f'Failed to post data, {ret.text}')
40
+ raise PostDataError(ret.status_code, f'发送数据失败, {ret.text}')
42
41
  ret_data = ret.json()
43
42
  return ret_data
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mijiaAPI"
3
- version = "1.5.0"
3
+ version = "2.0.1"
4
4
  description = "A Python API for Xiaomi Mijia"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9,<4.0"
@@ -15,7 +15,7 @@ mijiaAPI = "mijiaAPI.__main__:cli"
15
15
 
16
16
  [tool.poetry]
17
17
  name = "mijiaAPI"
18
- version = "1.5.0"
18
+ version = "2.0.1"
19
19
  description = "A Python API for Xiaomi Mijia"
20
20
  authors = ["Do1e <dpj.email@qq.com>"]
21
21
  license = "GPLv3"
@@ -1,3 +0,0 @@
1
- from .login import mijiaLogin
2
- from .apis import mijiaAPI
3
- from .devices import mijiaDevice, mijiaDevices, get_device_info
File without changes
File without changes
File without changes