mijiaAPI 3.0.4__tar.gz → 3.1.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.
Files changed (28) hide show
  1. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/CHANGELOG.md +12 -1
  2. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/FAQ.md +2 -1
  3. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/PKG-INFO +4 -8
  4. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/README.md +3 -7
  5. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI/__main__.py +3 -5
  6. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI/apis.py +2 -2
  7. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI/devices.py +71 -57
  8. mijiaapi-3.1.0/mijiaAPI/version.py +1 -0
  9. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI.egg-info/PKG-INFO +4 -8
  10. mijiaapi-3.0.4/mijiaAPI/version.py +0 -1
  11. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/.gitignore +0 -0
  13. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/LICENSE +0 -0
  14. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/demos/test_apis.py +0 -0
  15. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/demos/test_get_statistics.py +0 -0
  16. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/demos/test_login.py +0 -0
  17. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI/__init__.py +0 -0
  18. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI/errors.py +0 -0
  19. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI/logger.py +0 -0
  20. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI/miutils.py +0 -0
  21. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI.egg-info/SOURCES.txt +0 -0
  22. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI.egg-info/dependency_links.txt +0 -0
  23. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI.egg-info/entry_points.txt +0 -0
  24. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI.egg-info/requires.txt +0 -0
  25. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/mijiaAPI.egg-info/top_level.txt +0 -0
  26. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/pyproject.toml +0 -0
  27. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/setup.cfg +0 -0
  28. {mijiaapi-3.0.4 → mijiaapi-3.1.0}/uv.lock +0 -0
@@ -2,9 +2,20 @@
2
2
 
3
3
  本文档记录了项目的v1.3.7以来的重要变更。
4
4
 
5
+ ## [3.1.0](https://github.com/Do1e/mijia-api/compare/v3.0.5...v3.1.0) - 2026-05-27
6
+ ### new feature
7
+ * 删除设备属性的单位`unit`属性,因为 home.miot-spec.com 上已废弃
8
+ ### bugfix
9
+ * 适配 home.miot-spec.com 新的规格页格式
10
+ * 修复 execute-text-directive 中 quiet 参数的类型转换
11
+
12
+ ## [3.0.5](https://github.com/Do1e/mijia-api/compare/v3.0.4...v3.0.5) - 2026-01-24
13
+ ### bugfix
14
+ * 蓝牙设备控制返回 code 为 1 时表示网关已经接收指令,视为成功。
15
+
5
16
  ## [3.0.4](https://github.com/Do1e/mijia-api/compare/v3.0.3...v3.0.4) - 2026-01-12
6
17
  ### bugfix
7
- * api不可用时不对`available`进行缓存,以修复刷新token成果后依然提示不可用的问题
18
+ * api不可用时不对`available`进行缓存,以修复刷新token成功后依然提示不可用的问题
8
19
 
9
20
  ## [3.0.3](https://github.com/Do1e/mijia-api/compare/v3.0.2...v3.0.3) - 2026-01-02
10
21
  ### new feature
@@ -6,7 +6,8 @@
6
6
 
7
7
  ## 扫码登录后的有效期多长?
8
8
 
9
- `serviceToken` 有效期较短,但是已实现自动刷新。用于刷新的 `passToken` 有效期也许是一个月,即扫码登录后理论上可以保活一个月。
9
+ 用于访问API的 `serviceToken` 有效期较短,但是已实现自动刷新。
10
+ 用于刷新的 `passToken` 有效期也许是一个月,即扫码登录后理论上可以保活一个月。但实际上截至当前 commit,我一个半月前的 `passToken` 依然有效。
10
11
 
11
12
  ## XXX设备的XXX如何获取/设置?
12
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mijiaAPI
3
- Version: 3.0.4
3
+ Version: 3.1.0
4
4
  Summary: A Python API for Xiaomi Mijia
5
5
  Author-email: Do1e <i@do1e.cn>
6
6
  License-Expression: GPL-3.0-or-later
