From 878809ecb4c00545eb8112ec295ddaee1a2f6fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AC=A3=E5=8D=97=E7=A7=91=E6=8A=80?= Date: Tue, 18 Mar 2025 13:25:34 +0800 Subject: [PATCH] =?UTF-8?q?update:=E4=BF=AE=E5=A4=8Diot=E7=9A=84bug=20(#40?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复manual模式无法识别 (#404) * feat(docs): 新增Issues模板 * fix: 补全core依赖 * fix: 修复manual模式无法识别 * 去除重复依赖.txt 已经有torch和torchaudio --------- Co-authored-by: 欣南科技 * 修复iot功能中表达式问题 (#400) * Custom paths asr tts (#388) * #164 自定义asr、tts缓存目录,项目启动自动创建目录 * #164 自定义asr、tts缓存目录,项目启动自动创建目录 * fix:修复语音无法找到新配置项output_file的bug * fix:电脑不支持iot音量控制bug --------- Co-authored-by: Junsen <66542771+Huang-junsen@users.noreply.github.com> Co-authored-by: tang Co-authored-by: shudongW <178200623@qq.com> Co-authored-by: hrz <1710360675@qq.com> --- main/xiaozhi-server/config.yaml | 32 ++++++------ main/xiaozhi-server/config/settings.py | 49 +++++++++++++++++-- main/xiaozhi-server/core/handle/iotHandle.py | 8 +-- main/xiaozhi-server/core/handle/textHandle.py | 6 ++- .../xiaozhi-server/core/providers/tts/base.py | 2 +- .../core/providers/tts/custom.py | 2 +- .../core/providers/tts/openai.py | 2 +- .../core/providers/tts/ttson.py | 2 +- main/xiaozhi-server/docker-compose.yml | 1 + .../functions/raise_and_lower_the_volume.py | 2 + main/xiaozhi-server/requirements.txt | 2 +- 11 files changed, 78 insertions(+), 30 deletions(-) mode change 100755 => 100644 main/xiaozhi-server/docker-compose.yml diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 689b93fb..f3ca5706 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -114,10 +114,10 @@ Memory: # 每月有1000次免费调用 api_key: 你的mem0ai api key nomem: - # 不想使用记忆功能,可以使用nomem + # 不想使用记忆功能,可以使用nomem type: nomem mem_local_short: - # 本地记忆功能,通过selected_module的llm总结,数据保存在本地,不会上传到服务器 + # 本地记忆功能,通过selected_module的llm总结,数据保存在本地,不会上传到服务器 type: mem_local_short ASR: @@ -225,7 +225,7 @@ TTS: # 定义TTS API类型 type: edge voice: zh-CN-XiaoxiaoNeural - output_file: tmp/ + output_dir: tmp/ DoubaoTTS: # 定义TTS API类型 type: doubao @@ -235,7 +235,7 @@ TTS: # 地址:https://console.volcengine.com/speech/service/8 api_url: https://openspeech.bytedance.com/api/v1/tts voice: BV001_streaming - output_file: tmp/ + output_dir: tmp/ authorization: "Bearer;" appid: 你的火山引擎语音合成服务appid access_token: 你的火山引擎语音合成服务access_token @@ -246,7 +246,7 @@ TTS: # token申请地址 https://cloud.siliconflow.cn/account/ak model: FunAudioLLM/CosyVoice2-0.5B voice: FunAudioLLM/CosyVoice2-0.5B:alex - output_file: tmp/ + output_dir: tmp/ access_token: 你的硅基流动API密钥 response_format: wav CozeCnTTS: @@ -254,7 +254,7 @@ TTS: # COZECN TTS # token申请地址 https://www.coze.cn/open/oauth/pats voice: 7426720361733046281 - output_file: tmp/ + output_dir: tmp/ access_token: 你的coze web key response_format: wav FishSpeech: @@ -267,7 +267,7 @@ TTS: #--decoder-config-name firefly_gan_vq #--compile type: fishspeech - output_file: tmp/ + output_dir: tmp/ response_format: wav reference_id: null reference_audio: ["/tmp/test.wav",] @@ -291,7 +291,7 @@ TTS: #python api_v2.py -a 127.0.0.1 -p 9880 -c GPT_SoVITS/configs/caixukun.yaml type: gpt_sovits_v2 url: "http://127.0.0.1:9880/tts" - output_file: tmp/ + output_dir: tmp/ text_lang: "auto" ref_audio_path: "caixukun.wav" prompt_text: "" @@ -316,7 +316,7 @@ TTS: #python api.py type: gpt_sovits_v3 url: "http://127.0.0.1:9880" - output_file: tmp/ + output_dir: tmp/ text_language: "auto" refer_wav_path: "caixukun.wav" prompt_language: "zh" @@ -337,7 +337,7 @@ TTS: # api_key地址:https://platform.minimaxi.com/user-center/basic-information/interface-key # 定义TTS API类型 type: minimax - output_file: tmp/ + output_dir: tmp/ group_id: 你的minimax平台groupID api_key: 你的minimax平台接口密钥 model: "speech-01-turbo" @@ -374,7 +374,7 @@ TTS: # token地址:https://nls-portal.console.aliyun.com/overview # 定义TTS API类型 type: aliyun - output_file: tmp/ + output_dir: tmp/ appkey: 你的阿里云智能语音交互服务项目Appkey token: 你的阿里云智能语音交互服务AccessToken,临时的24小时,要长期用下方的access_key_id,access_key_secret voice: xiaoyun @@ -397,7 +397,7 @@ TTS: api_url: https://api.302ai.cn/doubao/tts_hd authorization: "Bearer " voice: "zh_female_wanwanxiaohe_moon_bigtts" - output_file: tmp/ + output_dir: tmp/ access_token: "你的302API密钥" ACGNTTS: #在线网址:https://acgn.ttson.cn/ @@ -414,7 +414,7 @@ TTS: to_lang: ZH url: https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token= format: mp3 - output_file: tmp/ + output_dir: tmp/ emotion: 1 OpenAITTS: # openai官方文本转语音服务,可支持全球大多数语种 @@ -428,7 +428,7 @@ TTS: voice: onyx # 语速范围0.25-4.0 speed: 1 - output_file: tmp/ + output_dir: tmp/ CustomTTS: # 自定义的TTS接口服务,请求参数可自定义 # 要求接口使用GET方式请求,并返回音频文件 @@ -443,7 +443,7 @@ TTS: headers: # 自定义请求头 # Authorization: Bearer xxxx format: wav # 接口返回的音频格式 - output_file: tmp/ + output_dir: tmp/ # 模块测试配置 module_test: test_sentences: # 自定义测试语句 @@ -466,4 +466,4 @@ manager: enabled: false ip: 0.0.0.0 port: 8002 -use_private_config: false \ No newline at end of file +use_private_config: false diff --git a/main/xiaozhi-server/config/settings.py b/main/xiaozhi-server/config/settings.py index 0582bf76..bb81cfe9 100644 --- a/main/xiaozhi-server/config/settings.py +++ b/main/xiaozhi-server/config/settings.py @@ -7,9 +7,48 @@ from core.utils.util import read_config, get_project_dir default_config_file = "config.yaml" +def ensure_directories(config): + """确保所有配置路径存在""" + dirs_to_create = set() + project_dir = get_project_dir() # 获取项目根目录 + # 日志文件目录 + log_dir = config.get('log', {}).get('log_dir', 'tmp') + dirs_to_create.add(os.path.join(project_dir, log_dir)) + + # ASR/TTS模块输出目录 + for module in ['ASR', 'TTS']: + for provider in config.get(module, {}).values(): + output_dir = provider.get('output_dir', '') + if output_dir: + dirs_to_create.add(output_dir) + + # 根据selected_module创建模型目录 + selected_modules = config.get('selected_module', {}) + for module_type in ['ASR', 'LLM', 'TTS']: + selected_provider = selected_modules.get(module_type) + if not selected_provider: + continue + provider_config = config.get(module_type, {}).get(selected_provider, {}) + output_dir = provider_config.get('output_dir') + if output_dir: + full_model_dir = os.path.join(project_dir, output_dir) + dirs_to_create.add(full_model_dir) + + # 统一创建目录(保留原data目录创建) + for dir_path in dirs_to_create: + try: + os.makedirs(dir_path, exist_ok=True) + except PermissionError: + print(f"警告:无法创建目录 {dir_path},请检查写入权限") + + def get_config_file(): global default_config_file - # 判断是否存在私有的配置文件 + """获取配置文件路径,优先使用私有配置文件(若存在)。 + + Returns: + str: 配置文件路径(相对路径或默认路径) + """ config_file = default_config_file if os.path.exists(get_project_dir() + "data/." + default_config_file): config_file = "data/." + default_config_file @@ -20,9 +59,13 @@ def load_config(): """加载配置文件""" parser = argparse.ArgumentParser(description="Server configuration") config_file = get_config_file() + parser.add_argument("--config_path", type=str, default=config_file) args = parser.parse_args() - return read_config(args.config_path) + config = read_config(args.config_path) + # 初始化目录 + ensure_directories(config) + return config def update_config(config): @@ -67,7 +110,7 @@ def find_missing_keys(new_config, old_config, parent_key=''): def check_config_file(): old_config_file = get_config_file() global default_config_file - if not old_config_file.startswith('data'): + if not 'data' in old_config_file: return old_config = read_config(get_project_dir() + old_config_file) new_config = read_config(get_project_dir() + default_config_file) diff --git a/main/xiaozhi-server/core/handle/iotHandle.py b/main/xiaozhi-server/core/handle/iotHandle.py index 9def78cd..1cb875cb 100644 --- a/main/xiaozhi-server/core/handle/iotHandle.py +++ b/main/xiaozhi-server/core/handle/iotHandle.py @@ -197,12 +197,12 @@ def register_device_type(descriptor): # 为每个属性创建查询函数 for prop_name, prop_info in descriptor["properties"].items(): - func_name = f"get_{device_name.lower()}_{prop_name.lower()}" + func_name = f"get_{{device_name.lower()}}_{{prop_name.lower()}}" func_desc = { "type": "function", "function": { "name": func_name, - "description": f"查询{descriptor['description']}的{prop_info['description']}", + "description": f"查询{{descriptor['description']}}的{{prop_info['description']}}", "parameters": { "type": "object", "properties": { @@ -212,7 +212,7 @@ def register_device_type(descriptor): }, "response_failure": { "type": "string", - "description": "查询失败时的友好回复,例如:'无法获取{device_name}的{prop_info['description']}'" + "description": f"查询失败时的友好回复,例如:'无法获取{{device_name}}的{{prop_info['description']}}'" } }, "required": ["response_success", "response_failure"] @@ -256,7 +256,7 @@ def register_device_type(descriptor): "type": "function", "function": { "name": func_name, - "description": f"{descriptor['description']} - {method_info['description']}", + "description": f"{{descriptor['description']}} - {{method_info['description']}}", "parameters": { "type": "object", "properties": parameters, diff --git a/main/xiaozhi-server/core/handle/textHandle.py b/main/xiaozhi-server/core/handle/textHandle.py index cc28dbb4..514de127 100644 --- a/main/xiaozhi-server/core/handle/textHandle.py +++ b/main/xiaozhi-server/core/handle/textHandle.py @@ -2,7 +2,7 @@ from config.logger import setup_logging import json from core.handle.abortHandle import handleAbortMessage from core.handle.helloHandle import handleHelloMessage -from core.handle.receiveAudioHandle import startToChat +from core.handle.receiveAudioHandle import startToChat, handleAudioMessage from core.handle.iotHandle import handleIotDescriptors, handleIotStatus TAG = __name__ @@ -24,13 +24,15 @@ async def handleTextMessage(conn, message): elif msg_json["type"] == "listen": if "mode" in msg_json: conn.client_listen_mode = msg_json["mode"] - logger.bind(tag=TAG).debug(f"客户端拾音模式:{conn.client_listen_mode}") + logger.bind(tag=TAG).debug(f"客户端拾音模式:{conn. client_listen_mode}") if msg_json["state"] == "start": conn.client_have_voice = True conn.client_voice_stop = False elif msg_json["state"] == "stop": conn.client_have_voice = True conn.client_voice_stop = True + if len(conn.asr_audio) > 0: + await handleAudioMessage(conn, b'') elif msg_json["state"] == "detect": conn.asr_server_receive = False conn.client_have_voice = False diff --git a/main/xiaozhi-server/core/providers/tts/base.py b/main/xiaozhi-server/core/providers/tts/base.py index e6f013ca..d4a059f8 100644 --- a/main/xiaozhi-server/core/providers/tts/base.py +++ b/main/xiaozhi-server/core/providers/tts/base.py @@ -13,7 +13,7 @@ logger = setup_logging() class TTSProviderBase(ABC): def __init__(self, config, delete_audio_file): self.delete_audio_file = delete_audio_file - self.output_file = config.get("output_file") + self.output_file = config.get("output_dir") @abstractmethod def generate_filename(self): diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index d5447878..3417790f 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -15,7 +15,7 @@ class TTSProvider(TTSProviderBase): self.headers = config.get("headers", {}) self.params = config.get("params") self.format = config.get("format", "wav") - self.output_file = config.get("output_file", "tmp/") + self.output_file = config.get("output_dir", "tmp/") def generate_filename(self): return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}.{self.format}") diff --git a/main/xiaozhi-server/core/providers/tts/openai.py b/main/xiaozhi-server/core/providers/tts/openai.py index dbdf311f..1849c128 100644 --- a/main/xiaozhi-server/core/providers/tts/openai.py +++ b/main/xiaozhi-server/core/providers/tts/openai.py @@ -14,7 +14,7 @@ class TTSProvider(TTSProviderBase): self.voice = config.get("voice", "alloy") self.response_format = "wav" self.speed = config.get("speed", 1.0) - self.output_file = config.get("output_file", "tmp/") + self.output_file = config.get("output_dir", "tmp/") check_model_key("TTS", self.api_key) def generate_filename(self, extension=".wav"): diff --git a/main/xiaozhi-server/core/providers/tts/ttson.py b/main/xiaozhi-server/core/providers/tts/ttson.py index e56fd02d..e9fee109 100644 --- a/main/xiaozhi-server/core/providers/tts/ttson.py +++ b/main/xiaozhi-server/core/providers/tts/ttson.py @@ -17,7 +17,7 @@ class TTSProvider(TTSProviderBase): self.volume_change_dB = config.get("volume_change_dB", 0) self.speed_factor = config.get("speed_factor", 1) self.stream = config.get("stream", False) - self.output_file = config.get("output_file") + self.output_file = config.get("output_dir") self.pitch_factor = config.get("pitch_factor", 0) self.format = config.get("format", "mp3") self.emotion = config.get("emotion", 1) diff --git a/main/xiaozhi-server/docker-compose.yml b/main/xiaozhi-server/docker-compose.yml old mode 100755 new mode 100644 index d74d77f2..c22e7545 --- a/main/xiaozhi-server/docker-compose.yml +++ b/main/xiaozhi-server/docker-compose.yml @@ -21,6 +21,7 @@ services: - ./data:/opt/xiaozhi-esp32-server/data # 模型文件挂接,很重要 - ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt + # #智控台还没开发好,还不能完全使用,会报很多错误,如果是非技术人员,请不要启用智控台服务 # xiaozhi-esp32-server-web: # image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest diff --git a/main/xiaozhi-server/plugins_func/functions/raise_and_lower_the_volume.py b/main/xiaozhi-server/plugins_func/functions/raise_and_lower_the_volume.py index 2a5aa965..c77cb78b 100644 --- a/main/xiaozhi-server/plugins_func/functions/raise_and_lower_the_volume.py +++ b/main/xiaozhi-server/plugins_func/functions/raise_and_lower_the_volume.py @@ -49,6 +49,8 @@ def raise_and_lower_the_volume(conn, action: str): async def _raise_and_lower_the_volume(conn, action): volume = await get_iot_status(conn, "Speaker", "volume") + if volume is None: + raise Exception("你的设备不支持音量控制") if action == 'raise': volume += 10 elif action == 'lower': diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt index bcd6b4ee..81d0cdfd 100755 --- a/main/xiaozhi-server/requirements.txt +++ b/main/xiaozhi-server/requirements.txt @@ -19,4 +19,4 @@ loguru==0.7.3 requests==2.32.3 cozepy==0.12.0 mem0ai==0.1.62 -bs4==0.0.2 \ No newline at end of file +bs4==0.0.2