mijiaAPI 1.4.5__tar.gz → 2.0.0__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.4.5
3
+ Version: 2.0.0
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 安装(推荐)
@@ -53,6 +59,13 @@ pip install .
53
59
  poetry install
54
60
  ```
55
61
 
62
+ ### aur
63
+ 如果你使用 Arch Linux 或基于 Arch 的发行版,可以通过 AUR 安装:
64
+
65
+ ```bash
66
+ yay -S python-mijia-api
67
+ ```
68
+
56
69
  ## 使用
57
70
 
58
71
  使用实例可以参考 `demos` 文件夹下的示例代码,以下是基本使用说明。
@@ -84,15 +97,18 @@ poetry install
84
97
 
85
98
  #### 设备与场景获取与控制:
86
99
 
100
+ 下述方法可参考 [demos/test_apis.py](demos/test_apis.py) 中的示例。
101
+
87
102
  * `get_devices_list() -> list`:获取设备列表
88
103
  * `get_homes_list() -> list`:获取家庭列表(包含房间信息)
89
104
  * `get_scenes_list(home_id: str) -> list`:获取手动场景列表
90
105
  - 在米家 App 中通过 **米家→添加→手动控制** 设置
91
106
  * `run_scene(scene_id: str) -> bool`:运行指定场景
92
- * `get_consumable_items(home_id: str) -> list`:获取设备的耗材信息
107
+ * `get_consumable_items(home_id: str, owner_id: Optional[int] = None) -> list`:获取设备的耗材信息,如果是共享家庭,需要额外指定 `owner_id` 参数
93
108
  * `get_devices_prop(data: list) -> list`:获取设备属性
94
109
  * `set_devices_prop(data: list) -> list`:设置设备属性
95
110
  * `run_action(data: dict) -> dict`:执行设备的特定动作
111
+ * `get_statistics(data: dict) -> list`:获取设备的统计信息,如空调每个月的耗电量,参考 [demos/test_get_statistics.py](demos/test_get_statistics.py)
96
112
 
97
113
  设备属性和动作的相关参数(`siid`, `piid`, `aiid`)可以从 [米家产品库](https://home.miot-spec.com) 查询:
98
114
  * 访问 `https://home.miot-spec.com/spec/{model}`(`model` 在设备列表中获取)
@@ -115,12 +131,12 @@ device_info = get_device_info('yeelink.light.lamp4') # 米家台灯 1S 的 mode
115
131
 
116
132
  ### 设备控制封装
117
133
 
118
- `mijiaDevices`:基于 `mijiaAPI` 的高级封装,提供更简便的设备控制方式。
134
+ `mijiaDevice`:基于 `mijiaAPI` 的高级封装,提供更简便的设备控制方式。
119
135
 
120
136
  #### 初始化:
121
137
 
122
138
  ```python
123
- mijiaDevices(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: str = None, sleep_time: float = 0.5)
139
+ mijiaDevice(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: str = None, sleep_time: float = 0.5)
124
140
  ```
125
141
 
126
142
  * `api`:已初始化的 `mijiaAPI` 对象
