xiaozhi-sdk 0.0.8__tar.gz → 0.0.10__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.
- {xiaozhi_sdk-0.0.8/xiaozhi_sdk.egg-info → xiaozhi_sdk-0.0.10}/PKG-INFO +1 -1
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/tests/test_xiaozhi.py +13 -16
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/__init__.py +1 -1
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/cli.py +56 -13
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/iot.py +4 -1
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/mcp.py +4 -3
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/utils/mcp_data.py +5 -2
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/utils/mcp_tool.py +1 -1
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10/xiaozhi_sdk.egg-info}/PKG-INFO +1 -1
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/LICENSE +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/MANIFEST.in +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/README.md +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/audio/greet.wav +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/audio/play_music.wav +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/audio/say_hello.wav +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/audio/take_photo.wav +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/image/leijun.jpg +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/opus/linux-arm64-libopus.so +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/opus/linux-x64-libopus.so +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/opus/macos-arm64-libopus.dylib +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/opus/macos-x64-libopus.dylib +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/file/opus/windows-opus.dll +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/pyproject.toml +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/setup.cfg +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/tests/test_iot.py +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/tests/test_pic.py +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/__main__.py +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/config.py +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/core.py +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/opus.py +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk/utils/__init__.py +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk.egg-info/SOURCES.txt +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk.egg-info/dependency_links.txt +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk.egg-info/requires.txt +0 -0
- {xiaozhi_sdk-0.0.8 → xiaozhi_sdk-0.0.10}/xiaozhi_sdk.egg-info/top_level.txt +0 -0
|
@@ -69,9 +69,6 @@ async def message_handler_callback(message):
|
|
|
69
69
|
|
|
70
70
|
MAC_ADDR = "00:22:44:66:88:00"
|
|
71
71
|
|
|
72
|
-
ota_url = "http://localhost:3080/api/ota"
|
|
73
|
-
URL = "ws://120.79.156.134:8380"
|
|
74
|
-
|
|
75
72
|
ota_url = None
|
|
76
73
|
URL = None
|
|
77
74
|
|
|
@@ -86,21 +83,21 @@ async def test_main():
|
|
|
86
83
|
await xiaozhi.init_connection(MAC_ADDR)
|
|
87
84
|
|
|
88
85
|
# # say hellow
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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)
|
|
86
|
+
for pcm in read_audio_file("./file/audio/say_hello.wav"):
|
|
87
|
+
await xiaozhi.send_audio(pcm)
|
|
88
|
+
await xiaozhi.send_silence_audio()
|
|
89
|
+
await assistant_audio_play(xiaozhi.output_audio_queue)
|
|
99
90
|
|
|
100
|
-
#
|
|
101
|
-
for pcm in read_audio_file("./file/audio/
|
|
91
|
+
# say take photo
|
|
92
|
+
for pcm in read_audio_file("./file/audio/take_photo.wav"):
|
|
102
93
|
await xiaozhi.send_audio(pcm)
|
|
103
94
|
await xiaozhi.send_silence_audio()
|
|
104
|
-
await assistant_audio_play(xiaozhi.output_audio_queue,
|
|
95
|
+
await assistant_audio_play(xiaozhi.output_audio_queue, 5)
|
|
96
|
+
|
|
97
|
+
# play music
|
|
98
|
+
# for pcm in read_audio_file("./file/audio/play_music.wav"):
|
|
99
|
+
# await xiaozhi.send_audio(pcm)
|
|
100
|
+
# await xiaozhi.send_silence_audio()
|
|
101
|
+
# await assistant_audio_play(xiaozhi.output_audio_queue, 500)
|
|
105
102
|
|
|
106
103
|
await xiaozhi.close()
|
|
@@ -12,6 +12,34 @@ import sounddevice as sd
|
|
|
12
12
|
from xiaozhi_sdk import XiaoZhiWebsocket
|
|
13
13
|
from xiaozhi_sdk.config import INPUT_SERVER_AUDIO_SAMPLE_RATE
|
|
14
14
|
|
|
15
|
+
# 定义自定义日志级别
|
|
16
|
+
INFO1 = 21
|
|
17
|
+
INFO2 = 22
|
|
18
|
+
INFO3 = 23
|
|
19
|
+
|
|
20
|
+
# 添加自定义日志级别到logging模块
|
|
21
|
+
logging.addLevelName(INFO1, "INFO1")
|
|
22
|
+
logging.addLevelName(INFO2, "INFO2")
|
|
23
|
+
logging.addLevelName(INFO3, "INFO3")
|
|
24
|
+
|
|
25
|
+
# 为logger添加自定义方法
|
|
26
|
+
def info1(self, message, *args, **kwargs):
|
|
27
|
+
if self.isEnabledFor(INFO1):
|
|
28
|
+
self._log(INFO1, message, args, **kwargs)
|
|
29
|
+
|
|
30
|
+
def info2(self, message, *args, **kwargs):
|
|
31
|
+
if self.isEnabledFor(INFO2):
|
|
32
|
+
self._log(INFO2, message, args, **kwargs)
|
|
33
|
+
|
|
34
|
+
def info3(self, message, *args, **kwargs):
|
|
35
|
+
if self.isEnabledFor(INFO3):
|
|
36
|
+
self._log(INFO3, message, args, **kwargs)
|
|
37
|
+
|
|
38
|
+
# 将自定义方法添加到Logger类
|
|
39
|
+
logging.Logger.info1 = info1
|
|
40
|
+
logging.Logger.info2 = info2
|
|
41
|
+
logging.Logger.info3 = info3
|
|
42
|
+
|
|
15
43
|
# 配置彩色logging
|
|
16
44
|
handler = colorlog.StreamHandler()
|
|
17
45
|
handler.setFormatter(
|
|
@@ -19,8 +47,11 @@ handler.setFormatter(
|
|
|
19
47
|
"%(log_color)s%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
20
48
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
21
49
|
log_colors={
|
|
22
|
-
"DEBUG": "
|
|
23
|
-
"INFO": "
|
|
50
|
+
"DEBUG": "white",
|
|
51
|
+
"INFO": "green",
|
|
52
|
+
"INFO1": "green",
|
|
53
|
+
"INFO2": "cyan",
|
|
54
|
+
"INFO3": "blue",
|
|
24
55
|
"WARNING": "yellow",
|
|
25
56
|
"ERROR": "red",
|
|
26
57
|
"CRITICAL": "red,bg_white",
|
|
@@ -41,17 +72,27 @@ is_end = False
|
|
|
41
72
|
async def handle_message(message):
|
|
42
73
|
"""处理接收到的消息"""
|
|
43
74
|
global is_end
|
|
44
|
-
|
|
75
|
+
if message["type"] == "stt":
|
|
76
|
+
logger.info1("message received: %s", message)
|
|
77
|
+
elif message["type"] == "tts":
|
|
78
|
+
logger.info2("message received: %s", message)
|
|
79
|
+
elif message["type"] == "llm":
|
|
80
|
+
logger.info3("message received: %s", message)
|
|
81
|
+
else:
|
|
82
|
+
logger.info("message received: %s", message)
|
|
83
|
+
|
|
45
84
|
if message["type"] == "websocket" and message["state"] == "close":
|
|
46
85
|
is_end = True
|
|
47
86
|
|
|
48
87
|
|
|
49
|
-
async def play_assistant_audio(audio_queue: deque[bytes]):
|
|
88
|
+
async def play_assistant_audio(audio_queue: deque[bytes], enable_audio):
|
|
50
89
|
"""播放音频流"""
|
|
51
90
|
global is_playing_audio
|
|
52
91
|
|
|
53
|
-
stream =
|
|
54
|
-
|
|
92
|
+
stream = None
|
|
93
|
+
if enable_audio:
|
|
94
|
+
stream = sd.OutputStream(samplerate=INPUT_SERVER_AUDIO_SAMPLE_RATE, channels=1, dtype=np.int16)
|
|
95
|
+
stream.start()
|
|
55
96
|
last_audio_time = None
|
|
56
97
|
|
|
57
98
|
while True:
|
|
@@ -66,7 +107,8 @@ async def play_assistant_audio(audio_queue: deque[bytes]):
|
|
|
66
107
|
|
|
67
108
|
is_playing_audio = True
|
|
68
109
|
pcm_data = audio_queue.popleft()
|
|
69
|
-
stream
|
|
110
|
+
if stream:
|
|
111
|
+
stream.write(pcm_data)
|
|
70
112
|
last_audio_time = time.time()
|
|
71
113
|
|
|
72
114
|
|
|
@@ -83,7 +125,7 @@ class XiaoZhiClient:
|
|
|
83
125
|
self.ota_url = ota_url
|
|
84
126
|
self.mac_address = ""
|
|
85
127
|
|
|
86
|
-
async def start(self, mac_address: str, serial_number: str
|
|
128
|
+
async def start(self, mac_address: str, serial_number: str, license_key: str, enable_audio):
|
|
87
129
|
"""启动客户端连接"""
|
|
88
130
|
self.mac_address = mac_address
|
|
89
131
|
self.xiaozhi = XiaoZhiWebsocket(handle_message, url=self.url, ota_url=self.ota_url, send_wake=True)
|
|
@@ -92,7 +134,7 @@ class XiaoZhiClient:
|
|
|
92
134
|
self.mac_address, aec=False, serial_number=serial_number, license_key=license_key
|
|
93
135
|
)
|
|
94
136
|
|
|
95
|
-
asyncio.create_task(play_assistant_audio(self.xiaozhi.output_audio_queue))
|
|
137
|
+
asyncio.create_task(play_assistant_audio(self.xiaozhi.output_audio_queue, enable_audio))
|
|
96
138
|
|
|
97
139
|
def audio_callback(self, indata, frames, time, status):
|
|
98
140
|
"""音频输入回调函数"""
|
|
@@ -115,11 +157,11 @@ class XiaoZhiClient:
|
|
|
115
157
|
await self.xiaozhi.send_audio(pcm_data)
|
|
116
158
|
|
|
117
159
|
|
|
118
|
-
async def run_client(mac_address: str, url: str, ota_url: str, serial_number: str, license_key: str):
|
|
160
|
+
async def run_client(mac_address: str, url: str, ota_url: str, serial_number: str, license_key: str, enable_audio: bool):
|
|
119
161
|
"""运行客户端的异步函数"""
|
|
120
162
|
logger.debug("Recording... Press Ctrl+C to stop.")
|
|
121
163
|
client = XiaoZhiClient(url, ota_url)
|
|
122
|
-
await client.start(mac_address, serial_number, license_key)
|
|
164
|
+
await client.start(mac_address, serial_number, license_key, enable_audio)
|
|
123
165
|
|
|
124
166
|
with sd.InputStream(callback=client.audio_callback, channels=1, samplerate=16000, blocksize=960):
|
|
125
167
|
await client.process_audio_input()
|
|
@@ -131,9 +173,10 @@ async def run_client(mac_address: str, url: str, ota_url: str, serial_number: st
|
|
|
131
173
|
@click.option("--ota_url", help="OTA地址")
|
|
132
174
|
@click.option("--serial_number", default="", help="设备的序列号")
|
|
133
175
|
@click.option("--license_key", default="", help="设备的授权密钥")
|
|
134
|
-
|
|
176
|
+
@click.option("--enable_audio", default=True, help="是否开启音频播放")
|
|
177
|
+
def main(mac_address: str, url: str, ota_url: str, serial_number: str, license_key: str, enable_audio: bool):
|
|
135
178
|
"""小智SDK客户端
|
|
136
179
|
|
|
137
180
|
MAC_ADDRESS: 设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)
|
|
138
181
|
"""
|
|
139
|
-
asyncio.run(run_client(mac_address, url, ota_url, serial_number, license_key))
|
|
182
|
+
asyncio.run(run_client(mac_address, url, ota_url, serial_number, license_key, enable_audio))
|
|
@@ -12,7 +12,7 @@ from xiaozhi_sdk.config import OTA_URL
|
|
|
12
12
|
# 常量定义
|
|
13
13
|
BOARD_TYPE = "xiaozhi-sdk-box"
|
|
14
14
|
USER_AGENT = "xiaozhi-sdk/{}".format(__version__)
|
|
15
|
-
BOARD_NAME = "xiaozhi-sdk
|
|
15
|
+
BOARD_NAME = "xiaozhi-sdk"
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger("xiaozhi_sdk")
|
|
18
18
|
|
|
@@ -60,6 +60,9 @@ class OtaDevice:
|
|
|
60
60
|
|
|
61
61
|
async with aiohttp.ClientSession() as session:
|
|
62
62
|
async with session.post(self.ota_url + "/", headers=headers, data=json.dumps(payload)) as response:
|
|
63
|
+
if response.status != 200:
|
|
64
|
+
err_text = await response.text()
|
|
65
|
+
raise Exception(err_text)
|
|
63
66
|
response.raise_for_status()
|
|
64
67
|
return await response.json()
|
|
65
68
|
|
|
@@ -43,7 +43,7 @@ class McpTool(object):
|
|
|
43
43
|
try:
|
|
44
44
|
response = requests.post(self.explain_url, files=files, data=payload, headers=headers, timeout=5)
|
|
45
45
|
res_json = response.json()
|
|
46
|
-
except Exception
|
|
46
|
+
except Exception:
|
|
47
47
|
return "网络异常", True
|
|
48
48
|
if res_json.get("error"):
|
|
49
49
|
return res_json, True
|
|
@@ -72,10 +72,11 @@ class McpTool(object):
|
|
|
72
72
|
else:
|
|
73
73
|
tool_res, is_error = {"message": "正在为你播放: {}".format(arguments["music_name"])}, False
|
|
74
74
|
data = {
|
|
75
|
-
"type": "music",
|
|
75
|
+
"type": "music",
|
|
76
|
+
"state": "start",
|
|
76
77
|
"url": music_info["url"],
|
|
77
78
|
"text": arguments["music_name"],
|
|
78
|
-
"source": "sdk.mcp_music_tool"
|
|
79
|
+
"source": "sdk.mcp_music_tool",
|
|
79
80
|
}
|
|
80
81
|
await self.message_handler_callback(data)
|
|
81
82
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict
|
|
1
|
+
from typing import Any, Dict
|
|
2
2
|
|
|
3
3
|
mcp_initialize_payload: Dict[str, Any] = {
|
|
4
4
|
"jsonrpc": "2.0",
|
|
@@ -23,7 +23,10 @@ mcp_tool_conf: Dict[str, Dict[str, Any]] = {
|
|
|
23
23
|
"description": "Play music using music IDs. IMPORTANT: You must call `search_custom_music` first to get the music IDs before using this tool. Use this tool after getting music IDs from search results. Args:\n `id_list`: The id list of the music to play (obtained from search_custom_music results). The list must contain more than 2 music IDs, and the system will randomly select one to play.\n `music_name`: The name of the music (obtained from search_custom_music results)",
|
|
24
24
|
"inputSchema": {
|
|
25
25
|
"type": "object",
|
|
26
|
-
"properties": {
|
|
26
|
+
"properties": {
|
|
27
|
+
"music_name": {"type": "string"},
|
|
28
|
+
"id_list": {"type": "array", "items": {"type": "string"}, "minItems": 3},
|
|
29
|
+
},
|
|
27
30
|
"required": ["music_name", "id_list"],
|
|
28
31
|
},
|
|
29
32
|
},
|
|
@@ -83,7 +83,7 @@ async def async_mcp_play_music(data) -> tuple[list, bool]:
|
|
|
83
83
|
|
|
84
84
|
chunk_size = 960 * 2
|
|
85
85
|
for i in range(0, len(pcm_data), chunk_size):
|
|
86
|
-
chunk = pcm_data[i: i + chunk_size]
|
|
86
|
+
chunk = pcm_data[i : i + chunk_size]
|
|
87
87
|
|
|
88
88
|
if chunk: # 确保不添加空块
|
|
89
89
|
chunk = np.frombuffer(chunk, dtype=np.int16)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|