xiaozhi-sdk 0.0.2__tar.gz → 0.0.3__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 (32) hide show
  1. {xiaozhi_sdk-0.0.2/xiaozhi_sdk.egg-info → xiaozhi_sdk-0.0.3}/PKG-INFO +7 -4
  2. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/README.md +6 -3
  3. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/setup.py +12 -1
  4. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/tests/test_iot.py +2 -0
  5. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/__init__.py +36 -17
  6. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/__main__.py +22 -11
  7. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/data.py +1 -1
  8. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/iot.py +5 -9
  9. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/mcp.py +8 -6
  10. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3/xiaozhi_sdk.egg-info}/PKG-INFO +7 -4
  11. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/LICENSE +0 -0
  12. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/MANIFEST.in +0 -0
  13. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/audio/greet.wav +0 -0
  14. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/audio/say_hello.wav +0 -0
  15. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/audio/take_photo.wav +0 -0
  16. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/image/leijun.jpg +0 -0
  17. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/opus/linux-arm64-libopus.so +0 -0
  18. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/opus/linux-x64-libopus.so +0 -0
  19. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/opus/macos-arm64-libopus.dylib +0 -0
  20. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/opus/macos-x64-libopus.dylib +0 -0
  21. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/file/opus/windows-x86_64-opus.dll +0 -0
  22. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/setup.cfg +0 -0
  23. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/tests/test_opus.py +0 -0
  24. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/tests/test_pic.py +0 -0
  25. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/tests/test_xiaozhi.py +0 -0
  26. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/config.py +0 -0
  27. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/opus.py +0 -0
  28. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk/utils.py +0 -0
  29. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk.egg-info/SOURCES.txt +0 -0
  30. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk.egg-info/dependency_links.txt +0 -0
  31. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk.egg-info/requires.txt +0 -0
  32. {xiaozhi_sdk-0.0.2 → xiaozhi_sdk-0.0.3}/xiaozhi_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xiaozhi-sdk
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: 一个用于连接和控制小智智能设备的Python SDK,支持实时音频通信、MCP工具集成和设备管理功能。
5
5
  Home-page: https://github.com/dairoot/xiaozhi-sdk
6
6
  Author: dairoot
@@ -71,9 +71,12 @@ positional arguments:
71
71
  device 你的小智设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)
72
72
 
73
73
  options:
