From a83410cb49830161cc4cc99198b9f60bea4338aa Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Tue, 15 Apr 2025 18:30:12 +0800 Subject: [PATCH] =?UTF-8?q?update:websocket=E6=92=AD=E6=8A=A56=E4=BD=8D?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=20(#821)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../xiaozhi/common/exception/ErrorCode.java | 3 + .../service/impl/ConfigServiceImpl.java | 8 +- .../device/controller/OTAController.java | 5 +- .../modules/device/service/DeviceService.java | 10 +- .../service/impl/DeviceServiceImpl.java | 96 ++++++++++++------- .../main/resources/i18n/messages.properties | 3 + .../resources/i18n/messages_en_US.properties | 5 +- .../resources/i18n/messages_zh_CN.properties | 5 +- .../resources/i18n/messages_zh_TW.properties | 5 +- .../main/resources/i18n/validation.properties | 2 + .../i18n/validation_en_US.properties | 2 + .../i18n/validation_zh_CN.properties | 2 + .../i18n/validation_zh_TW.properties | 3 + main/xiaozhi-server/config/config_loader.py | 17 +++- main/xiaozhi-server/core/connection.py | 21 +++- .../core/handle/receiveAudioHandle.py | 49 +++++++++- .../core/providers/tts/siliconflow.py | 2 +- 18 files changed, 183 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index e8958e61..5479d020 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ server: ``` 智控台地址: https://2662r3426b.vicp.fun -OTA接口地址: htts://2662r3426b.vicp.fun/xiaozhi/ota/ +OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/ ``` --- diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java index d9217013..c59a6a14 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java @@ -52,4 +52,7 @@ public interface ErrorCode { int PARAM_BOOLEAN_INVALID = 10038; int PARAM_ARRAY_INVALID = 10039; int PARAM_JSON_INVALID = 10040; + + int OTA_DEVICE_NOT_FOUND = 10041; + int OTA_DEVICE_NEED_BIND = 10042; } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java index 7be38a89..44f19089 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java @@ -9,6 +9,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import lombok.AllArgsConstructor; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; @@ -83,7 +84,12 @@ public class ConfigServiceImpl implements ConfigService { // 根据MAC地址查找设备 DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); if (device == null) { - throw new RenException("设备未找到"); + // 如果设备,去redis里看看有没有需要连接的设备 + String cachedCode = deviceService.geCodeByDeviceId(macAddress); + if (StringUtils.isNotBlank(cachedCode)) { + throw new RenException(ErrorCode.OTA_DEVICE_NEED_BIND, cachedCode); + } + throw new RenException(ErrorCode.OTA_DEVICE_NOT_FOUND, "not found device"); } // 获取智能体信息 AgentEntity agent = agentService.getAgentById(device.getAgentId()); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java index 4e3a2a95..a12e7653 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java @@ -37,9 +37,7 @@ public class OTAController { @PostMapping public ResponseEntity checkOTAVersion( @RequestBody DeviceReportReqDTO deviceReportReqDTO, - @Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId, - @Parameter(name = "Client-Id", description = "客户端标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Client-Id") String clientId) { if (StringUtils.isAnyBlank(deviceId, clientId)) { return createResponse(DeviceReportRespDTO.createError("Device ID is required")); @@ -50,10 +48,9 @@ public class OTAController { if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) { return createResponse(DeviceReportRespDTO.createError("Invalid OTA request")); } - return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO)); + return createResponse(deviceService.checkDeviceActive(macAddress, clientId, deviceReportReqDTO)); } - @Operation(summary = "获取 OTA 提示信息") @GetMapping public ResponseEntity getOTAPrompt() { return createResponse(DeviceReportRespDTO.createError("请提交正确的ota参数")); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java index 983cb6ee..8bb203cc 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java @@ -19,7 +19,7 @@ public interface DeviceService { /** * 检查设备是否激活 */ - DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId, + DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId, DeviceReportReqDTO deviceReport); /** @@ -74,4 +74,12 @@ public interface DeviceService { * @return 设备信息 */ DeviceEntity getDeviceByMacAddress(String macAddress); + + /** + * 根据设备ID获取激活码 + * + * @param deviceId 设备ID + * @return 激活码 + */ + String geCodeByDeviceId(String deviceId); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index 840f5ad6..71531c76 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -120,7 +120,7 @@ public class DeviceServiceImpl extends BaseServiceImpl } @Override - public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId, + public DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId, DeviceReportReqDTO deviceReport) { DeviceReportRespDTO response = new DeviceReportRespDTO(); response.setServer_time(buildServerTime()); @@ -132,46 +132,12 @@ public class DeviceServiceImpl extends BaseServiceImpl firmware.setUrl("http://localhost:8002/xiaozhi/ota/download"); response.setFirmware(firmware); - DeviceEntity deviceById = getDeviceById(deviceId); + DeviceEntity deviceById = getDeviceById(macAddress); if (deviceById != null) { // 如果设备存在,则更新上次连接时间 deviceById.setLastConnectedAt(new Date()); deviceDao.updateById(deviceById); } else { // 如果设备不存在,则生成激活码 - String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); - String dataKey = String.format("ota:activation:data:%s", safeDeviceId); - - Map cacheMap = redisTemplate.opsForHash().entries(dataKey); - DeviceReportRespDTO.Activation code = new DeviceReportRespDTO.Activation(); - - if (cacheMap != null && cacheMap.containsKey("activation_code")) { - String cachedCode = (String) cacheMap.get("activation_code"); - code.setCode(cachedCode); - code.setMessage(frontedUrl + "\n" + cachedCode); - } else { - String newCode = RandomUtil.randomNumbers(6); - code.setCode(newCode); - code.setMessage(frontedUrl + "\n" + newCode); - - Map dataMap = new HashMap<>(); - dataMap.put("id", deviceId); - dataMap.put("mac_address", macAddress); - dataMap.put("board", (deviceReport.getChipModelName() != null) ? deviceReport.getChipModelName() - : (deviceReport.getBoard() != null ? deviceReport.getBoard().getType() : "unknown")); - dataMap.put("app_version", (deviceReport.getApplication() != null) - ? deviceReport.getApplication().getVersion() - : null); - dataMap.put("deviceId", deviceId); - dataMap.put("activation_code", newCode); - - // 写入主数据 key - redisTemplate.opsForHash().putAll(dataKey, dataMap); - redisTemplate.expire(dataKey, 24, TimeUnit.HOURS); - - // 写入反查激活码 key - String codeKey = "ota:activation:code:" + newCode; - redisTemplate.opsForValue().set(codeKey, deviceId, 24, TimeUnit.HOURS); - } - + DeviceReportRespDTO.Activation code = buildActivation(macAddress, deviceReport); response.setActivation(code); } @@ -258,4 +224,60 @@ public class DeviceServiceImpl extends BaseServiceImpl serverTime.setTimezone_offset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000)); return serverTime; } + + @Override + public String geCodeByDeviceId(String deviceId) { + String dataKey = getDeviceCacheKey(deviceId); + + Map cacheMap = redisTemplate.opsForHash().entries(dataKey); + if (cacheMap != null && cacheMap.containsKey("activation_code")) { + String cachedCode = (String) cacheMap.get("activation_code"); + return cachedCode; + } + return null; + } + + private String getDeviceCacheKey(String deviceId) { + String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); + String dataKey = String.format("ota:activation:data:%s", safeDeviceId); + return dataKey; + } + + public DeviceReportRespDTO.Activation buildActivation(String deviceId, DeviceReportReqDTO deviceReport) { + DeviceReportRespDTO.Activation code = new DeviceReportRespDTO.Activation(); + + String cachedCode = geCodeByDeviceId(deviceId); + + if (StringUtils.isNotBlank(cachedCode)) { + code.setCode(cachedCode); + code.setMessage(frontedUrl + "\n" + cachedCode); + } else { + String newCode = RandomUtil.randomNumbers(6); + code.setCode(newCode); + code.setMessage(frontedUrl + "\n" + newCode); + + Map dataMap = new HashMap<>(); + dataMap.put("id", deviceId); + dataMap.put("mac_address", deviceId); + + dataMap.put("board", (deviceReport.getChipModelName() != null) ? deviceReport.getChipModelName() + : (deviceReport.getBoard() != null ? deviceReport.getBoard().getType() : "unknown")); + dataMap.put("app_version", (deviceReport.getApplication() != null) + ? deviceReport.getApplication().getVersion() + : null); + + dataMap.put("deviceId", deviceId); + dataMap.put("activation_code", newCode); + + // 写入主数据 key + String dataKey = getDeviceCacheKey(deviceId); + redisTemplate.opsForHash().putAll(dataKey, dataMap); + redisTemplate.expire(dataKey, 24, TimeUnit.HOURS); + + // 写入反查激活码 key + String codeKey = "ota:activation:code:" + newCode; + redisTemplate.opsForValue().set(codeKey, deviceId, 24, TimeUnit.HOURS); + } + return code; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages.properties b/main/manager-api/src/main/resources/i18n/messages.properties index ace07209..5126feb2 100644 --- a/main/manager-api/src/main/resources/i18n/messages.properties +++ b/main/manager-api/src/main/resources/i18n/messages.properties @@ -41,3 +41,6 @@ 10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false 10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F 10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F + +10041=\u8BBE\u5907\u672A\u627E\u5230 +10042={0} diff --git a/main/manager-api/src/main/resources/i18n/messages_en_US.properties b/main/manager-api/src/main/resources/i18n/messages_en_US.properties index 9c3a3c12..43781857 100644 --- a/main/manager-api/src/main/resources/i18n/messages_en_US.properties +++ b/main/manager-api/src/main/resources/i18n/messages_en_US.properties @@ -40,4 +40,7 @@ 10037=Parameter value must be a valid number 10038=Parameter value must be true or false 10039=Parameter value must be a valid JSON array format -10040=Parameter value must be a valid JSON format \ No newline at end of file +10040=Parameter value must be a valid JSON format + +10041=Device not found +10042={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties index 22c4842a..713ab96a 100644 --- a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties +++ b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties @@ -40,4 +40,7 @@ 10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57 10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false 10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F -10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F \ No newline at end of file +10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F + +10041=\u8BBE\u5907\u672A\u627E\u5230 +10042={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties index 663a8e98..b5847ec4 100644 --- a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties +++ b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties @@ -40,4 +40,7 @@ 10037=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684\u6578\u5B57 10038=\u53C3\u6578\u503C\u5FC5\u9808\u662Ftrue\u6216false 10039=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u6578\u7D44\u683C\u5F0F -10040=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u683C\u5F0F \ No newline at end of file +10040=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u683C\u5F0F + +10041=\u8A2D\u5099\u672A\u627E\u5230 +10042={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/validation.properties b/main/manager-api/src/main/resources/i18n/validation.properties index dd9b47e8..652ae269 100644 --- a/main/manager-api/src/main/resources/i18n/validation.properties +++ b/main/manager-api/src/main/resources/i18n/validation.properties @@ -33,3 +33,5 @@ timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u952E\u4E0D\u53EF\u4EE5\u4 timbre.ttsVoice.require=\u97F3\u8272\u7684\u7F16\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653EDemo\u4E0D\u53EF\u4EE5\u4E3A\u7A7A +ota.device.not.found=\u8BBE\u5907\u672A\u627E\u5230 +ota.device.need.bind={0} diff --git a/main/manager-api/src/main/resources/i18n/validation_en_US.properties b/main/manager-api/src/main/resources/i18n/validation_en_US.properties index efb2c296..fb6f3828 100644 --- a/main/manager-api/src/main/resources/i18n/validation_en_US.properties +++ b/main/manager-api/src/main/resources/i18n/validation_en_US.properties @@ -32,3 +32,5 @@ timbre.ttsModelId.require=The TTS model ID of the timbre cannot be empty timbre.ttsVoice.require=The TTS voice of the timbre cannot be empty timbre.voiceDemo.require=The voice demo of the timbre cannot be empty +ota.device.not.found=Device not found +ota.device.need.bind={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties index ebafda82..1ff8a3ba 100644 --- a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties +++ b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties @@ -32,3 +32,5 @@ timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u9375\u4E0D\u53EF\u4EE5\u7 timbre.ttsVoice.require=\u97F3\u8272\u7684\u7DE8\u78BC\u4E0D\u53EF\u4EE5\u70BA\u7A7A timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653E\u5730\u5740\u4E0D\u53EF\u4EE5\u70BA\u7A7A +ota.device.not.found=\u8A2D\u5099\u672A\u627E\u5230 +ota.device.need.bind={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties index 4a1aabd2..dd6eab90 100644 --- a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties +++ b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties @@ -31,3 +31,6 @@ timbre.remark.require=\u97F3\u8272\u7684\u5907\u6CE8\u4E0D\u53EF\u4EE5\u4E3A\u7A timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u952E\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.ttsVoice.require=\u97F3\u8272\u7684\u7F16\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653EDemo\u4E0D\u53EF\u4EE5\u4E3A\u7A7A + +ota.device.not.found=\u8A2D\u5099\u672A\u627E\u5230 +ota.device.need.bind={0} \ No newline at end of file diff --git a/main/xiaozhi-server/config/config_loader.py b/main/xiaozhi-server/config/config_loader.py index cdb9e2e1..d71da2d3 100644 --- a/main/xiaozhi-server/config/config_loader.py +++ b/main/xiaozhi-server/config/config_loader.py @@ -4,6 +4,17 @@ import requests import yaml import time + +class DeviceNotFoundException(Exception): + pass + + +class DeviceBindException(Exception): + def __init__(self, bind_code): + self.bind_code = bind_code + super().__init__(f"设备绑定异常,绑定码: {bind_code}") + + # 添加全局配置缓存 _config_cache = None @@ -83,7 +94,11 @@ def _make_api_request(api_url, secret, endpoint, json_data=None): response = requests.post(f"{api_url}{endpoint}", json=json_data) if response.status_code == 200: result = response.json() - if result.get("code") != 0: + if result.get("code") == 10041: + raise DeviceNotFoundException(result.get("msg")) + elif result.get("code") == 10042: + raise DeviceBindException(result.get("msg")) + elif result.get("code") != 0: raise Exception(f"API返回错误: {result.get('msg', '未知错误')}") return result.get("data") diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 146241ff..f2cb9499 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -27,7 +27,11 @@ from core.handle.functionHandler import FunctionHandler from plugins_func.register import Action, ActionResponse from core.auth import AuthMiddleware, AuthenticationError from core.mcp.manager import MCPManager -from config.config_loader import get_private_config_from_api +from config.config_loader import ( + get_private_config_from_api, + DeviceNotFoundException, + DeviceBindException, +) TAG = __name__ @@ -46,6 +50,9 @@ class ConnectionHandler: self.logger = setup_logging() self.auth = AuthMiddleware(config) + self.need_bind = False + self.bind_code = None + self.websocket = None self.headers = None self.client_ip = None @@ -206,7 +213,15 @@ class ConnectionHandler: ) private_config["delete_audio"] = bool(self.config.get("delete_audio", True)) self.logger.bind(tag=TAG).info(f"获取差异化配置成功: {private_config}") + except DeviceNotFoundException as e: + self.need_bind = True + private_config = {} + except DeviceBindException as e: + self.need_bind = True + self.bind_code = e.bind_code + private_config = {} except Exception as e: + self.need_bind = True self.logger.bind(tag=TAG).error(f"获取差异化配置失败: {e}") private_config = {} @@ -345,7 +360,6 @@ class ConnectionHandler: response_message = [] processed_chars = 0 # 跟踪已处理的字符位置 try: - start_time = time.time() # 使用带记忆的对话 future = asyncio.run_coroutine_threadsafe( self.memory.query_memory(query), self.loop @@ -367,9 +381,6 @@ class ConnectionHandler: if self.client_abort: break - end_time = time.time() - # self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}") - # 合并当前全部文本并处理未分割部分 full_text = "".join(response_message) current_text = full_text[processed_chars:] # 从未处理的位置开始 diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index c0ffa429..6c2fc37b 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -1,5 +1,6 @@ from config.logger import setup_logging import time +import asyncio from core.utils.util import remove_punctuation_and_length from core.handle.sendAudioHandle import send_stt_message from core.handle.intentHandler import handle_user_intent @@ -35,9 +36,7 @@ async def handleAudioMessage(conn, audio): if len(conn.asr_audio) < 15: conn.asr_server_receive = True else: - text, file_path = await conn.asr.speech_to_text( - conn.asr_audio, conn.session_id - ) + text, _ = await conn.asr.speech_to_text(conn.asr_audio, conn.session_id) logger.bind(tag=TAG).info(f"识别文本: {text}") text_len, _ = remove_punctuation_and_length(text) if text_len > 0: @@ -49,6 +48,9 @@ async def handleAudioMessage(conn, audio): async def startToChat(conn, text): + if conn.need_bind: + await check_bind_device(conn) + return # 首先进行意图分析 intent_handled = await handle_user_intent(conn, text) @@ -85,3 +87,44 @@ async def no_voice_close_connect(conn): "请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧。" ) await startToChat(conn, prompt) + + +async def check_bind_device(conn): + if conn.bind_code: + # 确保bind_code是6位数字 + if len(conn.bind_code) != 6: + logger.bind(tag=TAG).error(f"无效的绑定码格式: {conn.bind_code}") + text = "绑定码格式错误,请检查配置。" + await send_stt_message(conn, text) + return + + text = f"请登录控制面板,输入{conn.bind_code},绑定设备。" + await send_stt_message(conn, text) + conn.tts_first_text_index = 0 + conn.tts_last_text_index = 6 + conn.llm_finish_task = True + + # 播放提示音 + music_path = "config/assets/bind_code.wav" + opus_packets, _ = conn.tts.audio_to_opus_data(music_path) + conn.audio_play_queue.put((opus_packets, text, 0)) + + # 逐个播放数字 + for i in range(6): # 确保只播放6位数字 + try: + digit = conn.bind_code[i] + num_path = f"config/assets/bind_code/{digit}.wav" + num_packets, _ = conn.tts.audio_to_opus_data(num_path) + conn.audio_play_queue.put((num_packets, text, i + 1)) + except Exception as e: + logger.bind(tag=TAG).error(f"播放数字音频失败: {e}") + continue + else: + text = f"没有找到该设备的版本信息,请正确配置 OTA地址,然后重新编译固件。" + await send_stt_message(conn, text) + conn.tts_first_text_index = 0 + conn.tts_last_text_index = 0 + conn.llm_finish_task = True + music_path = "config/assets/bind_not_found.wav" + opus_packets, _ = conn.tts.audio_to_opus_data(music_path) + conn.audio_play_queue.put((opus_packets, text, 0)) diff --git a/main/xiaozhi-server/core/providers/tts/siliconflow.py b/main/xiaozhi-server/core/providers/tts/siliconflow.py index be6b9e2a..b2f564cc 100644 --- a/main/xiaozhi-server/core/providers/tts/siliconflow.py +++ b/main/xiaozhi-server/core/providers/tts/siliconflow.py @@ -16,7 +16,7 @@ class TTSProvider(TTSProviderBase): self.voice = config.get("voice") self.response_format = config.get("response_format") self.sample_rate = config.get("sample_rate") - self.speed = float(config.get("speed")) + self.speed = float(config.get("speed", 1.0)) self.gain = config.get("gain") self.host = "api.siliconflow.cn"