xiaozhi-sdk 0.0.2__tar.gz → 0.0.4__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.

Potentially problematic release.


This version of xiaozhi-sdk might be problematic. Click here for more details.

Files changed (34) hide show
  1. {xiaozhi_sdk-0.0.2/xiaozhi_sdk.egg-info → xiaozhi_sdk-0.0.4}/PKG-INFO +11 -17
  2. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/README.md +6 -3
  3. xiaozhi_sdk-0.0.4/pyproject.toml +64 -0
  4. xiaozhi_sdk-0.0.4/setup.cfg +4 -0
  5. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/tests/test_iot.py +6 -2
  6. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/__init__.py +39 -17
  7. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/__main__.py +33 -11
  8. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/data.py +1 -1
  9. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/iot.py +8 -9
  10. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/mcp.py +8 -6
  11. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4/xiaozhi_sdk.egg-info}/PKG-INFO +11 -17
  12. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk.egg-info/SOURCES.txt +1 -2
  13. xiaozhi_sdk-0.0.2/setup.cfg +0 -17
  14. xiaozhi_sdk-0.0.2/setup.py +0 -49
  15. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/LICENSE +0 -0
  16. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/MANIFEST.in +0 -0
  17. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/audio/greet.wav +0 -0
  18. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/audio/say_hello.wav +0 -0
  19. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/audio/take_photo.wav +0 -0
  20. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/image/leijun.jpg +0 -0
  21. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/opus/linux-arm64-libopus.so +0 -0
  22. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/opus/linux-x64-libopus.so +0 -0
  23. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/opus/macos-arm64-libopus.dylib +0 -0
  24. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/opus/macos-x64-libopus.dylib +0 -0
  25. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/file/opus/windows-x86_64-opus.dll +0 -0
  26. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/tests/test_opus.py +0 -0
  27. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/tests/test_pic.py +0 -0
  28. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/tests/test_xiaozhi.py +0 -0
  29. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/config.py +0 -0
  30. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/opus.py +0 -0
  31. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk/utils.py +0 -0
  32. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk.egg-info/dependency_links.txt +0 -0
  33. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk.egg-info/requires.txt +0 -0
  34. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.4}/xiaozhi_sdk.egg-info/top_level.txt +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xiaozhi-sdk
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: 一个用于连接和控制小智智能设备的Python SDK,支持实时音频通信、MCP工具集成和设备管理功能。
5
- Home-page: https://github.com/dairoot/xiaozhi-sdk
6
- Author: dairoot
7
- Author-email: 623815825@qq.com
5
+ Author-email: dairoot <623815825@qq.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/dairoot/xiaozhi-sdk
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.8.1
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: numpy
@@ -19,16 +19,7 @@ Requires-Dist: opuslib
19
19
  Requires-Dist: requests
20
20
  Requires-Dist: sounddevice
21
21
  Requires-Dist: python-socks
22
- Dynamic: author
23
- Dynamic: author-email
24
- Dynamic: classifier
25
- Dynamic: description
26
- Dynamic: description-content-type
27
- Dynamic: home-page
28
22
  Dynamic: license-file
29
- Dynamic: requires-dist
30
- Dynamic: requires-python
31
- Dynamic: summary
32
23
 
33
24
  # 小智SDK (XiaoZhi SDK)
34
25
 
@@ -71,9 +62,12 @@ positional arguments:
71
62
  device 你的小智设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)
72
63
 
73
64
  options:
