diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index c63be204..b005cf50 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -37,7 +37,8 @@ log: data_dir: data iot: Speaker: - volume: 100 + # 设置esp32的音量,范围0-100 + volume: 80 xiaozhi: type: hello version: 1 @@ -258,6 +259,21 @@ TTS: parallel_infer: true repetition_penalty: 1.35 aux_ref_audio_paths: [] + GPT_SOVITS_V3: + type: gpt_sovits_v3 + url: "http://127.0.0.1:9880/tts" + output_file: tmp/ + text_lang: "auto" + ref_audio_path: "caixukun.wav" + prompt_lang: "zh" + prompt_text: "" + top_k: 5 + top_p: 1 + temperature: 1 + sample_steps: 16 + media_type: "wav" + streaming_mode: false + threshold: 30 MinimaxTTS: # Minimax语音合成服务,需要先在minimax平台创建账户充值,并获取登录信息 # 平台地址:https://platform.minimaxi.com/ diff --git a/main/xiaozhi-server/core/handle/iotHandle.py b/main/xiaozhi-server/core/handle/iotHandle.py index 7fba6991..05d32d5a 100644 --- a/main/xiaozhi-server/core/handle/iotHandle.py +++ b/main/xiaozhi-server/core/handle/iotHandle.py @@ -109,7 +109,48 @@ async def handleIotDescriptors(conn, descriptors): default_iot_volume = conn.config["iot"]["Speaker"]["volume"] logger.bind(tag=TAG).info(f"服务端设置音量为{default_iot_volume}") await send_iot_conn(conn, "Speaker", "SetVolume", {"volume": default_iot_volume}) +async def handleIotStatus(conn, states): + """ + 处理物联网状态 + 示例: [{ + "name":"Speaker", + "state":{ + "volume":100 + } + }] + states: 状态列表 + """ + for state in states: + for key, value in conn.iot_descriptors.items(): + if key == state["name"]: + for property_item in value.properties: + # properties为字典列表, 记录各种属性 + for k, v in state["state"].items(): + # state为字典, 记录各种属性的值, 是需要记录的信息 + if property_item["name"] == k: + # 检查一下属性是不是相同的 + if type(v) != type(property_item["value"]): + logger.bind(tag=TAG).error(f"属性{property_item['name']}的值类型不匹配") + break + else: + property_item["value"] = v + logger.bind(tag=TAG).info(f"物联网状态更新: {key} , {property_item['name']} = {v}") + break + break +async def get_iot_status(conn, name, property_name): + """ + 获取物联网状态 + name: 设备名称 "Speaker" + property_name: 属性名称 "volume" + 返回值: 属性值, 实际的属性有int, bool和str三种类型 + """ + for key, value in conn.iot_descriptors.items(): + if key == name: + for property_item in value.properties: + if property_item["name"] == property_name: + return property_item["value"] + return None async def send_iot_conn(conn, name, method_name, parameters): """ diff --git a/main/xiaozhi-server/core/handle/textHandle.py b/main/xiaozhi-server/core/handle/textHandle.py index 2eb7304d..cc28dbb4 100644 --- a/main/xiaozhi-server/core/handle/textHandle.py +++ b/main/xiaozhi-server/core/handle/textHandle.py @@ -3,7 +3,7 @@ import json from core.handle.abortHandle import handleAbortMessage from core.handle.helloHandle import handleHelloMessage from core.handle.receiveAudioHandle import startToChat -from core.handle.iotHandle import handleIotDescriptors +from core.handle.iotHandle import handleIotDescriptors, handleIotStatus TAG = __name__ logger = setup_logging() @@ -40,5 +40,7 @@ async def handleTextMessage(conn, message): elif msg_json["type"] == "iot": if "descriptors" in msg_json: await handleIotDescriptors(conn, msg_json["descriptors"]) + if "states" in msg_json: + await handleIotStatus(conn, msg_json["states"]) except json.JSONDecodeError: await conn.websocket.send(message) diff --git a/main/xiaozhi-server/core/providers/tts/gpt_sovits_v3.py b/main/xiaozhi-server/core/providers/tts/gpt_sovits_v3.py new file mode 100644 index 00000000..9ad19855 --- /dev/null +++ b/main/xiaozhi-server/core/providers/tts/gpt_sovits_v3.py @@ -0,0 +1,52 @@ +import os +import uuid +import requests +from config.logger import setup_logging +from datetime import datetime +from core.providers.tts.base import TTSProviderBase + +TAG = __name__ +logger = setup_logging() + +class TTSProvider(TTSProviderBase): + def __init__(self, config, delete_audio_file): + super().__init__(config, delete_audio_file) + self.url = config.get("url") + self.text_lang = config.get("text_lang", "audo") + self.ref_audio_path = config.get("ref_audio_path") + self.prompt_lang = config.get("prompt_lang") + self.prompt_text = config.get("prompt_text") + self.top_k = config.get("top_k", 5) + self.top_p = config.get("top_p", 1) + self.temperature = config.get("temperature", 1) + self.sample_steps = config.get("sample_steps", 16) + self.media_type = config.get("media_type", "wav") + self.streaming_mode = config.get("streaming_mode", False) + self.threshold = config.get("threshold", 30) + + + def generate_filename(self, extension=".wav"): + return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}") + + async def text_to_speak(self, text, output_file): + request_params = { + "text": text, + "text_lang": self.text_lang, + "ref_audio_path": self.ref_audio_path, + "prompt_lang": self.prompt_lang, + "prompt_text": self.prompt_text, + "top_k": self.top_k, + "top_p": self.top_p, + "temperature": self.temperature, + "sample_steps": self.sample_steps, + "media_type": self.media_type, + "streaming_mode": self.streaming_mode, + "threshold": self.threshold, + } + + resp = requests.get(self.url, params=request_params) + if resp.status_code == 200: + with open(output_file, "wb") as file: + file.write(resp.content) + else: + logger.bind(tag=TAG).error(f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}") diff --git a/main/xiaozhi-server/core/utils/util.py b/main/xiaozhi-server/core/utils/util.py index 4bc37d28..8a8dac95 100644 --- a/main/xiaozhi-server/core/utils/util.py +++ b/main/xiaozhi-server/core/utils/util.py @@ -75,7 +75,7 @@ def get_string_no_punctuation_or_emoji(s): def remove_punctuation_and_length(text): # 全角符号和半角符号的Unicode范围 full_width_punctuations = '!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~' - half_width_punctuations = '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~' + half_width_punctuations = r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~' space = ' ' # 半角空格 full_width_space = ' ' # 全角空格 diff --git a/main/xiaozhi-server/docker-compose.yml b/main/xiaozhi-server/docker-compose.yml index d76e6ccb..d74d77f2 100755 --- a/main/xiaozhi-server/docker-compose.yml +++ b/main/xiaozhi-server/docker-compose.yml @@ -21,17 +21,18 @@ 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 - container_name: xiaozhi-esp32-server-web - restart: always - ports: - - "8002:8002" - environment: - - TZ=Asia/Shanghai - ##记得改mysql和redis IP 密码 - - SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://192.168.1.20:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai - - SPRING_DATASOURCE_DRUID_USERNAME=root - - SPRING_DATASOURCE_DRUID_PASSWORD=123456 - - SPRING_DATA_REDIS_HOST=192.168.1.20 - - SPRING_DATA_REDIS_PORT=6379 +# #智控台还没开发好,还不能完全使用,会报很多错误,如果是非技术人员,请不要启用智控台服务 +# xiaozhi-esp32-server-web: +# image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest +# container_name: xiaozhi-esp32-server-web +# restart: always +# ports: +# - "8002:8002" +# environment: +# - TZ=Asia/Shanghai +# ##记得改mysql和redis IP 密码 +# - SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://192.168.1.20:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai +# - SPRING_DATASOURCE_DRUID_USERNAME=root +# - SPRING_DATASOURCE_DRUID_PASSWORD=123456 +# - SPRING_DATA_REDIS_HOST=192.168.1.20 +# - SPRING_DATA_REDIS_PORT=6379