@@ -138,9 +154,9 @@ mijiaDevices(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: st
138
154
 
139
155
  #### 使用方法控制:
140
156
 
141
- * `set(name: str, did: str, value: Union[bool, int]) -> bool`:设置设备属性
142
- * `get(name: str, did: str) -> Union[bool, int, float, str]`:获取设备属性
143
- * `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`:执行设备动作
144
160
 
145
161
  #### 属性样式访问:
146
162
 
@@ -148,7 +164,7 @@ mijiaDevices(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: st
148
164
 
149
165
  ```python
150
166
  # 示例:控制台灯
151
- device = mijiaDevices(api, dev_name='台灯')
167
+ device = mijiaDevice(api, dev_name='台灯')
152
168
  device.on = True # 打开灯
153
169
  device.brightness = 60 # 设置亮度
154
170
  current_temp = device.color_temperature # 获取色温
@@ -248,6 +264,23 @@ mijiaAPI --run 明天天气如何
248
264
  mijiaAPI --run 打开台灯并将亮度调至最大 --quiet
249
265
  ```
250
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
+
251
284
  ## 致谢
252
285
 
253
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 安装(推荐)
@@ -29,6 +35,13 @@ pip install .
29
35
  poetry install
30
36
  ```
31
37
 
38
+ ### aur
39
+ 如果你使用 Arch Linux 或基于 Arch 的发行版,可以通过 AUR 安装:
40
+
41
+ ```bash
42
+ yay -S python-mijia-api
43
+ ```
44
+
32
45
  ## 使用
33
46
 
34
47
  使用实例可以参考 `demos` 文件夹下的示例代码,以下是基本使用说明。
@@ -60,15 +73,18 @@ poetry install
60
73
 
61
74
  #### 设备与场景获取与控制:
62
75
 
76
+ 下述方法可参考 [demos/test_apis.py](demos/test_apis.py) 中的示例。
77
+
63
78
  * `get_devices_list() -> list`:获取设备列表
64
79
  * `get_homes_list() -> list`:获取家庭列表(包含房间信息)
65
80
  * `get_scenes_list(home_id: str) -> list`:获取手动场景列表
66
81
  - 在米家 App 中通过 **米家→添加→手动控制** 设置
67
82
  * `run_scene(scene_id: str) -> bool`:运行指定场景
68
- * `get_consumable_items(home_id: str) -> list`:获取设备的耗材信息
83
+ * `get_consumable_items(home_id: str, owner_id: Optional[int] = None) -> list`:获取设备的耗材信息,如果是共享家庭,需要额外指定 `owner_id` 参数
69
84
  * `get_devices_prop(data: list) -> list`:获取设备属性
70
85
  * `set_devices_prop(data: list) -> list`:设置设备属性
71
86
  * `run_action(data: dict) -> dict`:执行设备的特定动作
87
+ * `get_statistics(data: dict) -> list`:获取设备的统计信息,如空调每个月的耗电量,参考 [demos/test_get_statistics.py](demos/test_get_statistics.py)
72
88
 
73
89
  设备属性和动作的相关参数(`siid`, `piid`, `aiid`)可以从 [米家产品库](https://home.miot-spec.com) 查询:
74
90
  * 访问 `https://home.miot-spec.com/spec/{model}`(`model` 在设备列表中获取)
@@ -91,12 +107,12 @@ device_info = get_device_info('yeelink.light.lamp4') # 米家台灯 1S 的 mode
91
107
 
92
108
  ### 设备控制封装
93
109
 
94
- `mijiaDevices`:基于 `mijiaAPI` 的高级封装,提供更简便的设备控制方式。
110
+ `mijiaDevice`:基于 `mijiaAPI` 的高级封装,提供更简便的设备控制方式。
95
111
 
96
112
  #### 初始化:
97
113
 
98
114
  ```python
99
- mijiaDevices(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: str = None, sleep_time: float = 0.5)
115
+ mijiaDevice(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: str = None, sleep_time: float = 0.5)
100
116
  ```
101
117
 
102
118
  * `api`:已初始化的 `mijiaAPI` 对象
@@ -114,9 +130,9 @@ mijiaDevices(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: st
114
130
 
115
131
  #### 使用方法控制:
116
132
 
117
- * `set(name: str, did: str, value: Union[bool, int]) -> bool`:设置设备属性
118
- * `get(name: str, did: str) -> Union[bool, int, float, str]`:获取设备属性
119
- * `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`:执行设备动作
120
136
 
121
137
  #### 属性样式访问:
122
138
 
@@ -124,7 +140,7 @@ mijiaDevices(api: mijiaAPI, dev_info: dict = None, dev_name: str = None, did: st
124
140
 
125
141
  ```python
126
142
  # 示例:控制台灯
127
- device = mijiaDevices(api, dev_name='台灯')
143
+ device = mijiaDevice(api, dev_name='台灯')
128
144
  device.on = True # 打开灯
129
145
  device.brightness = 60 # 设置亮度
130
146
  current_temp = device.color_temperature # 获取色温
@@ -224,6 +240,23 @@ mijiaAPI --run 明天天气如何
224
240
  mijiaAPI --run 打开台灯并将亮度调至最大 --quiet
225
241
  ```
226
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
+
227
260
  ## 致谢
228
261
 
229
262
  * [janzlan/mijia-api](https://gitee.com/janzlan/mijia-api/tree/master)
@@ -1,3 +1,3 @@
1
1
  from .login import mijiaLogin
2
2
  from .apis import mijiaAPI
3
- from .devices import mijiaDevices, get_device_info
3
+ from .devices import mijiaDevice, get_device_info
@@ -6,7 +6,7 @@ import sys
6
6
  import time
7
7
 
8
8
  from .apis import mijiaAPI
9
- from .devices import mijiaDevices, get_device_info
9
+ from .devices import mijiaDevice, get_device_info
10
10
  from .login import mijiaLogin
11
11
 
12
12
  def parse_args(args):
@@ -146,10 +146,6 @@ 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
150
  print("Devices:")
155
151
  for device in devices:
@@ -165,10 +161,6 @@ 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
165
  print("Homes:")
174
166
  for home in homes:
@@ -189,7 +181,7 @@ def get_homes_list(api: mijiaAPI, verbose: bool = True, device_mapping: Optional
189
181
  dids = ', '.join(devices_name)
190
182
  print(f" - {room['name']}\n"
191
183
  f" id: {room['id']}\n"
192
- f" dids: {dids}\n"
184
+ f" devices: {dids}\n"
193
185
  f" create time: {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
@@ -200,10 +192,6 @@ 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
196
  print(f"Scenes in {home['name']} ({home_id}):")
209
197
  for scene in scenes:
@@ -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 = []
209
+ items = api.get_consumable_items(home_id, home['uid'])
226
210
  print(f"Consumable items in {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']} in {consumes_data['name']}({consumes_data['did']})\n"
214
+ f" value: {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:
@@ -253,15 +238,15 @@ def run_scene(api: mijiaAPI, scene_id: str, scene_mapping: Optional[dict] = None
253
238
 
254
239
  def get(args):
255
240
  api = init_api(args.auth_path)
256
- device = mijiaDevices(api, dev_name=args.dev_name)
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
244
  print(f"The {args.prop_name} of {args.dev_name} is {value} {unit if unit else ''}")
260
245
 
261
246
  def set(args):
262
247
  api = init_api(args.auth_path)
263
- device = mijiaDevices(api, dev_name=args.dev_name)
264
- ret = device.set_v2(args.prop_name, args.value)
248
+ device = mijiaDevice(api, dev_name=args.dev_name)
249
+ ret = device.set(args.prop_name, args.value)
265
250
  unit = device.prop_list[args.prop_name].unit
266
251
  if ret:
267
252
  print(f"The {args.prop_name} of {args.dev_name} is set to {args.value} {unit if unit else ''}")
@@ -307,12 +292,12 @@ def main(args):
307
292
  wifispeaker = None
308
293
  for device in device_mapping.values():
309
294
  if 'xiaomi.wifispeaker' in device['model']:
310
- wifispeaker = mijiaDevices(api, dev_name=device['name'])
295
+ wifispeaker = mijiaDevice(api, dev_name=device['name'])
311
296
  break
312
297
  if wifispeaker is None:
313
298
  raise ValueError("No wifispeaker found")
314
299
  else:
315
- wifispeaker = mijiaDevices(api, dev_name=args.wifispeaker_name)
300
+ wifispeaker = mijiaDevice(api, dev_name=args.wifispeaker_name)
316
301
  wifispeaker.run_action('execute-text-directive', _in=[args.run, args.quiet])
317
302
  if hasattr(args, 'func') and args.func is not None:
318
303
  if args.func == 'get':
@@ -1,5 +1,5 @@
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
@@ -53,16 +53,30 @@ class mijiaAPI(object):
53
53
  return True
54
54
  return False
55
55
 
56
- def get_devices_list(self) -> dict:
56
+ def get_devices_list(self) -> list:
57
57
  """
58
58
  获取设备列表。
59
59
 
60
60
  Returns:
61
61
  dict: 设备列表。
62
62
  """
63
- uri = '/home/device_list'
64
- data = {"getVirtualModel": False, "getHuamiDevices": 0}
65
- return self._post_process(post_data(self.session, self.ssecurity, uri, data))
63
+ uri = '/home/home_device_list'
64
+ home_list = self.get_homes_list()
65
+ devices = []
66
+ for home in home_list:
67
+ data = {
68
+ "home_owner": home['uid'],
69
+ "home_id": int(home['id']),
70
+ "limit": 200,
71
+ "get_split_device": True,
72
+ "support_smart_home": True,
73
+ "get_cariot_device": True,
74
+ "get_third_device": True
75
+ }
76
+ ret = self._post_process(post_data(self.session, self.ssecurity, uri, data))
77
+ if ret and ret.get('device_info'):
78
+ devices.extend(ret['device_info'])
79
+ return devices
66
80
 
67
81
  def get_homes_list(self) -> list:
68
82
  """
@@ -71,9 +85,9 @@ class mijiaAPI(object):
71
85
  Returns:
72
86
  list: 家庭列表,包括房间信息。
73
87
  """
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))
88
+ uri = '/v2/homeroom/gethome_merged'
89
+ data = {"fg": True, "fetch_share": True, "fetch_share_dev": True, "limit": 300, "app_ver": 7}
90
+ return self._post_process(post_data(self.session, self.ssecurity, uri, data))['homelist']
77
91
 
78
92
  def get_scenes_list(self, home_id: str) -> list:
79
93
  """
@@ -89,7 +103,10 @@ class mijiaAPI(object):
89
103
  """
90
104
  uri = '/appgateway/miot/appsceneservice/AppSceneService/GetSceneList'
91
105
  data = {"home_id": home_id}
92
- return self._post_process(post_data(self.session, self.ssecurity, uri, data))
106
+ ret = self._post_process(post_data(self.session, self.ssecurity, uri, data))
107
+ if ret and 'scene_info_list' in ret:
108
+ return ret['scene_info_list']
109
+ return []
93
110
 
94
111
  def run_scene(self, scene_id: str) -> bool:
95
112
  """
@@ -105,19 +122,23 @@ class mijiaAPI(object):
105
122
  data = {"scene_id": scene_id, "trigger_key": "user.click"}
106
123
  return self._post_process(post_data(self.session, self.ssecurity, uri, data))
107
124
 
108
- def get_consumable_items(self, home_id: str) -> list:
125
+ def get_consumable_items(self, home_id: str, owner_id: Optional[int] = None) -> list:
109
126
  """
110
127
  获取耗材列表。
111
128
 
112
129
  Args:
113
130
  home_id (str): 家庭ID,从get_homes_list获取。
131
+ owner_id (str, optional): 用户ID,默认为None,如果`home_id`为共享家庭,则需要提供owner_id。
114
132
 
115
133
  Returns:
116
134
  list: 耗材列表。
117
135
  """
118
136
  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))
137
+ data = {"home_id": int(home_id), "owner_id": int(owner_id) if owner_id else self.userId}
138
+ ret = self._post_process(post_data(self.session, self.ssecurity, uri, data))
139
+ if ret and 'items' in ret:
140
+ return ret['items']
141
+ return []
121
142
 
122
143
  def get_devices_prop(self, data: list) -> list:
123
144
  """
@@ -155,7 +176,7 @@ class mijiaAPI(object):
155
176
 
156
177
  示例(yeelink.light.lamp4):
157
178
  [
158
- {"did": "1234567890", "siid": 2, "piid": 2, "value": 50} # 设置亮度为50%
179
+ {"did": "1234567890", "siid": 2, "piid": 2, "value": 50}, # 设置亮度为50%
159
180
  {"did": "1234567890", "siid": 2, "piid": 3, "value": 2700} # 设置色温为2700K
160
181
  ]
161
182
 
@@ -178,7 +199,7 @@ class mijiaAPI(object):
178
199
  - value: 参数列表
179
200
 
180
201
  示例(xiaomi.feeder.pi2001):
181
- {"did": "1234567890", "siid": 2, "aiid": 1, "value": [2]}, # 远程喂食2份
202
+ {"did": "1234567890", "siid": 2, "aiid": 1, "value": [2]} # 远程喂食2份
182
203
 
183
204
  Returns:
184
205
  dict: 操作结果。
@@ -186,3 +207,36 @@ class mijiaAPI(object):
186
207
  uri = '/miotspec/action'
187
208
  data = {"params": data}
188
209
  return self._post_process(post_data(self.session, self.ssecurity, uri, data))
210
+
211
+ def get_statistics(self, data: dict) -> list:
212
+ """
213
+ 获取设备的统计信息。
214
+
215
+ Args:
216
+ data (dict): 请求参数,包含以下键:
217
+ - did: 设备ID,从get_devices_list获取
218
+ - key: siid.piid,表示要获取统计数据的属性
219
+ - data_type: 统计类型,可选值包括:
220
+ - 'stat_hour_v3': 按小时统计
221
+ - 'stat_day_v3': 按天统计
222
+ - 'stat_week_v3': 按周统计
223
+ - 'stat_month_v3': 按月统计
224
+ - limit: 返回的最大条目数,可选参数
225
+ - time_start: 开始时间戳,单位为秒
226
+ - time_end: 结束时间戳,单位为秒
227
+
228
+ 示例(lumi.acpartner.mcn04 的 power-consumption):
229
+ {
230
+ "did": "1234567890",
231
+ "key": "7.1",
232
+ "data_type": "stat_month_v3",
233
+ "limit": 24,
234
+ "time_start": 1685548800,
235
+ "time_end": 1750694400,
236
+ } # 2023-06-01 00:00:00 到 2025-06-24 00:00:00 的月度统计数据
237
+
238
+ Returns:
239
+ list: 统计信息列表。
240
+ """
241
+ uri = '/v2/user/statistics'
242
+ return self._post_process(post_data(self.session, self.ssecurity, uri, data))
@@ -74,7 +74,7 @@ class DevAction(object):
74
74
  return f' {self.name}: {self.desc}'
75
75
 
76
76
 
77
- class mijiaDevices(object):
77
+ class mijiaDevice(object):
78
78
  def __init__(
79
79
  self,
80
80
  api: mijiaAPI,
@@ -113,7 +113,7 @@ class mijiaDevices(object):
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
118
  raise ValueError(f"Device {dev_name} not found")
119
119
  elif len(matches) > 1:
@@ -152,14 +152,14 @@ class mijiaDevices(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,6 +168,10 @@ class mijiaDevices(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('Please specify the did')
171
175
  if name not in self.prop_list:
172
176
  raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
173
177
  prop = self.prop_list[name]
@@ -232,28 +236,6 @@ class mijiaDevices(object):
232
236
  logger.debug(f"Set property: {self.name} -> {name}, value: {value}, result: {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
  获取设备的属性值。
@@ -303,7 +285,7 @@ class mijiaDevices(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):
288
+ if not self.set(name, value):
307
289
  raise RuntimeError(f'Failed to set property: {name}')
308
290
  else:
309
291
  super().__setattr__(name, value)
@@ -378,7 +360,7 @@ class mijiaDevices(object):
378
360
 
379
361
  def get_device_info(device_model: str, cache_path: Optional[str] = os.path.join(os.path.expanduser("~"), ".config/mijia-api")) -> dict:
380
362
  """
381
- 获取设备信息,用于初始化mijiaDevices对象。
363
+ 获取设备信息,用于初始化mijiaDevice对象。
382
364
 
383
365
  Args:
384
366
  device_model (str): 设备型号,从get_devices_list获取。
@@ -122,6 +122,8 @@ class mijiaLogin(object):
122
122
  latest_utc_time = max(parsed_times)
123
123
  china_time = latest_utc_time + timedelta(hours=8)
124
124
 
125
+ # [FIXME] 实测此处的过期时间并不准确,实际过期时间可能大于此处获取的时间
126
+ # cookie 中唯一用到的 serviceToken 并无过期时间
125
127
  return china_time
126
128
 
127
129
  def _save_auth(self) -> None:
@@ -188,6 +190,7 @@ class mijiaLogin(object):
188
190
  'ssecurity': ret_data['ssecurity'],
189
191
  'deviceId': data['deviceId'],
190
192
  'serviceToken': cookies['serviceToken'],
193
+ 'cUserId': cookies['cUserId'],
191
194
  'expireTime': self._extract_latest_gmt_datetime(cookies).strftime('%Y-%m-%d %H:%M:%S'),
192
195
  'account_info': self._get_account_info(ret_data['userId'])
193
196
  }
@@ -273,6 +276,7 @@ class mijiaLogin(object):
273
276
  'ssecurity': ret_data['ssecurity'],
274
277
  'deviceId': data['deviceId'],
275
278
  'serviceToken': cookies['serviceToken'],
279
+ 'cUserId': cookies['cUserId'],
276
280
  'expireTime': self._extract_latest_gmt_datetime(cookies).strftime('%Y-%m-%d %H:%M:%S'),
277
281
  'account_info': self._get_account_info(ret_data['userId'])
278
282
  }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mijiaAPI"
3
- version = "1.4.5"
3
+ version = "2.0.0"
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.4.5"
18
+ version = "2.0.0"
19
19
  description = "A Python API for Xiaomi Mijia"
20
20
  authors = ["Do1e <dpj.email@qq.com>"]
21
21
  license = "GPLv3"
File without changes
File without changes
File without changes
File without changes
File without changes