74
- -h, --help 显示帮助信息并退出
75
- --url URL 小智服务 websocket 地址
76
- --ota_url OTA_URL 小智 OTA 地址
65
+ -h, --help show this help message and exit
66
+ --url URL 服务端websocket地址
67
+ --ota_url OTA_URL OTA地址
68
+ --serial_number SERIAL_NUMBER 设备的序列号
69
+ --license_key LICENSE_KEY 设备的授权密钥
70
+
77
71
  ```
78
72
 
79
73
  #### 连接设备(需要提供 MAC 地址)
@@ -39,9 +39,12 @@ positional arguments:
39
39
  device 你的小智设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)
40
40
 
41
41
  options:
42
- -h, --help 显示帮助信息并退出
43
- --url URL 小智服务 websocket 地址
44
- --ota_url OTA_URL 小智 OTA 地址
42
+ -h, --help show this help message and exit
43
+ --url URL 服务端websocket地址
44
+ --ota_url OTA_URL OTA地址
45
+ --serial_number SERIAL_NUMBER 设备的序列号
46
+ --license_key LICENSE_KEY 设备的授权密钥
47
+
45
48
  ```
46
49
 
47
50
  #### 连接设备(需要提供 MAC 地址)
@@ -0,0 +1,64 @@
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 = {text = "MIT"}
12
+ requires-python = ">=3.8.1"
13
+ dependencies = [
14
+ "numpy",
15
+ "websockets",
16
+ "aiohttp",
17
+ "av",
18
+ "opuslib",
19
+ "requests",
20
+ "sounddevice",
21
+ "python-socks",
22
+ ]
23
+ classifiers = [
24
+ "Programming Language :: Python :: 3",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/dairoot/xiaozhi-sdk"
31
+
32
+
33
+
34
+ [tool.setuptools.dynamic]
35
+ version = {attr = "xiaozhi_sdk.__version__"}
36
+
37
+ [tool.setuptools.packages.find]
38
+ include = ["xiaozhi_sdk*"]
39
+
40
+ [tool.setuptools.package-data]
41
+ xiaozhi_sdk = ["../file/**/*"]
42
+
43
+ # 保留现有的工具配置
44
+ [tool.coverage.run]
45
+ omit = [
46
+ "xiaozhi_sdk/__main__.py",
47
+ "tests/*",
48
+ ]
49
+
50
+ [dependency-groups]
51
+ dev = [
52
+ "black>=24.8.0",
53
+ "flake8>=5.0.4",
54
+ "flake8-bugbear",
55
+ "flake8-comprehensions",
56
+ "isort>=5.13.2",
57
+ "mypy>=1.14.1",
58
+ "types-requests",
59
+ "pre-commit>=3.5.0",
60
+ "pytest>=8.3.5",
61
+ "pytest-asyncio",
62
+ "pytest-cov",
63
+ "build>=0.10",
64
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -13,7 +13,11 @@ from xiaozhi_sdk.iot import OtaDevice
13
13
  async def test_main():
14
14
  serial_number = ""
15
15
  license_key = ""
16
- mac_address = "00:22:44:66:88:00"
17
- ota = OtaDevice(mac_addr=mac_address, client_id=str(uuid.uuid4()), serial_number=serial_number)
16
+ mac_address = "00:22:44:66:88:04"
17
+ ota_url = "http://localhost:3080/api/ota"
18
+
19
+ ota = OtaDevice(mac_addr=mac_address, client_id=str(uuid.uuid4()), serial_number=serial_number, ota_url=ota_url)
18
20
  res = await ota.activate_device()
21
+ if not res.get("activation"):
22
+ return
19
23
  await ota.check_activate(res["activation"]["challenge"], license_key)
@@ -1,5 +1,8 @@
1
+ __version__ = "0.0.4"
2
+
1
3
  import asyncio
2
4
  import json
5
+ import logging
3
6
  import os
4
7
  import re
5
8
  import uuid
@@ -16,6 +19,8 @@ from xiaozhi_sdk.utils import get_wav_info, read_audio_file, setup_opus
16
19
  setup_opus()
17
20
  from xiaozhi_sdk.opus import AudioOpus
18
21
 
22
+ logger = logging.getLogger("xiaozhi_sdk")
23
+
19
24
 
20
25
  class XiaoZhiWebsocket(McpTool):
21
26
 
@@ -75,7 +80,7 @@ class XiaoZhiWebsocket(McpTool):
75
80
  listen_message = {"session_id": self.session_id, "type": "listen", "state": "start", "mode": "realtime"}
76
81
  await self.websocket.send(json.dumps(listen_message))
77
82
 
78
- async def _activate_iot_device(self, ota_info: Dict[str, Any]) -> None:
83
+ async def _activate_iot_device(self, license_key: str, ota_info: Dict[str, Any]) -> None:
79
84
  """激活IoT设备"""
80
85
  if not ota_info.get("activation"):
81
86
  return
@@ -88,7 +93,7 @@ class XiaoZhiWebsocket(McpTool):
88
93
  await asyncio.sleep(3)
89
94
 
90
95
  for _ in range(10):
91
- if await self.ota.check_activate(challenge):
96
+ if await self.ota.check_activate(challenge, license_key):
92
97
  break
93
98
  await asyncio.sleep(3)
94
99
 
@@ -114,20 +119,23 @@ class XiaoZhiWebsocket(McpTool):
114
119
 
115
120
  async def _handle_websocket_message(self, message: Any) -> None:
116
121
  """处理接受到的WebSocket消息"""
122
+
123
+ # audio data
117
124
  if isinstance(message, bytes):
118
125
  pcm_array = await self.audio_opus.opus_to_pcm(message)
119
126
  self.output_audio_queue.extend(pcm_array)
120
- else:
121
- data = json.loads(message)
122
- message_type = data["type"]
127
+ return
123
128
 
124
- if message_type == "hello":
125
- self.hello_received.set()
126
- self.session_id = data["session_id"]
127
- elif message_type == "mcp":
128
- await self.mcp(data)
129
- elif self.message_handler_callback:
130
- await self.message_handler_callback(data)
129
+ # json message
130
+ data = json.loads(message)
131
+ message_type = data["type"]
132
+ if message_type == "hello":
133
+ self.hello_received.set()
134
+ self.session_id = data["session_id"]
135
+ elif message_type == "mcp":
136
+ await self.mcp(data)
137
+ elif self.message_handler_callback:
138
+ await self.message_handler_callback(data)
131
139
 
132
140
  async def _message_handler(self) -> None:
133
141
  """消息处理器"""
@@ -139,12 +147,15 @@ class XiaoZhiWebsocket(McpTool):
139
147
  await self.message_handler_callback(
140
148
  {"type": "websocket", "state": "close", "source": "sdk.message_handler"}
141
149
  )
150
+ logger.info("[websocket] close")
142
151
 
143
152
  async def set_mcp_tool_callback(self, tool_func: Dict[str, Callable[..., Any]]) -> None:
144
153
  """设置MCP工具回调函数"""
145
154
  self.tool_func = tool_func
146
155
 
147
- async def init_connection(self, mac_addr: str, aec: bool = False) -> None:
156
+ async def init_connection(
157
+ self, mac_addr: str, aec: bool = False, serial_number: str = "", license_key: str = ""
158
+ ) -> None:
148
159
  """初始化连接"""
149
160
  # 校验MAC地址格式 XX:XX:XX:XX:XX:XX
150
161
  mac_pattern = r"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"
@@ -152,8 +163,14 @@ class XiaoZhiWebsocket(McpTool):
152
163
  raise ValueError(f"无效的MAC地址格式: {mac_addr}。正确格式应为 XX:XX:XX:XX:XX:XX")
153
164
 
154
165
  self.mac_addr = mac_addr.lower()
155
- self.ota = OtaDevice(self.mac_addr, self.client_id, self.ota_url)
166
+
167
+ self.ota = OtaDevice(self.mac_addr, self.client_id, self.ota_url, serial_number)
156
168
  ota_info = await self.ota.activate_device()
169
+ ws_url = ota_info["websocket"]["url"]
170
+ self.url = self.url or ws_url
171
+
172
+ if "tenclass.net" not in self.url and "xiaozhi.me" not in self.url:
173
+ logger.warning("[websocket] 检测到非官方服务器,请谨慎使用!当前链接地址: %s", self.url)
157
174
 
158
175
  headers = {
159
176
  "Authorization": "Bearer {}".format(ota_info["websocket"]["token"]),
@@ -161,13 +178,16 @@ class XiaoZhiWebsocket(McpTool):
161
178
  "Device-Id": self.mac_addr,
162
179
  "Client-Id": self.client_id,
163
180
  }
164
- self.url = self.url or ota_info["websocket"]["url"]
165
- self.websocket = await websockets.connect(uri=self.url, additional_headers=headers)
181
+ try:
182
+ self.websocket = await websockets.connect(uri=self.url, additional_headers=headers)
183
+ except websockets.exceptions.InvalidMessage as e:
184
+ logger.error("[websocket] 连接失败,请检查网络连接或设备状态。当前链接地址: %s, 错误信息:%s", self.url, e)
185
+ return
166
186
  self.message_handler_task = asyncio.create_task(self._message_handler())
167
187
 
168
188
  await self._send_hello(aec)
169
189
  await self._start_listen()
170
- asyncio.create_task(self._activate_iot_device(ota_info))
190
+ asyncio.create_task(self._activate_iot_device(license_key, ota_info))
171
191
  await asyncio.sleep(0.5)
172
192
 
173
193
  async def send_audio(self, pcm: bytes) -> None:
@@ -183,6 +203,8 @@ class XiaoZhiWebsocket(McpTool):
183
203
  if self.message_handler_callback:
184
204
  await self.message_handler_callback({"type": "websocket", "state": "close", "source": "sdk.send_audio"})
185
205
  self.websocket = None
206
+ logger.info("[websocket] close")
207
+
186
208
  await asyncio.sleep(0.5)
187
209
  else:
188
210
  await asyncio.sleep(0.1)
@@ -20,11 +20,15 @@ logger = logging.getLogger("xiaozhi_sdk")
20
20
  # 全局状态
21
21
  input_audio_buffer: deque[bytes] = deque()
22
22
  is_playing_audio = False
23
+ is_end = False
23
24
 
24
25
 
25
26
  async def handle_message(message):
26
27
  """处理接收到的消息"""
28
+ global is_end
27
29
  logger.info("message received: %s", message)
30
+ if message["type"] == "websocket" and message["state"] == "close":
31
+ is_end = True
28
32
 
29
33
 
30
34
  async def play_assistant_audio(audio_queue: deque[bytes]):
@@ -36,6 +40,9 @@ async def play_assistant_audio(audio_queue: deque[bytes]):
36
40
  last_audio_time = None
37
41
 
38
42
  while True:
43
+ if is_end:
44
+ return
45
+
39
46
  if not audio_queue:
40
47
  await asyncio.sleep(0.01)
41
48
  if last_audio_time and time.time() - last_audio_time > 1:
@@ -51,16 +58,22 @@ async def play_assistant_audio(audio_queue: deque[bytes]):
51
58
  class XiaoZhiClient:
52
59
  """小智客户端类"""
53
60
 
54
- def __init__(self, mac_address: str, url: Optional[str] = None, ota_url: Optional[str] = None):
55
- self.mac_address = mac_address
61
+ def __init__(
62
+ self,
63
+ url: Optional[str] = None,
64
+ ota_url: Optional[str] = None,
65
+ ):
56
66
  self.xiaozhi: Optional[XiaoZhiWebsocket] = None
57
67
  self.url = url
58
68
  self.ota_url = ota_url
59
69
 
60
- async def start(self):
70
+ async def start(self, mac_address: str, serial_number: str = "", license_key: str = ""):
61
71
  """启动客户端连接"""
72
+ self.mac_address = mac_address
62
73
  self.xiaozhi = XiaoZhiWebsocket(handle_message, url=self.url, ota_url=self.ota_url)
63
- await self.xiaozhi.init_connection(self.mac_address, aec=False)
74
+ await self.xiaozhi.init_connection(
75
+ self.mac_address, aec=False, serial_number=serial_number, license_key=license_key
76
+ )
64
77
  asyncio.create_task(play_assistant_audio(self.xiaozhi.output_audio_queue))
65
78
 
66
79
  def audio_callback(self, indata, frames, time, status):
@@ -71,6 +84,10 @@ class XiaoZhiClient:
71
84
  async def process_audio_input(self):
72
85
  """处理音频输入"""
73
86
  while True:
87
+
88
+ if is_end:
89
+ return
90
+
74
91
  if not input_audio_buffer:
75
92
  await asyncio.sleep(0.02)
76
93
  continue
@@ -83,19 +100,24 @@ class XiaoZhiClient:
83
100
  async def main():
84
101
  """主函数"""
85
102
  parser = argparse.ArgumentParser(description="小智SDK客户端")
86
- parser.add_argument("device", help="小智设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)")
87
- parser.add_argument("--url", help="小智服务websocket地址")
88
- parser.add_argument("--ota_url", help="小智OTA地址")
103
+ parser.add_argument("device", help="设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)")
104
+ parser.add_argument("--url", help="服务端websocket地址")
105
+ parser.add_argument("--ota_url", help="OTA地址")
106
+
107
+ parser.add_argument("--serial_number", default="", help="设备的序列号")
108
+ parser.add_argument("--license_key", default="", help="设备的授权密钥")
89
109
 
90
110
  args = parser.parse_args()
91
111
  logger.info("Recording... Press Ctrl+C to stop.")
92
-
93
- client = XiaoZhiClient(args.device, args.url, args.ota_url)
94
- await client.start()
112
+ client = XiaoZhiClient(args.url, args.ota_url)
113
+ await client.start(args.device, args.serial_number, args.license_key)
95
114
 
96
115
  with sd.InputStream(callback=client.audio_callback, channels=1, samplerate=16000, blocksize=960):
97
116
  await client.process_audio_input()
98
117
 
99
118
 
100
119
  if __name__ == "__main__":
101
- asyncio.run(main())
120
+ try:
121
+ asyncio.run(main())
122
+ except KeyboardInterrupt:
123
+ logger.info("Stopping...")
@@ -16,7 +16,7 @@ mcp_tool_conf: Dict[str, Dict[str, Any]] = {
16
16
  "inputSchema": {"type": "object", "properties": {}},
17
17
  },
18
18
  "set_volume": {
19
- "description": "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.",
19
+ "description": "Set the volume of the audio speaker. If the current volume is unknown, you must call `get_device_status` tool first and then call this tool.",
20
20
  "inputSchema": {
21
21
  "type": "object",
22
22
  "properties": {"volume": {"type": "integer", "minimum": 0, "maximum": 100}},
@@ -5,13 +5,13 @@ from typing import Any, Dict, Optional
5
5
 
6
6
  import aiohttp
7
7
 
8
+ from xiaozhi_sdk import __version__
8
9
  from xiaozhi_sdk.config import OTA_URL
9
10
 
10
11
  # 常量定义
11
- DEFAULT_APPLICATION_VERSION = "1.0.0"
12
12
  BOARD_TYPE = "xiaozhi-sdk-box"
13
- USER_AGENT = "xiaozhi-sdk/{}".format(DEFAULT_APPLICATION_VERSION)
14
- BOARD_NAME = "xiaozhi-sdk-{}".format(DEFAULT_APPLICATION_VERSION)
13
+ USER_AGENT = "xiaozhi-sdk/{}".format(__version__)
14
+ BOARD_NAME = "xiaozhi-sdk-{}".format(__version__)
15
15
 
16
16
 
17
17
  class OtaDevice:
@@ -29,6 +29,8 @@ class OtaDevice:
29
29
 
30
30
  def __init__(self, mac_addr: str, client_id: str, ota_url: Optional[str] = None, serial_number: str = "") -> None:
31
31
  self.ota_url = ota_url or OTA_URL
32
+ self.ota_url = self.ota_url.rstrip("/")
33
+
32
34
  self.mac_addr = mac_addr
33
35
  self.client_id = client_id
34
36
  self.serial_number = serial_number
@@ -46,7 +48,7 @@ class OtaDevice:
46
48
  headers["serial-number"] = self.serial_number
47
49
 
48
50
  payload = {
49
- "application": {"version": DEFAULT_APPLICATION_VERSION},
51
+ "application": {"version": __version__},
50
52
  "board": {
51
53
  "type": BOARD_TYPE,
52
54
  "name": BOARD_NAME,
@@ -60,16 +62,13 @@ class OtaDevice:
60
62
 
61
63
  async def check_activate(self, challenge: str, license_key: str = "") -> bool:
62
64
  url = f"{self.ota_url}/activate"
65
+
63
66
  headers = self._get_base_headers()
64
67
 
65
68
  hmac_instance = hmac.new(license_key.encode(), challenge.encode(), hashlib.sha256)
66
69
  hmac_result = hmac_instance.hexdigest()
67
70
 
68
- payload = {
69
- "serial_number": self.serial_number,
70
- "challenge": challenge,
71
- "hmac": hmac_result
72
- }
71
+ payload = {"serial_number": self.serial_number, "challenge": challenge, "hmac": hmac_result}
73
72
 
74
73
  async with aiohttp.ClientSession() as session:
75
74
  async with session.post(url, headers=headers, data=json.dumps(payload)) as response:
@@ -51,7 +51,7 @@ class McpTool(object):
51
51
  try:
52
52
  tool_res, is_error = tool_func(mcp_json["params"]["arguments"])
53
53
  except Exception as e:
54
- logger.error("tool_func error: %s", e)
54
+ logger.error("[MCP] tool_func error: %s", e)
55
55
  return
56
56
 
57
57
  if tool_name == "take_photo":
@@ -76,7 +76,7 @@ class McpTool(object):
76
76
  pass
77
77
 
78
78
  elif method == "notifications/cancelled":
79
- logger.error("MCP 工具加载失败")
79
+ logger.error("[MCP] 工具加载失败")
80
80
 
81
81
  elif method == "tools/list":
82
82
  mcp_tools_payload["id"] = payload["id"]
@@ -87,14 +87,16 @@ class McpTool(object):
87
87
  mcp_tool_conf[name]["name"] = name
88
88
  mcp_tools_payload["result"]["tools"].append(mcp_tool_conf[name])
89
89
  await self.websocket.send(self.get_mcp_json(mcp_tools_payload))
90
- if tool_list:
91
- logger.info("MCP 加载成功,当前可用工具列表为:%s", tool_list)
90
+ logger.info("[MCP] 加载成功,当前可用工具列表为:%s", tool_list)
92
91
 
93
92
  elif method == "tools/call":
94
93
  tool_name = payload["params"]["name"]
95
94
  if not self.tool_func.get(tool_name):
96
- raise Exception("Tool not found")
95
+ logger.warning("[MCP] Tool not found: %s", tool_name)
96
+ return
97
+
97
98
  mcp_res = await self.mcp_tool_call(payload)
98
99
  await self.websocket.send(mcp_res)
100
+ logger.info("[MCP] Tool %s called", tool_name)
99
101
  else:
100
- logger.warning("unknown method %s: %s", method, payload)
102
+ logger.warning("[MCP] unknown method %s: %s", method, payload)
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xiaozhi-sdk
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: 一个用于连接和控制小智智能设备的Python SDK,支持实时音频通信、MCP工具集成和设备管理功能。
5
- Home-page: https://github.com/dairoot/xiaozhi-sdk
6
- Author: dairoot
7
- Author-email: 623815825@qq.com
5
+ Author-email: dairoot <623815825@qq.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/dairoot/xiaozhi-sdk
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.8.1
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: numpy
@@ -19,16 +19,7 @@ Requires-Dist: opuslib
19
19
  Requires-Dist: requests
20
20
  Requires-Dist: sounddevice
21
21
  Requires-Dist: python-socks
22
- Dynamic: author
23
- Dynamic: author-email
24
- Dynamic: classifier
25
- Dynamic: description
26
- Dynamic: description-content-type
27
- Dynamic: home-page
28
22
  Dynamic: license-file
29
- Dynamic: requires-dist
30
- Dynamic: requires-python
31
- Dynamic: summary
32
23
 
33
24
  # 小智SDK (XiaoZhi SDK)
34
25
 
@@ -71,9 +62,12 @@ positional arguments:
71
62
  device 你的小智设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)
72
63
 
73
64
  options:
74
- -h, --help 显示帮助信息并退出
75
- --url URL 小智服务 websocket 地址
76
- --ota_url OTA_URL 小智 OTA 地址
65
+ -h, --help show this help message and exit
66
+ --url URL 服务端websocket地址
67
+ --ota_url OTA_URL OTA地址
68
+ --serial_number SERIAL_NUMBER 设备的序列号
69
+ --license_key LICENSE_KEY 设备的授权密钥
70
+
77
71
  ```
78
72
 
79
73
  #### 连接设备(需要提供 MAC 地址)
@@ -1,8 +1,7 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
- setup.cfg
5
- setup.py
4
+ pyproject.toml
6
5
  file/audio/greet.wav
7
6
  file/audio/say_hello.wav
8
7
  file/audio/take_photo.wav
@@ -1,17 +0,0 @@
1
- [flake8]
2
- ignore = E203, E266, E501, W503, F403, F401, E731, E711, C901, F841, D103, B008, D400, E402, E712
3
- max-line-length = 120
4
- max-complexity = 20
5
- exclude =
6
- tests/*
7
-
8
- [coverage:run]
9
- omit =
10
- xiaozhi_sdk/__main__.py
11
- tests/*
12
- setup.py
13
-
14
- [egg_info]
15
- tag_build =
16
- tag_date = 0
17
-
@@ -1,49 +0,0 @@
1
- import os
2
-
3
- from setuptools import find_packages, setup
4
-
5
-
6
- # 收集指定目录下的所有文件
7
- def get_data_files(*directories):
8
- data_files = []
9
- for directory in directories:
10
- if os.path.exists(directory):
11
- for root, _, files in os.walk(directory):
12
- for file in files:
13
- # 将相对于项目根目录的路径转换为相对于包的路径
14
- rel_path = os.path.relpath(os.path.join(root, file), ".")
15
- data_files.append(rel_path)
16
- return data_files
17
-
18
-
19
- setup(
20
- name="xiaozhi-sdk", # 包名
21
- version="0.0.2", # 版本号
22
- packages=find_packages(), # 自动发现包
23
- package_data={
24
- "xiaozhi_sdk": ["../" + f for f in get_data_files("file")], # 包含file目录下的所有文件
25
- },
26
- include_package_data=True, # 使用MANIFEST.in文件包含额外文件
27
- install_requires=[ # 依赖
28
- "numpy",
29
- "websockets",
30
- "aiohttp",
31
- "av",
32
- "opuslib",
33
- "requests",
34
- "sounddevice",
35
- "python-socks",
36
- ],
37
- author="dairoot",
38
- author_email="623815825@qq.com", # 作者邮箱
39
- description="一个用于连接和控制小智智能设备的Python SDK,支持实时音频通信、MCP工具集成和设备管理功能。", # 简短描述
40
- long_description=open("README.md").read(), # 详细描述(通常从 README 读取)
41
- long_description_content_type="text/markdown", # README 文件格式
42
- url="https://github.com/dairoot/xiaozhi-sdk", # 项目主页
43
- classifiers=[ # 分类元数据
44
- "Programming Language :: Python :: 3",
45
- "License :: OSI Approved :: MIT License",
46
- "Operating System :: OS Independent",
47
- ],
48
- python_requires=">=3.8", # 支持的 Python 版本
49
- )
File without changes
File without changes