@@ -26,12 +26,6 @@ Dynamic: license-file
26
26
  [![PyPI](https://img.shields.io/badge/PyPI-mijiaAPI-blue)](https://pypi.org/project/mijiaAPI/)
27
27
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-green.svg)](https://opensource.org/licenses/GPL-3.0)
28
28
 
29
- ## ⚠️ 重要提醒
30
-
31
- **v1.5.0 和 v3.0.0包含多项破坏性变更!**
32
-
33
- 如果您正在从旧版本升级,请务必查看 [CHANGELOG.md](CHANGELOG.md) 以了解详细的变更内容和迁移指南。
34
-
35
29
  常见问题见 [FAQ.md](FAQ.md)。
36
30
 
37
31
  ## 安装
@@ -40,6 +34,7 @@ Dynamic: license-file
40
34
 
41
35
  ```bash
42
36
  pip install mijiaAPI
37
+ # Or `uv add mijiaAPI` for uv users
43
38
  ```
44
39
 
45
40
  ### 从源码安装
@@ -49,7 +44,8 @@ git clone https://github.com/Do1e/mijia-api.git
49
44
  cd mijia-api
50
45
  pip install .
51
46
  # Or `pip install -e .` for editable mode
52
- # Or `uv sync` for uv users
47
+ # Or `pip install git+https://github.com/Do1e/mijia-api.git` for direct install
48
+ # Or `uv add git+https://github.com/Do1e/mijia-api.git` for uv users
53
49
  ```
54
50
 
55
51
  ### aur
@@ -6,12 +6,6 @@
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 和 v3.0.0包含多项破坏性变更!**
12
-
13
- 如果您正在从旧版本升级,请务必查看 [CHANGELOG.md](CHANGELOG.md) 以了解详细的变更内容和迁移指南。
14
-
15
9
  常见问题见 [FAQ.md](FAQ.md)。
16
10
 
17
11
  ## 安装
@@ -20,6 +14,7 @@
20
14
 
21
15
  ```bash
22
16
  pip install mijiaAPI
17
+ # Or `uv add mijiaAPI` for uv users
23
18
  ```
24
19
 
25
20
  ### 从源码安装
@@ -29,7 +24,8 @@ git clone https://github.com/Do1e/mijia-api.git
29
24
  cd mijia-api
30
25
  pip install .
31
26
  # Or `pip install -e .` for editable mode
32
- # Or `uv sync` for uv users
27
+ # Or `pip install git+https://github.com/Do1e/mijia-api.git` for direct install
28
+ # Or `uv add git+https://github.com/Do1e/mijia-api.git` for uv users
33
29
  ```
34
30
 
35
31
  ### aur
@@ -265,8 +265,7 @@ def get(args):
265
265
  api = init_api(args.auth_path)
266
266
  device = mijiaDevice(api, did=args.did, dev_name=args.dev_name)
267
267
  value = device.get(args.prop_name)
268
- unit = device.prop_list[args.prop_name].unit
269
- print(f"{device.name} ({device.did}) 的 {args.prop_name} 值为 {value} {unit if unit else ''}")
268
+ print(f"{device.name} ({device.did}) 的 {args.prop_name} 值为 {value}")
270
269
 
271
270
  def set(args):
272
271
  api = init_api(args.auth_path)
@@ -276,8 +275,7 @@ def set(args):
276
275
  except Exception as e:
277
276
  print(f"设置 {args.dev_name} 的 {args.prop_name} 值为 {args.value} 失败: {e}")
278
277
  return
279
- unit = device.prop_list[args.prop_name].unit
280
- print(f"{device.name} ({device.did}) 的 {args.prop_name} 值已设置为 {args.value} {unit if unit else ''}")
278
+ print(f"{device.name} ({device.did}) 的 {args.prop_name} 值已设置为 {args.value}")
281
279
 
282
280
  def main(args):
283
281
  args = parse_args(args)
@@ -323,7 +321,7 @@ def main(args):
323
321
  raise ValueError("未找到小爱音箱设备")
324
322
  else:
325
323
  wifispeaker = mijiaDevice(api, dev_name=args.wifispeaker_name)
326
- wifispeaker.run_action('execute-text-directive', _in=[args.run, args.quiet])
324
+ wifispeaker.run_action('execute-text-directive', _in=[args.run, 1 if args.quiet else 0])
327
325
  if hasattr(args, 'func') and args.func is not None:
328
326
  if args.func == 'get':
329
327
  get(args)
@@ -665,7 +665,7 @@ class mijiaAPI():
665
665
  uri = "/miotspec/prop/set"
666
666
  ret_data = self._request(uri, {"params": params})
667
667
  for ret in ret_data:
668
- if ret.get("code", 0) != 0:
668
+ if ret.get("code", 0) not in (0, 1):
669
669
  ret.update({"message": ERROR_CODE.get(str(ret["code"]), "未知错误")})
670
670
  else:
671
671
  ret.update({"message": "成功"})
@@ -741,7 +741,7 @@ class mijiaAPI():
741
741
  ret = self._request(uri, {"params": param})
742
742
  ret_data.append(ret)
743
743
  for ret in ret_data:
744
- if ret.get("code", 0) != 0:
744
+ if ret.get("code", 0) not in (0, 1):
745
745
  ret.update({"message": ERROR_CODE.get(str(ret["code"]), "未知错误")})
746
746
  else:
747
747
  ret.update({"message": "成功"})
@@ -30,7 +30,6 @@ class DevProp():
30
30
  if self.type not in ["bool", "int", "uint", "float", "string"]:
31
31
  raise ValueError(f"不支持的类型: {self.type}, 可选类型: bool, int, uint, float, string")
32
32
  self.rw = prop_dict["rw"]
33
- self.unit = prop_dict["unit"]
34
33
  self.range = prop_dict["range"]
35
34
  self.value_list = prop_dict.get("value-list", None)
36
35
  self.method = prop_dict["method"]
@@ -38,7 +37,7 @@ class DevProp():
38
37
  def __str__(self):
39
38
  lines = [
40
39
  f" {self.name}: {self.desc}",
41
- f" valuetype: {self.type}, rw: {self.rw}, unit: {self.unit}, range: {self.range}"
40
+ f" valuetype: {self.type}, rw: {self.rw}, range: {self.range}"
42
41
  ]
43
42
 
44
43
  if self.value_list:
@@ -190,7 +189,9 @@ class mijiaDevice():
190
189
  method["did"] = self.did
191
190
  method["value"] = value
192
191
  result = self.api.set_devices_prop(method)
193
- if result["code"] != 0:
192
+ if result["code"] == 1:
193
+ logger.warning(f"网关已经接收指令,无法判断是否设置成功: {self.name} -> {name}, 值: {value}")
194
+ elif result["code"] != 0:
194
195
  raise DeviceSetError(self.name, name, result["code"])
195
196
  time.sleep(self.sleep_time)
196
197
  logger.debug(f"设置属性: {self.name} -> {name}, 值: {value}, 结果: {result}")
@@ -228,7 +229,9 @@ class mijiaDevice():
228
229
  raise ValueError(f"无效的参数: {k}. 请勿使用以下参数 ({', '.join(method.keys())})")
229
230
  method[k] = v
230
231
  result = self.api.run_action(method)
231
- if result["code"] != 0:
232
+ if result["code"] == 1:
233
+ logger.warning(f"网关已经接收指令,无法判断是否执行成功: {self.name} -> {name}")
234
+ elif result["code"] != 0:
232
235
  raise DeviceActionError(self.name, name, result["code"])
233
236
  time.sleep(self.sleep_time)
234
237
  logger.debug(f"执行动作: {self.name} -> {name}, 结果: {result}")
@@ -256,7 +259,6 @@ def get_device_info(device_model: str, cache_path: Optional[Union[str, Path]] =
256
259
  - description (str): 属性描述
257
260
  - type (str): 属性数据类型(int、uint、float、bool、string)
258
261
  - rw (str): 读写权限('r' 可读,'w' 可写,'rw' 可读写)
259
- - unit (str): 属性单位
260
262
  - range (list): 属性值范围 [min, max, step]
261
263
  - value-list (list): 枚举值列表
262
264
  - method (dict): API 调用方法参数
@@ -284,72 +286,84 @@ def get_device_info(device_model: str, cache_path: Optional[Union[str, Path]] =
284
286
  })
285
287
  if response.status_code != 200:
286
288
  raise GetDeviceInfoError(device_model)
287
- content = re.search(r"data-page=\"(.*?)\">", response.text)
289
+ content = re.search(r"<script data-page=\"app\" type=\"application/json\">(.*?)</script>", response.text)
288
290
  if content is None:
289
291
  raise GetDeviceInfoError(device_model)
290
292
  content = content.group(1)
291
- content = json.loads(content.replace("&quot;", "\""))
292
-
293
- if content["props"]["product"]:
294
- name = content["props"]["product"]["name"]
295
- model = content["props"]["product"]["model"]
296
- else:
297
- name = content["props"]["spec"]["name"]
298
- model = device_model
293
+ content = json.loads(content)
294
+
295
+ product = content["props"]["product"]
296
+ name = product["name"]
297
+ model = product["model"]
298
+ i18n_zh = content["props"]["i18n"]["zh_cn"]
299
299
  result = {
300
300
  "name": name,
301
301
  "model": model,
302
302
  "properties": [],
303
303
  "actions": []
304
304
  }
305
- services = content["props"]["spec"]["services"]
305
+ services = content["props"]["tree"]["services"]
306
306
 
307
307
  properties_name = []
308
308
  actions_name = []
309
- for siid in services:
310
- if "properties" in services[siid]:
311
- for piid in services[siid]["properties"]:
312
- prop = services[siid]["properties"][piid]
313
- if prop["format"].startswith("int"):
314
- prop_type = "int"
315
- elif prop["format"].startswith("uint"):
316
- prop_type = "uint"
317
- else:
318
- prop_type = prop["format"]
319
- item = {
320
- "name": prop["name"],
321
- "description": f"{prop.get('description', '')} / {prop.get('desc_zh_cn', '')}",
322
- "type": prop_type,
323
- "rw": "".join([
324
- "r" if "read" in prop["access"] else "",
325
- "w" if "write" in prop["access"] else ""
326
- ]),
327
- "unit": prop.get("unit", None),
328
- "range": prop.get("value-range", None),
329
- "value-list": prop.get("value-list", None),
330
- "method": {
331
- "siid": int(siid),
332
- "piid": int(piid)
333
- }
309
+ for svc in services:
310
+ siid = svc["iid"]
311
+ svc_type = svc["type"]
312
+ for prop in svc.get("properties", []):
313
+ piid = prop["iid"]
314
+ if prop["format"].startswith("int"):
315
+ prop_type = "int"
316
+ elif prop["format"].startswith("uint"):
317
+ prop_type = "uint"
318
+ else:
319
+ prop_type = prop["format"]
320
+ access_str = "".join([
321
+ "r" if "read" in prop["access"] else "",
322
+ "w" if "write" in prop["access"] else ""
323
+ ])
324
+ zh_cn = i18n_zh.get(f"service:{siid:03d}:property:{piid:03d}", "")
325
+ item = {
326
+ "name": prop["type"],
327
+ "description": f"{prop['description']} / {zh_cn}".rstrip(" / "),
328
+ "type": prop_type,
329
+ "rw": access_str,
330
+ "range": prop.get("valueRange", None),
331
+ "value-list": None,
332
+ "method": {
333
+ "siid": siid,
334
+ "piid": piid
334
335
  }
335
- if item["name"] in properties_name:
336
- item["name"] = f"{services[siid]['name']}-{item['name']}"
337
- properties_name.append(item["name"])
338
- result["properties"].append({k: None if v == "none" else v for k, v in item.items()})
339
- if "actions" in services[siid]:
340
- for aiid in services[siid]["actions"]:
341
- act = services[siid]["actions"][aiid]
342
- if act["name"] in actions_name:
343
- act["name"] = f"{services[siid]['name']}-{act['name']}"
344
- actions_name.append(act["name"])
345
- result["actions"].append({
346
- "name": act["name"],
347
- "description": f"{act.get('description', '')} / {act.get('desc_zh_cn', '')}",
348
- "method": {
349
- "siid": int(siid),
350
- "aiid": int(aiid)
336
+ }
337
+ if prop.get("valueList"):
338
+ item["value-list"] = []
339
+ for vl_item in prop["valueList"]:
340
+ vl_zh = i18n_zh.get(vl_item.get("i18nKey", ""), "")
341
+ vl_entry = {
342
+ "value": vl_item["value"],
343
+ "description": vl_item["description"]
351
344
  }
352
- })
345
+ if vl_zh:
346
+ vl_entry["desc_zh_cn"] = vl_zh
347
+ item["value-list"].append(vl_entry)
348
+ if item["name"] in properties_name:
349
+ item["name"] = f"{svc_type}-{item['name']}"
350
+ properties_name.append(item["name"])
351
+ result["properties"].append(item)
352
+ for act in svc.get("actions", []):
353
+ aiid = act["iid"]
354
+ zh_cn = i18n_zh.get(f"service:{siid:03d}:action:{aiid:03d}", "")
355
+ act_item = {
356
+ "name": act["type"],
357
+ "description": f"{act['description']} / {zh_cn}".rstrip(" / "),
358
+ "method": {
359
+ "siid": siid,
360
+ "aiid": aiid
361
+ }
362
+ }
363
+ if act_item["name"] in actions_name:
364
+ act_item["name"] = f"{svc_type}-{act_item['name']}"
365
+ actions_name.append(act_item["name"])
366
+ result["actions"].append(act_item)
353
367
 
354
368
  if cache_path is not None:
355
369
  cache_path = Path(cache_path)
@@ -0,0 +1 @@
1
+ version = "3.1.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mijiaAPI
3
- Version: 3.0.4
3
+ Version: 3.1.0
4
4
  Summary: A Python API for Xiaomi Mijia
5
5
  Author-email: Do1e <i@do1e.cn>
6
6
  License-Expression: GPL-3.0-or-later
@@ -26,12 +26,6 @@ Dynamic: license-file
26
26
  [![PyPI](https://img.shields.io/badge/PyPI-mijiaAPI-blue)](https://pypi.org/project/mijiaAPI/)
27
27
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-green.svg)](https://opensource.org/licenses/GPL-3.0)
28
28
 
29
- ## ⚠️ 重要提醒
30
-
31
- **v1.5.0 和 v3.0.0包含多项破坏性变更!**
32
-
33
- 如果您正在从旧版本升级,请务必查看 [CHANGELOG.md](CHANGELOG.md) 以了解详细的变更内容和迁移指南。
34
-
35
29
  常见问题见 [FAQ.md](FAQ.md)。
36
30
 
37
31
  ## 安装
@@ -40,6 +34,7 @@ Dynamic: license-file
40
34
 
41
35
  ```bash
42
36
  pip install mijiaAPI
37
+ # Or `uv add mijiaAPI` for uv users
43
38
  ```
44
39
 
45
40
  ### 从源码安装
@@ -49,7 +44,8 @@ git clone https://github.com/Do1e/mijia-api.git
49
44
  cd mijia-api
50
45
  pip install .
51
46
  # Or `pip install -e .` for editable mode
52
- # Or `uv sync` for uv users
47
+ # Or `pip install git+https://github.com/Do1e/mijia-api.git` for direct install
48
+ # Or `uv add git+https://github.com/Do1e/mijia-api.git` for uv users
53
49
  ```
54
50
 
55
51
  ### aur
@@ -1 +0,0 @@
1
- version = "3.0.4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes