xiaozhi-sdk 0.1.0__tar.gz → 0.2.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 (49) hide show
  1. xiaozhi_sdk-0.2.0/LICENSE +21 -0
  2. xiaozhi_sdk-0.2.0/MANIFEST.in +5 -0
  3. xiaozhi_sdk-0.2.0/PKG-INFO +90 -0
  4. xiaozhi_sdk-0.2.0/README.md +63 -0
  5. xiaozhi_sdk-0.2.0/file/audio/greet.wav +0 -0
  6. xiaozhi_sdk-0.2.0/file/audio/play_music.wav +0 -0
  7. xiaozhi_sdk-0.2.0/file/audio/say_hello.wav +0 -0
  8. xiaozhi_sdk-0.2.0/file/audio/take_photo.wav +0 -0
  9. xiaozhi_sdk-0.2.0/file/image/leijun.jpg +0 -0
  10. xiaozhi_sdk-0.2.0/file/opus/linux-arm64-libopus.so +0 -0
  11. xiaozhi_sdk-0.2.0/file/opus/linux-x64-libopus.so +0 -0
  12. xiaozhi_sdk-0.2.0/file/opus/macos-arm64-libopus.dylib +0 -0
  13. xiaozhi_sdk-0.2.0/file/opus/macos-x64-libopus.dylib +0 -0
  14. xiaozhi_sdk-0.2.0/file/opus/windows-opus.dll +0 -0
  15. xiaozhi_sdk-0.2.0/pyproject.toml +67 -0
  16. xiaozhi_sdk-0.2.0/tests/test_iot.py +28 -0
  17. {xiaozhi_sdk-0.1.0 → xiaozhi_sdk-0.2.0}/tests/test_pic.py +5 -4
  18. xiaozhi_sdk-0.2.0/tests/test_wake_word.py +31 -0
  19. xiaozhi_sdk-0.2.0/tests/test_xiaozhi.py +106 -0
  20. xiaozhi_sdk-0.2.0/xiaozhi_sdk/__init__.py +3 -0
  21. xiaozhi_sdk-0.2.0/xiaozhi_sdk/__main__.py +11 -0
  22. xiaozhi_sdk-0.2.0/xiaozhi_sdk/cli.py +231 -0
  23. xiaozhi_sdk-0.2.0/xiaozhi_sdk/config.py +3 -0
  24. xiaozhi_sdk-0.2.0/xiaozhi_sdk/core.py +269 -0
  25. xiaozhi_sdk-0.2.0/xiaozhi_sdk/iot.py +84 -0
  26. xiaozhi_sdk-0.2.0/xiaozhi_sdk/mcp.py +171 -0
  27. {xiaozhi_sdk-0.1.0 → xiaozhi_sdk-0.2.0}/xiaozhi_sdk/opus.py +13 -11
  28. xiaozhi_sdk-0.2.0/xiaozhi_sdk/utils/__init__.py +57 -0
  29. xiaozhi_sdk-0.2.0/xiaozhi_sdk/utils/mcp_tool.py +185 -0
  30. xiaozhi_sdk-0.2.0/xiaozhi_sdk.egg-info/PKG-INFO +90 -0
  31. xiaozhi_sdk-0.2.0/xiaozhi_sdk.egg-info/SOURCES.txt +43 -0
  32. xiaozhi_sdk-0.2.0/xiaozhi_sdk.egg-info/requires.txt +13 -0
  33. xiaozhi_sdk-0.1.0/PKG-INFO +0 -58
  34. xiaozhi_sdk-0.1.0/README.md +0 -33
  35. xiaozhi_sdk-0.1.0/setup.py +0 -24
  36. xiaozhi_sdk-0.1.0/tests/test_xiaozhi.py +0 -92
  37. xiaozhi_sdk-0.1.0/xiaozhi_sdk/__init__.py +0 -155
  38. xiaozhi_sdk-0.1.0/xiaozhi_sdk/__main__.py +0 -90
  39. xiaozhi_sdk-0.1.0/xiaozhi_sdk/config.py +0 -5
  40. xiaozhi_sdk-0.1.0/xiaozhi_sdk/data.py +0 -58
  41. xiaozhi_sdk-0.1.0/xiaozhi_sdk/iot.py +0 -50
  42. xiaozhi_sdk-0.1.0/xiaozhi_sdk/mcp.py +0 -75
  43. xiaozhi_sdk-0.1.0/xiaozhi_sdk/utils.py +0 -23
  44. xiaozhi_sdk-0.1.0/xiaozhi_sdk.egg-info/PKG-INFO +0 -58
  45. xiaozhi_sdk-0.1.0/xiaozhi_sdk.egg-info/SOURCES.txt +0 -17
  46. xiaozhi_sdk-0.1.0/xiaozhi_sdk.egg-info/requires.txt +0 -3
  47. {xiaozhi_sdk-0.1.0 → xiaozhi_sdk-0.2.0}/setup.cfg +0 -0
  48. {xiaozhi_sdk-0.1.0 → xiaozhi_sdk-0.2.0}/xiaozhi_sdk.egg-info/dependency_links.txt +0 -0
  49. {xiaozhi_sdk-0.1.0 → xiaozhi_sdk-0.2.0}/xiaozhi_sdk.egg-info/top_level.txt +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 dairoot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include file *
4
+ recursive-exclude file *.pyc
5
+ recursive-exclude file __pycache__
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: xiaozhi-sdk
3
+ Version: 0.2.0
4
+ Summary: 一个用于连接和控制小智智能设备的Python SDK,支持实时音频通信、MCP工具集成和设备管理功能。
5
+ Author-email: dairoot <623815825@qq.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dairoot/xiaozhi-sdk
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: numpy
14
+ Requires-Dist: websockets>=15.0.1
15
+ Requires-Dist: aiohttp
16
+ Requires-Dist: av
17
+ Requires-Dist: opuslib
18
+ Requires-Dist: requests
19
+ Requires-Dist: sounddevice
20
+ Requires-Dist: python-socks
21
+ Requires-Dist: click
22
+ Requires-Dist: colorlog
23
+ Requires-Dist: soundfile>=0.13.1
24
+ Requires-Dist: pydub>=0.25.1
25
+ Requires-Dist: pillow>=11.3.0
26
+ Dynamic: license-file
27
+
28
+ # 小智SDK (XiaoZhi SDK)
29
+
30
+ [![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
31
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
32
+ [![PyPI](https://img.shields.io/badge/pypi-xiaozhi--sdk-blue.svg)](https://pypi.org/project/xiaozhi-sdk/)
33
+
34
+ 基于虾哥的 [小智esp32 websocket 通讯协议](https://github.com/78/xiaozhi-esp32/blob/main/docs/websocket.md) 实现的 Python SDK。
35
+
36
+ 一个用于连接和控制小智设备的 Python SDK。支持以下功能:
37
+ - 实时音频通信
38
+ - MCP 工具集成
39
+ - 设备管理与控制
40
+
41
+ ---
42
+
43
+ ## 📦 安装
44
+
45
+ ```bash
46
+ pip install xiaozhi-sdk
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 🚀 快速开始
52
+
53
+ ### 1. 终端使用
54
+
55
+ 最简单的方式是通过终端直接连接设备:
56
+
57
+ #### 查看帮助信息
58
+
59
+ ```bash
60
+ python -m xiaozhi_sdk --help
61
+ ```
62
+
63
+ #### 连接设备(需要提供 MAC 地址)
64
+
65
+ ```bash
66
+ python -m xiaozhi_sdk 00:22:44:66:88:00
67
+ ```
68
+
69
+ ### 2. 编程使用 (高阶用法)
70
+ 参考 [examples](examples/) 文件中的示例代码,可以快速开始使用 SDK。
71
+
72
+
73
+ ---
74
+
75
+ ## ✅ 运行测试
76
+
77
+ ```bash
78
+ # 安装开发依赖
79
+ uv sync --group dev
80
+
81
+ # 运行测试
82
+ uv run pytest
83
+ ```
84
+
85
+
86
+ ---
87
+
88
+ ## 🫡 致敬
89
+
90
+ - 🫡 虾哥的 [xiaozhi-esp32](https://github.com/78/xiaozhi-esp32) 项目
@@ -0,0 +1,63 @@
1
+ # 小智SDK (XiaoZhi SDK)
2
+
3
+ [![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
4
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
5
+ [![PyPI](https://img.shields.io/badge/pypi-xiaozhi--sdk-blue.svg)](https://pypi.org/project/xiaozhi-sdk/)
6
+
7
+ 基于虾哥的 [小智esp32 websocket 通讯协议](https://github.com/78/xiaozhi-esp32/blob/main/docs/websocket.md) 实现的 Python SDK。
8
+
9
+ 一个用于连接和控制小智设备的 Python SDK。支持以下功能:
10
+ - 实时音频通信
11
+ - MCP 工具集成
12
+ - 设备管理与控制
13
+
14
+ ---
15
+
16
+ ## 📦 安装
17
+
18
+ ```bash
19
+ pip install xiaozhi-sdk
20
+ ```
21
+
22
+ ---
23
+
24
+ ## 🚀 快速开始
25
+
26
+ ### 1. 终端使用
27
+
28
+ 最简单的方式是通过终端直接连接设备:
29
+
30
+ #### 查看帮助信息
31
+
32
+ ```bash
33
+ python -m xiaozhi_sdk --help
34
+ ```
35
+
36
+ #### 连接设备(需要提供 MAC 地址)
37
+
38
+ ```bash
39
+ python -m xiaozhi_sdk 00:22:44:66:88:00
40
+ ```
41
+
42
+ ### 2. 编程使用 (高阶用法)
43
+ 参考 [examples](examples/) 文件中的示例代码,可以快速开始使用 SDK。
44
+
45
+
46
+ ---
47
+
48
+ ## ✅ 运行测试
49
+
50
+ ```bash
51
+ # 安装开发依赖
52
+ uv sync --group dev
53
+
54
+ # 运行测试
55
+ uv run pytest
56
+ ```
57
+
58
+
59
+ ---
60
+
61
+ ## 🫡 致敬
62
+
63
+ - 🫡 虾哥的 [xiaozhi-esp32](https://github.com/78/xiaozhi-esp32) 项目
Binary file
Binary file
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xiaozhi-sdk"
7
+ dynamic = ["version"]
8
+ description = "一个用于连接和控制小智智能设备的Python SDK,支持实时音频通信、MCP工具集成和设备管理功能。"
9
+ readme = "README.md"
10
+ authors = [{name = "dairoot", email = "623815825@qq.com"}]
11
+ license = "MIT"
12
+ requires-python = ">=3.9"
13
+ dependencies = [
14
+ "numpy",
15
+ "websockets>=15.0.1",
16
+ "aiohttp",
17
+ "av",
18
+ "opuslib",
19
+ "requests",
20
+ "sounddevice",
21
+ "python-socks",
22
+ "click",
23
+ "colorlog",
24
+ "soundfile>=0.13.1",
25
+ "pydub>=0.25.1",
26
+ "pillow>=11.3.0",
27
+ ]
28
+ classifiers = [
29
+ "Programming Language :: Python :: 3",
30
+ "Operating System :: OS Independent",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/dairoot/xiaozhi-sdk"
35
+
36
+ [tool.setuptools.dynamic]
37
+ version = {attr = "xiaozhi_sdk.__version__"}
38
+
39
+ [tool.setuptools.packages.find]
40
+ include = ["xiaozhi_sdk*"]
41
+
42
+ [tool.setuptools.package-data]
43
+ xiaozhi_sdk = ["../file/**/*"]
44
+
45
+ [tool.uv]
46
+ dev-dependencies = [
47
+ "black>=24.8.0",
48
+ "flake8>=5.0.4",
49
+ "flake8-bugbear",
50
+ "flake8-comprehensions",
51
+ "isort>=5.13.2",
52
+ "mypy>=1.14.1",
53
+ "types-requests",
54
+ "pre-commit>=3.5.0",
55
+ "pytest>=8.3.5",
56
+ "pytest-asyncio",
57
+ "pytest-cov",
58
+ "build>=0.10",
59
+ ]
60
+
61
+ # 保留现有的工具配置
62
+ [tool.coverage.run]
63
+ omit = [
64
+ "xiaozhi_sdk/__main__.py",
65
+ "xiaozhi_sdk/cli.py",
66
+ "tests/*",
67
+ ]
@@ -0,0 +1,28 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ import sys
5
+ import uuid
6
+
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
8
+
9
+ from xiaozhi_sdk.iot import OtaDevice
10
+
11
+
12
+ async def iot_main():
13
+ serial_number = ""
14
+ license_key = ""
15
+ mac_address = "00:22:44:66:88:14"
16
+ ota_url = "http://localhost:3080/api/ota"
17
+ ota_url = None
18
+ ota = OtaDevice(mac_addr=mac_address, client_id=str(uuid.uuid4()), serial_number=serial_number, ota_url=ota_url)
19
+ res = await ota.activate_device()
20
+ print(json.dumps(res["mqtt"]))
21
+
22
+ # if not res.get("activation"):
23
+ # return
24
+ # await ota.check_activate(res["activation"]["challenge"], license_key)
25
+
26
+
27
+ if __name__ == "__main__":
28
+ asyncio.run(iot_main())
@@ -1,15 +1,15 @@
1
- import aiohttp
2
1
  import asyncio
3
2
 
3
+ import aiohttp
4
+
4
5
 
5
6
  async def explain_image():
6
- url = "http://api.xiaozhi.me/mcp/vision/explain"
7
+ url = "http://api.xiaozhi.me/vision/explain"
7
8
  question = "这个图片里有什么?"
8
- image_path = "./file/leijun.jpg"
9
+ image_path = "./file/image/leijun.jpg"
9
10
 
10
11
  boundary = "----ESP32_CAMERA_BOUNDARY"
11
12
  headers = {
12
-
13
13
  "Content-Type": f"multipart/form-data; boundary={boundary}",
14
14
  # 不设置Transfer-Encoding,aiohttp自动处理
15
15
  }
@@ -42,5 +42,6 @@ async def explain_image():
42
42
  text = await resp.text()
43
43
  print("Response:", text)
44
44
 
45
+
45
46
  if __name__ == "__main__":
46
47
  asyncio.run(explain_image())
@@ -0,0 +1,31 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+
5
+ import pytest
6
+
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
8
+
9
+ from xiaozhi_sdk import XiaoZhiWebsocket
10
+
11
+
12
+ MAC_ADDR = "00:22:44:66:88:00"
13
+ ota_url = None
14
+ URL = None
15
+
16
+
17
+ @pytest.mark.asyncio
18
+ async def test_main():
19
+ is_end = asyncio.Event()
20
+ async def message_handler_callback(message):
21
+ if message.get("state") == "stop":
22
+ is_end.set()
23
+ print("message received:", message)
24
+
25
+ xiaozhi = XiaoZhiWebsocket(message_handler_callback, url=URL, ota_url=ota_url)
26
+ await xiaozhi.init_connection(MAC_ADDR)
27
+
28
+ await xiaozhi.send_wake_word("你是")
29
+
30
+ await asyncio.wait_for(is_end.wait(), timeout=20.0)
31
+ await xiaozhi.close()
@@ -0,0 +1,106 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ import time
5
+
6
+ import numpy as np
7
+ import pytest
8
+ import sounddevice as sd
9
+
10
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
11
+
12
+ from xiaozhi_sdk import XiaoZhiWebsocket
13
+ from xiaozhi_sdk.utils import read_audio_file
14
+
15
+
16
+ async def assistant_audio_play(audio_queue, wait_time=5):
17
+ # 创建一个持续播放的流
18
+ stream = sd.OutputStream(samplerate=16000, channels=1, dtype=np.int16)
19
+ stream.start()
20
+ last_time = int(time.time())
21
+ while True:
22
+ if not audio_queue:
23
+ await asyncio.sleep(0.01)
24
+ if last_time and time.time() - last_time > wait_time:
25
+ break
26
+
27
+ continue
28
+
29
+ pcm_data = audio_queue.popleft()
30
+
31
+ # 将字节数据转换为 numpy int16 数组
32
+ audio_array = pcm_data
33
+
34
+ stream.write(audio_array)
35
+ last_time = time.time()
36
+
37
+ stream.stop()
38
+ stream.close()
39
+
40
+
41
+ def mcp_tool_func():
42
+ def mcp_take_photo(data) -> tuple[bytes, bool]:
43
+ with open("./file/image/leijun.jpg", "rb") as f:
44
+ return f.read(), False
45
+
46
+ def mcp_get_device_status(data) -> tuple[dict, bool]:
47
+ data = {
48
+ "audio_speaker": {"volume": 80},
49
+ "screen": {"brightness": 75, "theme": "light"},
50
+ "network": {"type": "wifi", "ssid": "wifi名称", "signal": "strong"},
51
+ }
52
+ return data, False
53
+
54
+ def mcp_set_volume(data) -> tuple[dict, bool]:
55
+ return {}, False
56
+
57
+ from xiaozhi_sdk.utils.mcp_tool import take_photo, get_device_status, set_volume
58
+
59
+ take_photo["tool_func"] = mcp_take_photo
60
+ get_device_status["tool_func"] = mcp_get_device_status
61
+ set_volume["tool_func"] = mcp_set_volume
62
+
63
+ return [take_photo, get_device_status, set_volume]
64
+
65
+
66
+ async def message_handler_callback(message):
67
+ print("message received:", message)
68
+ if message["type"] == "music":
69
+ print("music:", message["text"])
70
+
71
+
72
+ MAC_ADDR = "00:22:44:66:88:00"
73
+
74
+ ota_url = None
75
+ URL = None
76
+
77
+
78
+ # URL = None
79
+
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_main():
83
+ xiaozhi = XiaoZhiWebsocket(message_handler_callback, url=URL, ota_url=ota_url)
84
+
85
+ await xiaozhi.set_mcp_tool(mcp_tool_func())
86
+ await xiaozhi.init_connection(MAC_ADDR)
87
+
88
+ # # say hellow
89
+ for pcm in read_audio_file("./file/audio/say_hello.wav"):
90
+ await xiaozhi.send_audio(pcm)
91
+ await xiaozhi.send_silence_audio()
92
+ await assistant_audio_play(xiaozhi.output_audio_queue)
93
+
94
+ # say take photo
95
+ for pcm in read_audio_file("./file/audio/take_photo.wav"):
96
+ await xiaozhi.send_audio(pcm)
97
+ await xiaozhi.send_silence_audio()
98
+ await assistant_audio_play(xiaozhi.output_audio_queue, 5)
99
+
100
+ # play music
101
+ # for pcm in read_audio_file("./file/audio/play_music.wav"):
102
+ # await xiaozhi.send_audio(pcm)
103
+ # await xiaozhi.send_silence_audio()
104
+ # await assistant_audio_play(xiaozhi.output_audio_queue, 500)
105
+
106
+ await xiaozhi.close()
@@ -0,0 +1,3 @@
1
+ __version__ = "0.2.0"
2
+
3
+ from xiaozhi_sdk.core import XiaoZhiWebsocket # noqa
@@ -0,0 +1,11 @@
1
+ import logging
2
+
3
+ from xiaozhi_sdk.cli import main
4
+
5
+ logger = logging.getLogger("xiaozhi_sdk")
6
+
7
+ if __name__ == "__main__":
8
+ try:
9
+ main()
10
+ except KeyboardInterrupt:
11
+ logger.debug("Stopping...")