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.
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/.gitignore +4 -0
- mijiaapi-4.0.0/PKG-INFO +88 -0
- mijiaapi-4.0.0/README.md +67 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/__main__.py +75 -17
- mijiaapi-4.0.0/mijiaAPI/mcp_server.py +320 -0
- mijiaapi-4.0.0/mijiaAPI/version.py +1 -0
- mijiaapi-4.0.0/mijiaAPI.egg-info/PKG-INFO +88 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/SOURCES.txt +1 -2
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/requires.txt +1 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/pyproject.toml +2 -1
- mijiaapi-4.0.0/uv.lock +2139 -0
- mijiaapi-3.1.0/CHANGELOG.md +0 -156
- mijiaapi-3.1.0/FAQ.md +0 -24
- mijiaapi-3.1.0/PKG-INFO +0 -580
- mijiaapi-3.1.0/README.md +0 -560
- mijiaapi-3.1.0/mijiaAPI/version.py +0 -1
- mijiaapi-3.1.0/mijiaAPI.egg-info/PKG-INFO +0 -580
- mijiaapi-3.1.0/uv.lock +0 -749
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/LICENSE +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/demos/test_apis.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/demos/test_get_statistics.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/demos/test_login.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/__init__.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/apis.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/devices.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/errors.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/logger.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI/miutils.py +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/dependency_links.txt +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/entry_points.txt +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/mijiaAPI.egg-info/top_level.txt +0 -0
- {mijiaapi-3.1.0 → mijiaapi-4.0.0}/setup.cfg +0 -0
mijiaapi-4.0.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/Do1e/mijia-api)
|
|
27
|
+
[](https://pypi.org/project/mijiaAPI/)
|
|
28
|
+
[](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
|
+
* 开发者不对使用本项目产生的任何直接或间接损失负责
|
mijiaapi-4.0.0/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# mijiaAPI
|
|
2
|
+
|
|
3
|
+
小米米家设备的 API,可以使用代码直接控制米家设备。
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Do1e/mijia-api)
|
|
6
|
+
[](https://pypi.org/project/mijiaAPI/)
|
|
7
|
+
[](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
|
-
|
|
96
|
+
run.add_argument(
|
|
76
97
|
'--wifispeaker_name',
|
|
77
98
|
type=str,
|
|
78
99
|
help="指定小爱音箱名称,默认是获取到的第一个小爱音箱",
|
|
79
100
|
default=None,
|
|
80
101
|
)
|
|
81
|
-
|
|
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"
|