mijiaAPI 3.1.0__tar.gz → 4.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.
Files changed (33) hide show
  1. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/.gitignore +4 -0
  2. mijiaapi-4.0.0/PKG-INFO +88 -0
  3. mijiaapi-4.0.0/README.md +67 -0
  4. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/__main__.py +75 -17
  5. mijiaapi-4.0.0/mijiaAPI/mcp_server.py +320 -0
  6. mijiaapi-4.0.0/mijiaAPI/version.py +1 -0
  7. mijiaapi-4.0.0/mijiaAPI.egg-info/PKG-INFO +88 -0
  8. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/SOURCES.txt +1 -2
  9. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/requires.txt +1 -0
  10. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/pyproject.toml +2 -1
  11. mijiaapi-4.0.0/uv.lock +2139 -0
  12. mijiaapi-3.1.0/CHANGELOG.md +0 -156
  13. mijiaapi-3.1.0/FAQ.md +0 -24
  14. mijiaapi-3.1.0/PKG-INFO +0 -580
  15. mijiaapi-3.1.0/README.md +0 -560
  16. mijiaapi-3.1.0/mijiaAPI/version.py +0 -1
  17. mijiaapi-3.1.0/mijiaAPI.egg-info/PKG-INFO +0 -580
  18. mijiaapi-3.1.0/uv.lock +0 -749
  19. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  20. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/LICENSE +0 -0
  21. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/demos/test_apis.py +0 -0
  22. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/demos/test_get_statistics.py +0 -0
  23. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/demos/test_login.py +0 -0
  24. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/__init__.py +0 -0
  25. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/apis.py +0 -0
  26. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/devices.py +0 -0
  27. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/errors.py +0 -0
  28. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/logger.py +0 -0
  29. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/miutils.py +0 -0
  30. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/dependency_links.txt +0 -0
  31. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/entry_points.txt +0 -0
  32. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/top_level.txt +0 -0
  33. {mijiaapi-3.1.0 → mijiaapi-4.0.0}/setup.cfg +0 -0
@@ -17,3 +17,7 @@ wheels/
17
17
 
18
18
  # har files
19
19
  *.har
