update:测试页面增加OTA地址 (#842)

This commit is contained in:
hrz
2025-04-16 16:44:44 +08:00
committed by GitHub
parent d1b7cb6a36
commit 7a0cf5ef9a
5 changed files with 397 additions and 90 deletions
+21 -1
View File
@@ -116,6 +116,27 @@ class ConnectionHandler:
try: try:
# 获取并验证headers # 获取并验证headers
self.headers = dict(ws.request.headers) self.headers = dict(ws.request.headers)
if self.headers.get("device-id", None) is None:
# 尝试从 URL 的查询参数中获取 device-id
from urllib.parse import parse_qs, urlparse
# 从 WebSocket 请求中获取路径
request_path = ws.request.path
if not request_path:
self.logger.bind(tag=TAG).error("无法获取请求路径")
return
parsed_url = urlparse(request_path)
query_params = parse_qs(parsed_url.query)
if "device-id" in query_params:
self.headers["device-id"] = query_params["device-id"][0]
self.headers["client-id"] = query_params["client-id"][0]
else:
self.logger.bind(tag=TAG).error(
"无法从请求头和URL查询参数中获取device-id"
)
return
# 获取客户端ip地址 # 获取客户端ip地址
self.client_ip = ws.remote_address[0] self.client_ip = ws.remote_address[0]
self.logger.bind(tag=TAG).info( self.logger.bind(tag=TAG).info(
@@ -184,7 +205,6 @@ class ConnectionHandler:
self._initialize_models() self._initialize_models()
"""加载提示词""" """加载提示词"""
self.prompt = self.config["prompt"]
self.dialogue.put(Message(role="system", content=self.prompt)) self.dialogue.put(Message(role="system", content=self.prompt))
"""加载记忆""" """加载记忆"""
@@ -81,6 +81,8 @@ async def wakeupWordsResponse(conn):
"""唤醒词响应""" """唤醒词响应"""
wakeup_word = random.choice(WAKEUP_CONFIG["words"]) wakeup_word = random.choice(WAKEUP_CONFIG["words"])
result = conn.llm.response_no_stream(conn.config["prompt"], wakeup_word) result = conn.llm.response_no_stream(conn.config["prompt"], wakeup_word)
if result is None or result == "":
return
tts_file = await asyncio.to_thread(conn.tts.to_tts, result) tts_file = await asyncio.to_thread(conn.tts.to_tts, result)
if tts_file is not None and os.path.exists(tts_file): if tts_file is not None and os.path.exists(tts_file):
@@ -115,7 +115,7 @@ async def check_bind_device(conn):
digit = conn.bind_code[i] digit = conn.bind_code[i]
num_path = f"config/assets/bind_code/{digit}.wav" num_path = f"config/assets/bind_code/{digit}.wav"
num_packets, _ = conn.tts.audio_to_opus_data(num_path) num_packets, _ = conn.tts.audio_to_opus_data(num_path)
conn.audio_play_queue.put((num_packets, text, i + 1)) conn.audio_play_queue.put((num_packets, None, i + 1))
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"播放数字音频失败: {e}") logger.bind(tag=TAG).error(f"播放数字音频失败: {e}")
continue continue
@@ -3,7 +3,6 @@ import json
import asyncio import asyncio
import time import time
from core.utils.util import ( from core.utils.util import (
remove_punctuation_and_length,
get_string_no_punctuation_or_emoji, get_string_no_punctuation_or_emoji,
) )
+317 -31
View File
@@ -42,6 +42,50 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 10px 0;
}
.section h2 .toggle-button {
margin-left: auto;
padding: 4px 12px;
font-size: 12px;
border-radius: 4px;
cursor: pointer;
height: 28px;
line-height: 20px;
}
.device-info {
display: flex;
align-items: center;
gap: 20px;
margin-left: 20px;
padding: 0 15px;
background-color: #f9f9f9;
border-radius: 4px;
height: 28px;
line-height: 28px;
}
.device-info span {
color: #666;
font-size: 13px;
}
.device-info strong {
color: #333;
font-weight: 500;
}
.config-panel {
display: none;
transition: all 0.3s ease;
margin-top: 5px;
padding: 5px 0;
}
.config-panel.expanded {
display: block;
} }
.control-panel { .control-panel {
@@ -70,7 +114,8 @@
cursor: not-allowed; cursor: not-allowed;
} }
#serverUrl { #serverUrl,
#otaUrl {
flex-grow: 1; flex-grow: 1;
padding: 8px; padding: 8px;
border: 1px solid #ddd; border: 1px solid #ddd;
@@ -316,6 +361,76 @@
gap: 20px; gap: 20px;
margin-top: 10px; margin-top: 10px;
} }
.config-item {
display: flex;
align-items: center;
margin-bottom: 8px;
width: 100%;
}
.config-item label {
width: 100px;
text-align: right;
margin-right: 10px;
color: #666;
}
.config-item input {
flex-grow: 1;
padding: 6px;
border: 1px solid #ddd;
border-radius: 5px;
}
.control-panel {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 10px;
}
.connection-controls {
display: flex;
gap: 10px;
align-items: center;
width: 100%;
}
.connection-controls input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 5px;
min-width: 200px;
}
.connection-controls button {
white-space: nowrap;
padding: 8px 15px;
}
.connection-status {
display: flex;
align-items: center;
gap: 20px;
margin-left: 20px;
padding: 0 15px;
background-color: #f9f9f9;
border-radius: 4px;
height: 28px;
line-height: 28px;
}
.connection-status span {
color: #666;
font-size: 13px;
}
.connection-status .status {
color: #333;
font-weight: 500;
}
</style> </style>
</head> </head>
@@ -326,17 +441,56 @@
<div id="scriptStatus" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);"> <div id="scriptStatus" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);">
正在加载Opus库...</div> 正在加载Opus库...</div>
<!-- 添加配置面板 -->
<div class="section"> <div class="section">
<h2>WebSocket连接 <span id="connectionStatus" class="status">未连接</span></h2> <h2>
<div class="control-panel"> 设备配置
<input type="text" id="serverUrl" value="ws://127.0.0.1:8000/xiaozhi/v1/" placeholder="WebSocket服务器地址"> <span class="device-info">
<span>MAC: <strong id="displayMac">00:11:22:33:44:55</strong></span>
<span>客户端: <strong id="displayClient">web_test_client</strong></span>
</span>
<button class="toggle-button" id="toggleConfig">编辑</button>
</h2>
<div class="config-panel" id="configPanel">
<div class="control-panel">
<div class="config-item">
<label for="deviceMac">设备MAC:</label>
<input type="text" id="deviceMac" value="00:11:22:33:44:55" placeholder="设备MAC地址">
</div>
<div class="config-item">
<label for="deviceName">设备名称:</label>
<input type="text" id="deviceName" value="Web测试设备" placeholder="设备名称">
</div>
<div class="config-item">
<label for="clientId">客户端ID:</label>
<input type="text" id="clientId" value="web_test_client" placeholder="客户端ID">
</div>
<div class="config-item">
<label for="token">认证Token:</label>
<input type="text" id="token" value="your-token1" placeholder="认证Token">
</div>
</div>
</div>
</div>
<div class="section">
<h2>
连接信息
<span class="connection-status">
<span>OTA: <span id="otaStatus" class="status">ota未连接</span></span>
<span>WS: <span id="connectionStatus" class="status">ws未连接</span></span>
</span>
</h2>
<div class="connection-controls">
<input type="text" id="otaUrl" value="http://127.0.0.1:8002/xiaozhi/ota/" placeholder="OTA服务器地址" />
<input type="text" id="serverUrl" value="ws://127.0.0.1:8000/xiaozhi/v1/"
placeholder="WebSocket服务器地址" />
<button id="connectButton">连接</button> <button id="connectButton">连接</button>
<button id="authTestButton">测试认证</button> <button id="authTestButton">测试认证</button>
</div> </div>
</div> </div>
<div class="section"> <div class="section">
<h2>消息发送</h2>
<div class="tabs"> <div class="tabs">
<button class="tab active" data-tab="text">文本消息</button> <button class="tab active" data-tab="text">文本消息</button>
<button class="tab" data-tab="voice">语音消息</button> <button class="tab" data-tab="voice">语音消息</button>
@@ -633,7 +787,7 @@
lastPlayTime: 0, // 上次播放的时间戳 lastPlayTime: 0, // 上次播放的时间戳
// 将Opus数据解码为PCM // 将Opus数据解码为PCM
decodeOpusFrames: async function(opusFrames) { decodeOpusFrames: async function (opusFrames) {
if (!opusDecoder) { if (!opusDecoder) {
log('Opus解码器未初始化,无法解码', 'error'); log('Opus解码器未初始化,无法解码', 'error');
return; return;
@@ -671,7 +825,7 @@
}, },
// 开始播放音频 // 开始播放音频
startPlaying: function() { startPlaying: function () {
if (this.playing || this.queue.length === 0) return; if (this.playing || this.queue.length === 0) return;
this.playing = true; this.playing = true;
@@ -804,7 +958,7 @@
decoderPtr: null, // 初始为null decoderPtr: null, // 初始为null
// 初始化解码器 // 初始化解码器
init: function() { init: function () {
if (this.decoderPtr) return true; // 已经初始化 if (this.decoderPtr) return true; // 已经初始化
// 获取解码器大小 // 获取解码器大小
@@ -834,7 +988,7 @@
}, },
// 解码方法 // 解码方法
decode: function(opusData) { decode: function (opusData) {
if (!this.decoderPtr) { if (!this.decoderPtr) {
if (!this.init()) { if (!this.init()) {
throw new Error("解码器未初始化且无法初始化"); throw new Error("解码器未初始化且无法初始化");
@@ -885,7 +1039,7 @@
}, },
// 销毁方法 // 销毁方法
destroy: function() { destroy: function () {
if (this.decoderPtr) { if (this.decoderPtr) {
this.module._free(this.decoderPtr); this.module._free(this.decoderPtr);
this.decoderPtr = null; this.decoderPtr = null;
@@ -1132,25 +1286,100 @@
} }
// 连接WebSocket服务器 // 连接WebSocket服务器
function connectToServer() { async function connectToServer() {
const url = serverUrlInput.value.trim(); const url = serverUrlInput.value.trim();
if (url === '') return; if (url === '') return;
try { try {
// 获取并验证配置
const config = getConfig();
if (!validateConfig(config)) {
return;
}
// 检查URL格式 // 检查URL格式
if (!url.startsWith('ws://') && !url.startsWith('wss://')) { if (!url.startsWith('ws://') && !url.startsWith('wss://')) {
log('URL格式错误,必须以ws://或wss://开头', 'error'); log('URL格式错误,必须以ws://或wss://开头', 'error');
return; return;
} }
// 先检查OTA状态
log('正在检查OTA状态...', 'info');
const otaUrl = document.getElementById('otaUrl').value.trim();
try {
const otaResponse = await fetch(otaUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Device-Id': config.deviceId,
'Client-Id': config.clientId
},
body: JSON.stringify({
"version": 0,
"uuid": "",
"application": {
"name": "xiaozhi-web-test",
"version": "1.0.0",
"compile_time": "2025-04-16 10:00:00",
"idf_version": "4.4.3",
"elf_sha256": "1234567890abcdef1234567890abcdef1234567890abcdef"
},
"ota": {
"label": "xiaozhi-web-test",
},
"board": {
"type": "xiaozhi-web-test",
"ssid": "xiaozhi-web-test",
"rssi": 0,
"channel": 0,
"ip": "192.168.1.1",
"mac": config.deviceMac
},
"flash_size": 0,
"minimum_free_heap_size": 0,
"mac_address": config.deviceMac,
"chip_model_name": "",
"chip_info": {
"model": 0,
"cores": 0,
"revision": 0,
"features": 0
},
"partition_table": [
{
"label": "",
"type": 0,
"subtype": 0,
"address": 0,
"size": 0
}
]
})
});
if (!otaResponse.ok) {
throw new Error(`OTA检查失败: ${otaResponse.status} ${otaResponse.statusText}`);
}
const otaResult = await otaResponse.json();
log(`OTA检查结果: ${JSON.stringify(otaResult)}`, 'info');
log('OTA检查通过,开始连接WebSocket...', 'success');
document.getElementById('otaStatus').textContent = 'ota已连接';
document.getElementById('otaStatus').style.color = 'green';
} catch (error) {
log(`OTA检查错误: ${error.message}`, 'error');
document.getElementById('otaStatus').textContent = 'ota未连接';
document.getElementById('otaStatus').style.color = 'red';
}
// 使用自定义WebSocket实现以添加认证头信息 // 使用自定义WebSocket实现以添加认证头信息
// 注意:浏览器原生WebSocket不支持自定义头,需要通过服务器添加一个代理层
// 但我们可以通过URL参数模拟认证信息
let connUrl = new URL(url); let connUrl = new URL(url);
// 添加认证参数 // 添加认证参数
connUrl.searchParams.append('device_id', 'web_test_device'); connUrl.searchParams.append('device-id', config.deviceId);
connUrl.searchParams.append('device_mac', '00:11:22:33:44:55'); connUrl.searchParams.append('client-id', config.clientId);
log(`正在连接: ${connUrl.toString()}`, 'info'); log(`正在连接: ${connUrl.toString()}`, 'info');
websocket = new WebSocket(connUrl.toString()); websocket = new WebSocket(connUrl.toString());
@@ -1160,7 +1389,7 @@
websocket.onopen = async () => { websocket.onopen = async () => {
log(`已连接到服务器: ${url}`, 'success'); log(`已连接到服务器: ${url}`, 'success');
connectionStatus.textContent = '已连接'; connectionStatus.textContent = 'ws已连接';
connectionStatus.style.color = 'green'; connectionStatus.style.color = 'green';
// 连接成功后发送hello消息 // 连接成功后发送hello消息
@@ -1181,7 +1410,7 @@
websocket.onclose = () => { websocket.onclose = () => {
log('已断开连接', 'info'); log('已断开连接', 'info');
connectionStatus.textContent = '已断开'; connectionStatus.textContent = 'ws已断开';
connectionStatus.style.color = 'red'; connectionStatus.style.color = 'red';
connectButton.textContent = '连接'; connectButton.textContent = '连接';
@@ -1196,7 +1425,7 @@
websocket.onerror = (error) => { websocket.onerror = (error) => {
log(`WebSocket错误: ${error.message || '未知错误'}`, 'error'); log(`WebSocket错误: ${error.message || '未知错误'}`, 'error');
connectionStatus.textContent = '连接错误'; connectionStatus.textContent = 'ws未连接';
connectionStatus.style.color = 'red'; connectionStatus.style.color = 'red';
}; };
@@ -1262,11 +1491,11 @@
} }
}; };
connectionStatus.textContent = '正在连接...'; connectionStatus.textContent = 'ws未连接';
connectionStatus.style.color = 'orange'; connectionStatus.style.color = 'orange';
} catch (error) { } catch (error) {
log(`连接错误: ${error.message}`, 'error'); log(`连接错误: ${error.message}`, 'error');
connectionStatus.textContent = '连接失败'; connectionStatus.textContent = 'ws未连接';
} }
} }
@@ -1275,13 +1504,15 @@
if (!websocket || websocket.readyState !== WebSocket.OPEN) return; if (!websocket || websocket.readyState !== WebSocket.OPEN) return;
try { try {
const config = getConfig();
// 设置设备信息 // 设置设备信息
const helloMessage = { const helloMessage = {
type: 'hello', type: 'hello',
device_id: 'web_test_device', device_id: config.deviceId,
device_name: 'Web测试设备', device_name: config.deviceName,
device_mac: '00:11:22:33:44:55', device_mac: config.deviceMac,
token: 'your-token1' // 使用config.yaml中配置的token token: config.token
}; };
log('发送hello握手消息', 'info'); log('发送hello握手消息', 'info');
@@ -1356,6 +1587,34 @@
connectButton.addEventListener('click', connectToServer); connectButton.addEventListener('click', connectToServer);
document.getElementById('authTestButton').addEventListener('click', testAuthentication); document.getElementById('authTestButton').addEventListener('click', testAuthentication);
// 设备配置面板折叠/展开
const toggleButton = document.getElementById('toggleConfig');
const configPanel = document.getElementById('configPanel');
const deviceMacInput = document.getElementById('deviceMac');
const clientIdInput = document.getElementById('clientId');
const displayMac = document.getElementById('displayMac');
const displayClient = document.getElementById('displayClient');
// 更新显示的值
function updateDisplayValues() {
displayMac.textContent = deviceMacInput.value;
displayClient.textContent = clientIdInput.value;
}
// 监听输入变化
deviceMacInput.addEventListener('input', updateDisplayValues);
clientIdInput.addEventListener('input', updateDisplayValues);
// 初始更新显示值
updateDisplayValues();
// 切换面板显示
toggleButton.addEventListener('click', () => {
const isExpanded = configPanel.classList.contains('expanded');
configPanel.classList.toggle('expanded');
toggleButton.textContent = isExpanded ? '编辑' : '收起';
});
// 标签页切换 // 标签页切换
const tabs = document.querySelectorAll('.tab'); const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => { tabs.forEach(tab => {
@@ -1390,12 +1649,14 @@
async function testAuthentication() { async function testAuthentication() {
log('开始测试认证...', 'info'); log('开始测试认证...', 'info');
const config = getConfig();
// 显示服务器配置 // 显示服务器配置
log('-------- 服务器认证配置检查 --------', 'info'); log('-------- 服务器认证配置检查 --------', 'info');
log('请确认config.yaml中的auth配置:', 'info'); log('请确认config.yaml中的auth配置:', 'info');
log('1. server.auth.enabled 为 false 或服务器已正确配置认证', 'info'); log('1. server.auth.enabled 为 false 或服务器已正确配置认证', 'info');
log('2. 如果启用了认证,请确认使用了正确的token', 'info'); log('2. 如果启用了认证,请确认使用了正确的token', 'info');
log('3. 或者在allowed_devices中添加了测试设备MAC00:11:22:33:44:55', 'info'); log(`3. 或者在allowed_devices中添加了测试设备MAC${config.deviceMac}`, 'info');
const serverUrl = serverUrlInput.value.trim(); const serverUrl = serverUrlInput.value.trim();
if (!serverUrl) { if (!serverUrl) {
@@ -1436,9 +1697,9 @@
log('测试2: 尝试带token参数连接...', 'info'); log('测试2: 尝试带token参数连接...', 'info');
let url = new URL(serverUrl); let url = new URL(serverUrl);
url.searchParams.append('token', 'your-token1'); url.searchParams.append('token', config.token);
url.searchParams.append('device_id', 'web_test_device'); url.searchParams.append('device_id', config.deviceId);
url.searchParams.append('device_mac', '00:11:22:33:44:55'); url.searchParams.append('device_mac', config.deviceMac);
const ws2 = new WebSocket(url.toString()); const ws2 = new WebSocket(url.toString());
@@ -1448,9 +1709,9 @@
// 尝试发送hello消息 // 尝试发送hello消息
const helloMsg = { const helloMsg = {
type: 'hello', type: 'hello',
device_id: 'web_test_device', device_id: config.deviceId,
device_mac: '00:11:22:33:44:55', device_mac: config.deviceMac,
token: 'your-token1' token: config.token
}; };
ws2.send(JSON.stringify(helloMsg)); ws2.send(JSON.stringify(helloMsg));
@@ -2115,6 +2376,31 @@
} }
} }
// 获取配置值
function getConfig() {
const deviceMac = document.getElementById('deviceMac').value.trim();
return {
deviceId: deviceMac, // 使用MAC地址作为deviceId
deviceName: document.getElementById('deviceName').value.trim(),
deviceMac: deviceMac,
clientId: document.getElementById('clientId').value.trim(),
token: document.getElementById('token').value.trim()
};
}
// 验证配置
function validateConfig(config) {
if (!config.deviceMac) {
log('设备MAC地址不能为空', 'error');
return false;
}
if (!config.clientId) {
log('客户端ID不能为空', 'error');
return false;
}
return true;
}
initApp(); initApp();
</script> </script>
</body> </body>