74
- -h, --help 显示帮助信息并退出
75
- --url URL 小智服务 websocket 地址
76
- --ota_url OTA_URL 小智 OTA 地址
74
+ -h, --help show this help message and exit
75
+ --url URL 服务端websocket地址
76
+ --ota_url OTA_URL OTA地址
77
+ --serial_number SERIAL_NUMBER 设备的序列号
78
+ --license_key LICENSE_KEY 设备的授权密钥
79
+
77
80
  ```
78
81
 
79
82
  #### 连接设备(需要提供 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 地址)
@@ -1,8 +1,19 @@
1
1
  import os
2
+ import re
2
3
 
3
4
  from setuptools import find_packages, setup
4
5
 
5
6
 
7
+ # 读取版本号
8
+ def get_version():
9
+ with open(os.path.join("xiaozhi_sdk", "__init__.py"), "r", encoding="utf-8") as f:
10
+ content = f.read()
11
+ match = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', content, re.M)
12
+ if match:
13
+ return match.group(1)
14
+ raise RuntimeError("Unable to find version string.")
15
+
16
+
6
17
  # 收集指定目录下的所有文件
7
18
  def get_data_files(*directories):
8
19
  data_files = []
@@ -18,7 +29,7 @@ def get_data_files(*directories):
18
29
 
19
30
  setup(
20
31
  name="xiaozhi-sdk", # 包名
21
- version="0.0.2", # 版本号
32
+ version=get_version(), # 版本号
22
33
  packages=find_packages(), # 自动发现包
23
34
  package_data={
24
35
  "xiaozhi_sdk": ["../" + f for f in get_data_files("file")], # 包含file目录下的所有文件
@@ -16,4 +16,6 @@ async def test_main():
16
16
  mac_address = "00:22:44:66:88:00"
17
17
  ota = OtaDevice(mac_addr=mac_address, client_id=str(uuid.uuid4()), serial_number=serial_number)
18
18
  res = await ota.activate_device()
19
+ if not res.get("activation"):
20
+ return
19
21
  await ota.check_activate(res["activation"]["challenge"], license_key)
@@ -1,5 +1,8 @@
1
+ __version__ = "0.0.3"
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
  """消息处理器"""
@@ -144,7 +152,9 @@ class XiaoZhiWebsocket(McpTool):
144
152
  """设置MCP工具回调函数"""
145
153
  self.tool_func = tool_func
146
154
 
147
- async def init_connection(self, mac_addr: str, aec: bool = False) -> None:
155
+ async def init_connection(
156
+ self, mac_addr: str, aec: bool = False, serial_number: str = "", license_key: str = ""
157
+ ) -> None:
148
158
  """初始化连接"""
149
159
  # 校验MAC地址格式 XX:XX:XX:XX:XX:XX
150
160
  mac_pattern = r"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"
@@ -152,8 +162,14 @@ class XiaoZhiWebsocket(McpTool):
152
162
  raise ValueError(f"无效的MAC地址格式: {mac_addr}。正确格式应为 XX:XX:XX:XX:XX:XX")
153
163
 
154
164
  self.mac_addr = mac_addr.lower()
155
- self.ota = OtaDevice(self.mac_addr, self.client_id, self.ota_url)
165
+
166
+ self.ota = OtaDevice(self.mac_addr, self.client_id, self.ota_url, serial_number)
156
167
  ota_info = await self.ota.activate_device()
168
+ ws_url = ota_info["websocket"]["url"]
169
+ self.url = self.url or ws_url
170
+
171
+ if "tenclass.net" not in self.url and "xiaozhi.me" not in self.url:
172
+ logger.warning("[websocket] 检测到非官方服务器,请谨慎使用!当前链接地址: %s", self.url)
157
173
 
158
174
  headers = {
159
175
  "Authorization": "Bearer {}".format(ota_info["websocket"]["token"]),
@@ -161,13 +177,16 @@ class XiaoZhiWebsocket(McpTool):
161
177
  "Device-Id": self.mac_addr,
162
178
  "Client-Id": self.client_id,
163
179
  }
164
- self.url = self.url or ota_info["websocket"]["url"]
165
- self.websocket = await websockets.connect(uri=self.url, additional_headers=headers)
180
+ try:
181
+ self.websocket = await websockets.connect(uri=self.url, additional_headers=headers)
182
+ except websockets.exceptions.InvalidMessage as e:
183
+ logger.error("[websocket] 连接失败,请检查网络连接或设备状态。当前链接地址: %s, 错误信息:%s", self.url, e)
184
+ return
166
185
  self.message_handler_task = asyncio.create_task(self._message_handler())
167
186
 
168
187
  await self._send_hello(aec)
169
188
  await self._start_listen()
170
- asyncio.create_task(self._activate_iot_device(ota_info))
189
+ asyncio.create_task(self._activate_iot_device(license_key, ota_info))
171
190
  await asyncio.sleep(0.5)
172
191
 
173
192
  async def send_audio(self, pcm: bytes) -> None:
@@ -51,16 +51,22 @@ async def play_assistant_audio(audio_queue: deque[bytes]):
51
51
  class XiaoZhiClient:
52
52
  """小智客户端类"""
53
53
 
54
- def __init__(self, mac_address: str, url: Optional[str] = None, ota_url: Optional[str] = None):
55
- self.mac_address = mac_address
54
+ def __init__(
55
+ self,
56
+ url: Optional[str] = None,
57
+ ota_url: Optional[str] = None,
58
+ ):
56
59
  self.xiaozhi: Optional[XiaoZhiWebsocket] = None
57
60
  self.url = url
58
61
  self.ota_url = ota_url
59
62
 
60
- async def start(self):
63
+ async def start(self, mac_address: str, serial_number: str = "", license_key: str = ""):
61
64
  """启动客户端连接"""
65
+ self.mac_address = mac_address
62
66
  self.xiaozhi = XiaoZhiWebsocket(handle_message, url=self.url, ota_url=self.ota_url)
63
- await self.xiaozhi.init_connection(self.mac_address, aec=False)
67
+ await self.xiaozhi.init_connection(
68
+ self.mac_address, aec=False, serial_number=serial_number, license_key=license_key
69
+ )
64
70
  asyncio.create_task(play_assistant_audio(self.xiaozhi.output_audio_queue))
65
71
 
66
72
  def audio_callback(self, indata, frames, time, status):
@@ -83,19 +89,24 @@ class XiaoZhiClient:
83
89
  async def main():
84
90
  """主函数"""
85
91
  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地址")
92
+ parser.add_argument("device", help="设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)")
93
+ parser.add_argument("--url", help="服务端websocket地址")
94
+ parser.add_argument("--ota_url", help="OTA地址")
95
+
96
+ parser.add_argument("--serial_number", default="", help="设备的序列号")
97
+ parser.add_argument("--license_key", default="", help="设备的授权密钥")
89
98
 
90
99
  args = parser.parse_args()
91
100
  logger.info("Recording... Press Ctrl+C to stop.")
92
-
93
- client = XiaoZhiClient(args.device, args.url, args.ota_url)
94
- await client.start()
101
+ client = XiaoZhiClient(args.url, args.ota_url)
102
+ await client.start(args.device, args.serial_number, args.license_key)
95
103
 
96
104
  with sd.InputStream(callback=client.audio_callback, channels=1, samplerate=16000, blocksize=960):
97
105
  await client.process_audio_input()
98
106
 
99
107
 
100
108
  if __name__ == "__main__":
101
- asyncio.run(main())
109
+ try:
110
+ asyncio.run(main())
111
+ except KeyboardInterrupt:
112
+ 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:
@@ -46,7 +46,7 @@ class OtaDevice:
46
46
  headers["serial-number"] = self.serial_number
47
47
 
48
48
  payload = {
49
- "application": {"version": DEFAULT_APPLICATION_VERSION},
49
+ "application": {"version": __version__},
50
50
  "board": {
51
51
  "type": BOARD_TYPE,
52
52
  "name": BOARD_NAME,
@@ -65,11 +65,7 @@ class OtaDevice:
65
65
  hmac_instance = hmac.new(license_key.encode(), challenge.encode(), hashlib.sha256)
66
66
  hmac_result = hmac_instance.hexdigest()
67
67
 
68
- payload = {
69
- "serial_number": self.serial_number,
70
- "challenge": challenge,
71
- "hmac": hmac_result
72
- }
68
+ payload = {"serial_number": self.serial_number, "challenge": challenge, "hmac": hmac_result}
73
69
 
74
70
  async with aiohttp.ClientSession() as session:
75
71
  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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xiaozhi-sdk
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: 一个用于连接和控制小智智能设备的Python SDK,支持实时音频通信、MCP工具集成和设备管理功能。
5
5
  Home-page: https://github.com/dairoot/xiaozhi-sdk
6
6
  Author: dairoot
@@ -71,9 +71,12 @@ positional arguments:
71
71
  device 你的小智设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)
72
72
 
73
73
  options:
74
- -h, --help 显示帮助信息并退出
75
- --url URL 小智服务 websocket 地址
76
- --ota_url OTA_URL 小智 OTA 地址
74
+ -h, --help show this help message and exit
75
+ --url URL 服务端websocket地址
76
+ --ota_url OTA_URL OTA地址
77
+ --serial_number SERIAL_NUMBER 设备的序列号
78
+ --license_key LICENSE_KEY 设备的授权密钥
79
+
77
80
  ```
78
81
 
79
82
  #### 连接设备(需要提供 MAC 地址)
File without changes
File without changes
File without changes