20
+
21
+ # VitePress
22
+ node_modules/
23
+ .vitepress/
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: mijiaAPI
3
+ Version: 4.0.0
4
+ Summary: A Python API for Xiaomi Mijia
5
+ Author-email: Do1e <i@do1e.cn>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/Do1e/mijia-api
8
+ Project-URL: Repository, https://github.com/Do1e/mijia-api
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: <4.0,>=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: fastmcp>=3.4.2
15
+ Requires-Dist: pillow>=11.3.0
16
+ Requires-Dist: pycryptodome>=3.23.0
17
+ Requires-Dist: qrcode>=8.2
18
+ Requires-Dist: requests>=2.32.5
19
+ Requires-Dist: tzlocal>=5.3.1
20
+ Dynamic: license-file
21
+
22
+ # mijiaAPI
23
+
24
+ 小米米家设备的 API,可以使用代码直接控制米家设备。
25
+
26
+ [![GitHub](https://img.shields.io/badge/GitHub-Do1e%2Fmijia--api-blue)](https://github.com/Do1e/mijia-api)
27
+ [![PyPI](https://img.shields.io/badge/PyPI-mijiaAPI-blue)](https://pypi.org/project/mijiaAPI/)
28
+ [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-green.svg)](https://opensource.org/licenses/GPL-3.0)
29
+
30
+ 📖 **完整文档请见 [mijia-api.do1e.com](https://mijia-api.do1e.com)**
31
+
32
+ ## 安装
33
+
34
+ > 要求 Python >= 3.10
35
+
36
+ ```bash
37
+ pip install mijiaAPI
38
+ # Or `uv add mijiaAPI` for uv users
39
+ ```
40
+
41
+ 其他安装方式(源码安装、AUR)请参考[文档](https://mijia-api.do1e.com)。
42
+
43
+ ## 快速开始
44
+
45
+ ```python
46
+ from mijiaAPI import mijiaAPI, mijiaDevice
47
+
48
+ # 初始化并扫码登录(认证文件默认保存在 ~/.config/mijia-api/auth.json)
49
+ api = mijiaAPI()
50
+ api.login()
51
+
52
+ # 通过设备名称控制设备(推荐)
53
+ device = mijiaDevice(api, dev_name="我的台灯")
54
+ device.on = True # 打开设备
55
+ device.brightness = 60 # 设置亮度为 60%
56
+
57
+ # 查看设备支持的所有属性和动作
58
+ print(device)
59
+ ```
60
+
61
+ CLI 用法:
62
+
63
+ ```bash
64
+ mijiaAPI login # 扫码登录
65
+ mijiaAPI -l # 列出所有设备
66
+ mijiaAPI set --dev_name "台灯" --prop_name "brightness" --value 60
67
+ ```
68
+
69
+ 更多用法(API 基础调用、MCP Server、CLI 完整参数、最佳实践等)请查阅[完整文档](https://mijia-api.do1e.com)。
70
+
71
+ ## 致谢
72
+
73
+ * [janzlan/mijia-api](https://gitee.com/janzlan/mijia-api/tree/master)
74
+ * [米家 APP 网络请求的抓包、加解密与构造的代码笔记](https://imkero.net/posts/mihome-app-api/)
75
+ * [al-one/hass-xiaomi-miot](https://github.com/al-one/hass-xiaomi-miot)
76
+
77
+ ## 开源许可
78
+
79
+ 本项目采用 [GPL-3.0](LICENSE) 开源许可证。
80
+
81
+ **请注意:GPL-3.0 是具有“强传染性”的开源许可证。**
82
+ 如果您在您的项目中使用、修改或分发本项目的代码(包括作为库依赖),您的整个项目也**必须**以 GPL-3.0 或兼容许可证开源发布。
83
+
84
+ ## 免责声明
85
+
86
+ * 本项目仅供学习交流使用,不得用于商业用途,如有侵权请联系删除
87
+ * 用户使用本项目所产生的任何后果,需自行承担风险
88
+ * 开发者不对使用本项目产生的任何直接或间接损失负责
@@ -0,0 +1,67 @@
1
+ # mijiaAPI
2
+
3
+ 小米米家设备的 API,可以使用代码直接控制米家设备。
4
+
5
+ [![GitHub](https://img.shields.io/badge/GitHub-Do1e%2Fmijia--api-blue)](https://github.com/Do1e/mijia-api)
6
+ [![PyPI](https://img.shields.io/badge/PyPI-mijiaAPI-blue)](https://pypi.org/project/mijiaAPI/)
7
+ [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-green.svg)](https://opensource.org/licenses/GPL-3.0)
8
+
9
+ 📖 **完整文档请见 [mijia-api.do1e.com](https://mijia-api.do1e.com)**
10
+
11
+ ## 安装
12
+
13
+ > 要求 Python >= 3.10
14
+
15
+ ```bash
16
+ pip install mijiaAPI
17
+ # Or `uv add mijiaAPI` for uv users
18
+ ```
19
+
20
+ 其他安装方式(源码安装、AUR)请参考[文档](https://mijia-api.do1e.com)。
21
+
22
+ ## 快速开始
23
+
24
+ ```python
25
+ from mijiaAPI import mijiaAPI, mijiaDevice
26
+
27
+ # 初始化并扫码登录(认证文件默认保存在 ~/.config/mijia-api/auth.json)
28
+ api = mijiaAPI()
29
+ api.login()
30
+
31
+ # 通过设备名称控制设备(推荐)
32
+ device = mijiaDevice(api, dev_name="我的台灯")
33
+ device.on = True # 打开设备
34
+ device.brightness = 60 # 设置亮度为 60%
35
+
36
+ # 查看设备支持的所有属性和动作
37
+ print(device)
38
+ ```
39
+
40
+ CLI 用法:
41
+
42
+ ```bash
43
+ mijiaAPI login # 扫码登录
44
+ mijiaAPI -l # 列出所有设备
45
+ mijiaAPI set --dev_name "台灯" --prop_name "brightness" --value 60
46
+ ```
47
+
48
+ 更多用法(API 基础调用、MCP Server、CLI 完整参数、最佳实践等)请查阅[完整文档](https://mijia-api.do1e.com)。
49
+
50
+ ## 致谢
51
+
52
+ * [janzlan/mijia-api](https://gitee.com/janzlan/mijia-api/tree/master)
53
+ * [米家 APP 网络请求的抓包、加解密与构造的代码笔记](https://imkero.net/posts/mihome-app-api/)
54
+ * [al-one/hass-xiaomi-miot](https://github.com/al-one/hass-xiaomi-miot)
55
+
56
+ ## 开源许可
57
+
58
+ 本项目采用 [GPL-3.0](LICENSE) 开源许可证。
59
+
60
+ **请注意:GPL-3.0 是具有“强传染性”的开源许可证。**
61
+ 如果您在您的项目中使用、修改或分发本项目的代码(包括作为库依赖),您的整个项目也**必须**以 GPL-3.0 或兼容许可证开源发布。
62
+
63
+ ## 免责声明
64
+
65
+ * 本项目仅供学习交流使用,不得用于商业用途,如有侵权请联系删除
66
+ * 用户使用本项目所产生的任何后果,需自行承担风险
67
+ * 开发者不对使用本项目产生的任何直接或间接损失负责
@@ -9,6 +9,7 @@ from typing import Optional
9
9
 
10
10
  from .apis import mijiaAPI
11
11
  from .devices import get_device_info, mijiaDevice
12
+ from .mcp_server import run as run_mcp
12
13
  from .version import version
13
14
 
14
15
 
@@ -69,21 +70,65 @@ def parse_args(args):
69
70
  parser.add_argument(
70
71
  '--run',
71
72
  type=str,
73
+ help=argparse.SUPPRESS,
74
+ nargs='?',
75
+ default=None,
76
+ metavar='PROMPT',
77
+ )
78
+
79
+ run = subparsers.add_parser(
80
+ 'run',
72
81
  help="使用自然语言描述你的需求,如果你有小爱音箱的话",
82
+ )
83
+ run.set_defaults(func='run')
84
+ run.add_argument(
85
+ '-p', '--auth_path',
86
+ type=Path,
87
+ default=Path.home() / ".config" / "mijia-api" / "auth.json",
88
+ help="认证文件保存路径,默认保存在 ~/.config/mijia-api/auth.json",
89
+ )
90
+ run.add_argument(
91
+ 'prompt',
92
+ type=str,
93
+ help="使用自然语言描述你的需求",
73
94
  metavar='PROMPT',
74
95
  )
75
- parser.add_argument(
96
+ run.add_argument(
76
97
  '--wifispeaker_name',
77
98
  type=str,
78
99
  help="指定小爱音箱名称,默认是获取到的第一个小爱音箱",
79
100
  default=None,
80
101
  )
81
- parser.add_argument(
102
+ run.add_argument(
82
103
  '--quiet',
83
104
  action='store_true',
84
105
  help="小爱音箱静默执行",
85
106
  )
86
107
 
108
+ mcp_cmd = subparsers.add_parser(
109
+ 'mcp',
110
+ help="启动 MCP server(stdio 传输)",
111
+ )
112
+ mcp_cmd.set_defaults(func='mcp')
113
+ mcp_cmd.add_argument(
114
+ '-p', '--auth_path',
115
+ type=Path,
116
+ default=Path.home() / ".config" / "mijia-api" / "auth.json",
117
+ help="认证文件保存路径,默认保存在 ~/.config/mijia-api/auth.json",
118
+ )
119
+
120
+ login_cmd = subparsers.add_parser(
121
+ 'login',
122
+ help="二维码登录米家账号",
123
+ )
124
+ login_cmd.set_defaults(func='login')
125
+ login_cmd.add_argument(
126
+ '-p', '--auth_path',
127
+ type=Path,
128
+ default=Path.home() / ".config" / "mijia-api" / "auth.json",
129
+ help="认证文件保存路径,默认保存在 ~/.config/mijia-api/auth.json",
130
+ )
131
+
87
132
  get = subparsers.add_parser(
88
133
  'get',
89
134
  help="获取设备属性",
@@ -280,6 +325,11 @@ def set(args):
280
325
  def main(args):
281
326
  args = parse_args(args)
282
327
 
328
+ if args.run is not None:
329
+ print("错误: '--run' 参数已弃用,请使用 'run' 子命令代替。")
330
+ print(f"新用法: mijiaAPI run \"{args.run}\"")
331
+ sys.exit(1)
332
+
283
333
  if args.get_device_info:
284
334
  device_info = get_device_info(args.get_device_info)
285
335
  print(json.dumps(device_info, indent=2, ensure_ascii=False))
@@ -288,10 +338,18 @@ def main(args):
288
338
  args.list_scenes or
289
339
  args.list_consumable_items or
290
340
  args.run_scene or
291
- args.run or
292
341
  hasattr(args, 'func') and args.func is not None):
293
342
  return
294
343
 
344
+ if hasattr(args, 'func') and args.func == 'mcp':
345
+ run_mcp(args.auth_path)
346
+ return
347
+ if hasattr(args, 'func') and args.func == 'login':
348
+ api = init_api(args.auth_path)
349
+ if not api.available:
350
+ api.login()
351
+ return
352
+
295
353
  api = init_api(args.auth_path)
296
354
  device_mapping = None
297
355
  home_mapping = None
@@ -308,25 +366,25 @@ def main(args):
308
366
  if args.run_scene:
309
367
  for scene_id in args.run_scene:
310
368
  run_scene(api, scene_id, scene_mapping=scenes_mapping)
311
- if args.run:
312
- if device_mapping is None:
313
- device_mapping = get_devices_list(api, verbose=False)
314
- if args.wifispeaker_name is None:
315
- wifispeaker = None
316
- for device in device_mapping.values():
317
- if 'xiaomi.wifispeaker' in device['model']:
318
- wifispeaker = mijiaDevice(api, dev_name=device['name'])
319
- break
320
- if wifispeaker is None:
321
- raise ValueError("未找到小爱音箱设备")
322
- else:
323
- wifispeaker = mijiaDevice(api, dev_name=args.wifispeaker_name)
324
- wifispeaker.run_action('execute-text-directive', _in=[args.run, 1 if args.quiet else 0])
325
369
  if hasattr(args, 'func') and args.func is not None:
326
370
  if args.func == 'get':
327
371
  get(args)
328
372
  if args.func == 'set':
329
373
  set(args)
374
+ if args.func == 'run':
375
+ if device_mapping is None:
376
+ device_mapping = get_devices_list(api, verbose=False)
377
+ if args.wifispeaker_name is None:
378
+ wifispeaker = None
379
+ for device in device_mapping.values():
380
+ if 'xiaomi.wifispeaker' in device['model']:
381
+ wifispeaker = mijiaDevice(api, dev_name=device['name'])
382
+ break
383
+ if wifispeaker is None:
384
+ raise ValueError("未找到小爱音箱设备")
385
+ else:
386
+ wifispeaker = mijiaDevice(api, dev_name=args.wifispeaker_name)
387
+ wifispeaker.run_action('execute-text-directive', _in=[args.prompt, 1 if args.quiet else 0])
330
388
 
331
389
  def cli():
332
390
  main(sys.argv[1:])
@@ -0,0 +1,320 @@
1
+ import json
2
+ import logging
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from fastmcp import FastMCP
8
+
9
+ from .apis import mijiaAPI
10
+ from .devices import get_device_info, mijiaDevice
11
+ from .errors import LoginError
12
+ from .logger import logger
13
+
14
+
15
+ mcp = FastMCP("mijia-api")
16
+
17
+ _api: Optional[mijiaAPI] = None
18
+ _auth_path: Optional[Path] = None
19
+
20
+
21
+ def _get_api() -> mijiaAPI:
22
+ global _api
23
+ if _api is not None:
24
+ return _api
25
+ raise RuntimeError("mijiaAPI 未初始化,请先登录后重启 MCP server")
26
+
27
+
28
+ def _refresh_if_needed(api: mijiaAPI) -> None:
29
+ if not api.available:
30
+ try:
31
+ api._refresh_token()
32
+ except LoginError:
33
+ raise RuntimeError(
34
+ "认证已失效且无法自动刷新,请运行 "
35
+ f"`mijiaAPI login -p {_auth_path}` 重新登录后重启 MCP server"
36
+ )
37
+
38
+
39
+ def run(auth_path: Path) -> None:
40
+ global _api, _auth_path
41
+ _auth_path = auth_path if not auth_path.is_dir() else auth_path / "auth.json"
42
+
43
+ if not _auth_path.exists():
44
+ print(
45
+ f"认证文件不存在: {_auth_path}\n"
46
+ f"请先运行 `mijiaAPI login -p {_auth_path}` 登录后再启动 MCP server",
47
+ file=sys.stderr,
48
+ )
49
+ sys.exit(1)
50
+
51
+ logger.handlers = [h for h in logger.handlers if not isinstance(h, logging.StreamHandler) or h.stream is sys.stderr]
52
+ try:
53
+ _api = mijiaAPI(auth_data_path=_auth_path)
54
+ if not _api.available:
55
+ _api._refresh_token()
56
+ if not _api.available:
57
+ raise LoginError(-1, "认证不可用")
58
+ except Exception as e:
59
+ print(
60
+ f"认证不可用且无法自动刷新: {e}\n"
61
+ f"请运行 `mijiaAPI login -p {_auth_path}` 重新登录后再启动 MCP server",
62
+ file=sys.stderr,
63
+ )
64
+ sys.exit(1)
65
+
66
+ logger.info(f"MCP server 启动,认证文件: {_auth_path}")
67
+ mcp.run()
68
+
69
+
70
+ @mcp.tool
71
+ def list_homes() -> str:
72
+ """列出米家所有家庭及房间信息。
73
+
74
+ 返回每个家庭的名称、ID、地址、房间列表(含房间内设备名称)。
75
+ """
76
+ api = _get_api()
77
+ _refresh_if_needed(api)
78
+ homes = api.get_homes_list()
79
+ return json.dumps(homes, ensure_ascii=False)
80
+
81
+
82
+ @mcp.tool
83
+ def list_devices(home_id: Optional[str] = None) -> str:
84
+ """列出米家设备列表(包含共享设备)。
85
+
86
+ 参数:
87
+ home_id: 可选,指定家庭ID则仅列出该家庭的设备;不传则列出所有设备。
88
+
89
+ 返回每个设备的名称、did、model、在线状态等。
90
+ """
91
+ api = _get_api()
92
+ _refresh_if_needed(api)
93
+ devices = api.get_devices_list() + api.get_shared_devices_list()
94
+ if home_id is not None:
95
+ devices = [d for d in devices if d.get("home_id") == home_id]
96
+ return json.dumps(devices, ensure_ascii=False)
97
+
98
+
99
+ @mcp.tool
100
+ def list_scenes(home_id: Optional[str] = None) -> str:
101
+ """列出米家手动场景列表。
102
+
103
+ 参数:
104
+ home_id: 可选,指定家庭ID则仅列出该家庭的场景;不传则列出所有场景。
105
+
106
+ 返回每个场景的名称、scene_id、所属家庭等。
107
+ """
108
+ api = _get_api()
109
+ _refresh_if_needed(api)
110
+ scenes = api.get_scenes_list(home_id)
111
+ return json.dumps(scenes, ensure_ascii=False)
112
+
113
+
114
+ @mcp.tool
115
+ def list_consumables(home_id: Optional[str] = None) -> str:
116
+ """列出耗材列表(如滤芯、电池等需更换的配件)。
117
+
118
+ 参数:
119
+ home_id: 可选,指定家庭ID则仅列出该家庭的耗材;不传则列出所有耗材。
120
+
121
+ 返回耗材所属设备、描述、当前值等。
122
+ """
123
+ api = _get_api()
124
+ _refresh_if_needed(api)
125
+ items = api.get_consumable_items(home_id)
126
+ return json.dumps(items, ensure_ascii=False)
127
+
128
+
129
+ @mcp.tool
130
+ def get_device_spec(device_model: str) -> str:
131
+ """获取设备规格信息(属性和动作列表)。
132
+
133
+ 参数:
134
+ device_model: 设备型号,例如 'yeelink.light.lamp4',可从 list_devices 获取。
135
+
136
+ 返回设备支持的属性(名称/描述/类型/读写/范围/枚举值)和动作列表,
137
+ 用于确定 get_device_properties / set_device_property / run_device_action 的可用参数名。
138
+ """
139
+ api = _get_api()
140
+ info = get_device_info(device_model, cache_path=api.auth_data_path.parent)
141
+ return json.dumps(info, ensure_ascii=False)
142
+
143
+
144
+ @mcp.tool
145
+ def get_device_properties(
146
+ dev_name: Optional[str] = None,
147
+ did: Optional[str] = None,
148
+ prop_names: Optional[list[str]] = None,
149
+ ) -> str:
150
+ """获取设备属性值(高层封装,无需 siid/piid)。
151
+
152
+ 参数:
153
+ dev_name: 设备名称(米家APP中设定),与 did 二选一。
154
+ did: 设备did,优先于 dev_name。
155
+ prop_names: 属性名列表(如 ["brightness", "on"]),可从 get_device_spec 获取;
156
+ 不传则返回设备所有可读属性的值。
157
+
158
+ 返回属性名与对应值的映射。
159
+ """
160
+ api = _get_api()
161
+ _refresh_if_needed(api)
162
+ device = mijiaDevice(api, did=did, dev_name=dev_name)
163
+ names = prop_names if prop_names else [k for k in device.prop_list if "_" not in k]
164
+ result = {}
165
+ for name in names:
166
+ prop = device.prop_list.get(name)
167
+ if prop is None or "r" not in prop.rw:
168
+ continue
169
+ try:
170
+ result[name] = device.get(name)
171
+ except Exception as e:
172
+ result[name] = f"<读取失败: {e}>"
173
+ return json.dumps(result, ensure_ascii=False)
174
+
175
+
176
+ @mcp.tool
177
+ def set_device_property(
178
+ prop_name: str,
179
+ value: str,
180
+ dev_name: Optional[str] = None,
181
+ did: Optional[str] = None,
182
+ ) -> str:
183
+ """设置设备属性值(高层封装,无需 siid/piid)。
184
+
185
+ 参数:
186
+ prop_name: 属性名,可从 get_device_spec 获取。
187
+ value: 要设置的值。布尔值传 "true"/"false";数值传对应数字字符串。
188
+ dev_name: 设备名称(米家APP中设定),与 did 二选一。
189
+ did: 设备did,优先于 dev_name。
190
+
191
+ 返回设置结果。
192
+ """
193
+ api = _get_api()
194
+ _refresh_if_needed(api)
195
+ device = mijiaDevice(api, did=did, dev_name=dev_name)
196
+ device.set(prop_name, value)
197
+ return f"{device.name}({device.did}) 的 {prop_name} 已设置为 {value}"
198
+
199
+
200
+ @mcp.tool
201
+ def run_device_action(
202
+ action_name: str,
203
+ dev_name: Optional[str] = None,
204
+ did: Optional[str] = None,
205
+ value: Optional[list] = None,
206
+ ) -> str:
207
+ """执行设备动作(高层封装,无需 siid/aiid)。
208
+
209
+ 参数:
210
+ action_name: 动作名,可从 get_device_spec 获取。
211
+ dev_name: 设备名称(米家APP中设定),与 did 二选一。
212
+ did: 设备did,优先于 dev_name。
213
+ value: 可选,动作参数列表,根据具体动作定义而定。
214
+
215
+ 返回执行结果。
216
+ """
217
+ api = _get_api()
218
+ _refresh_if_needed(api)
219
+ device = mijiaDevice(api, did=did, dev_name=dev_name)
220
+ device.run_action(action_name, value=value)
221
+ return f"{device.name}({device.did}) 的动作 {action_name} 执行成功"
222
+
223
+
224
+ @mcp.tool
225
+ def run_scene(scene_id_or_name: str) -> str:
226
+ """运行米家手动场景。
227
+
228
+ 参数:
229
+ scene_id_or_name: 场景ID或场景名称。名称需唯一,否则请使用 scene_id。
230
+
231
+ 返回执行结果。
232
+ """
233
+ api = _get_api()
234
+ _refresh_if_needed(api)
235
+ scenes = api.get_scenes_list()
236
+ scene_mapping = {s["scene_id"]: s for s in scenes}
237
+ target = scene_id_or_name
238
+ if target not in scene_mapping:
239
+ found = [s for s in scenes if s["name"] == target]
240
+ if not found:
241
+ return f"场景 {scene_id_or_name} 未找到"
242
+ if len(found) > 1:
243
+ return f"找到多个名为 {scene_id_or_name} 的场景,请使用 scene_id"
244
+ target = found[0]["scene_id"]
245
+ scene = scene_mapping[target]
246
+ ret = api.run_scene(target, scene["home_id"])
247
+ return f"场景 {scene['name']}({target}) 运行{'成功' if ret else '失败'}"
248
+
249
+
250
+ @mcp.tool
251
+ def get_statistics(
252
+ did: str,
253
+ key: str,
254
+ data_type: str,
255
+ limit: int = 6,
256
+ time_start: Optional[int] = None,
257
+ time_end: Optional[int] = None,
258
+ ) -> str:
259
+ """获取设备统计数据(如耗电量、使用时长)。
260
+
261
+ 参数:
262
+ did: 设备ID,可从 list_devices 获取。
263
+ key: 统计数据键,格式为 siid.piid(如 "7.1"),需根据设备型号确定。
264
+ data_type: 统计粒度,可选 stat_hour_v3 / stat_day_v3 / stat_week_v3 / stat_month_v3。
265
+ limit: 返回最大条目数,默认6。
266
+ time_start: 起始时间戳(秒),不传默认为30天前。
267
+ time_end: 结束时间戳(秒),不传默认为当前时间。
268
+
269
+ 返回统计条目列表(时间戳与数值)。
270
+ """
271
+ import time
272
+ api = _get_api()
273
+ _refresh_if_needed(api)
274
+ now = int(time.time())
275
+ data = {
276
+ "did": did,
277
+ "key": key,
278
+ "data_type": data_type,
279
+ "limit": limit,
280
+ "time_start": time_start if time_start is not None else now - 30 * 24 * 3600,
281
+ "time_end": time_end if time_end is not None else now,
282
+ }
283
+ ret = api.get_statistics(data)
284
+ return json.dumps(ret, ensure_ascii=False)
285
+
286
+
287
+ @mcp.tool
288
+ def run_speaker_command(
289
+ prompt: str,
290
+ speaker_name: Optional[str] = None,
291
+ quiet: bool = False,
292
+ ) -> str:
293
+ """通过小爱音箱执行自然语言指令。
294
+
295
+ 参数:
296
+ prompt: 自然语言指令,如 "打开卧室台灯"、"把亮度调到50%"。
297
+ speaker_name: 可选,指定小爱音箱名称,默认使用获取到的第一个小爱音箱。
298
+ quiet: 是否静默执行(不语音播报),默认 False。
299
+
300
+ 返回执行结果。
301
+ """
302
+ api = _get_api()
303
+ _refresh_if_needed(api)
304
+ devices = api.get_devices_list()
305
+ if speaker_name is None:
306
+ match = None
307
+ for device in devices:
308
+ if "xiaomi.wifispeaker" in device["model"]:
309
+ match = device
310
+ break
311
+ if match is None:
312
+ return "未找到小爱音箱设备"
313
+ else:
314
+ matches = [d for d in devices if d["name"] == speaker_name]
315
+ if not matches:
316
+ return f"未找到名为 {speaker_name} 的小爱音箱"
317
+ match = matches[0]
318
+ speaker = mijiaDevice(api, did=match["did"])
319
+ speaker.run_action("execute-text-directive", _in=[prompt, 1 if quiet else 0])
320
+ return f"已通过 {match['name']} 执行: {prompt}"
@@ -0,0 +1 @@
1
+ version = "4.0.0"