mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-26 00:53:54 +08:00
Merge branch 'main' of https://github.com/MakerZorky/xiaozhi-esp32-server
This commit is contained in:
@@ -163,6 +163,8 @@ server:
|
|||||||
|
|
||||||
```
|
```
|
||||||
智控台地址: https://2662r3426b.vicp.fun
|
智控台地址: https://2662r3426b.vicp.fun
|
||||||
|
|
||||||
|
服务测试工具: https://2662r3426b.vicp.fun/test/
|
||||||
OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/
|
OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/
|
||||||
Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||||
```
|
```
|
||||||
|
|||||||
+2
-6
@@ -106,10 +106,6 @@ VAD:
|
|||||||
|
|
||||||
### 8、更多问题,可联系我们反馈 💬
|
### 8、更多问题,可联系我们反馈 💬
|
||||||
|
|
||||||
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。
|
可以在[issues](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues)提交您的问题。
|
||||||
|
|
||||||
网盘里有"硬件烧录QQ群"、"开源服务端交流群"、"产品建议联系人" 三张图片,请根据需要选择加入。
|
也可以发邮件我们取得联系:huangrongzhuang@xin-nan.com
|
||||||
|
|
||||||
- 硬件烧录QQ群:适用于硬件烧录问题
|
|
||||||
- 开源服务端交流群:适用于服务端问题
|
|
||||||
- 产品建议联系人:适用于产品功能、产品设计等建议
|
|
||||||
@@ -86,14 +86,9 @@ idf.py set-target esp32s3
|
|||||||
idf.py menuconfig
|
idf.py menuconfig
|
||||||
```
|
```
|
||||||
|
|
||||||

|
进入菜单配置后,再进入`Xiaozhi Assistant`,将`BOARD_TYPE`设置你板子的具体型号
|
||||||
|
|
||||||
进入菜单配置后,再进入`Xiaozhi Assistant`,将`CONNECTION_TYPE`设置为`Websocket`
|
|
||||||
回退到主菜单,再进入`Xiaozhi Assistant`,将`BOARD_TYPE`设置你板子的具体型号
|
|
||||||
保存退出,回到终端命令行。
|
保存退出,回到终端命令行。
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 第5步 编译固件
|
## 第5步 编译固件
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 137 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 160 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 149 KiB |
+16
-2
@@ -28,7 +28,6 @@ import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
|||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.device.utils.NetworkUtil;
|
|
||||||
import xiaozhi.modules.sys.service.SysParamsService;
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
@Tag(name = "设备管理", description = "OTA 相关接口")
|
@Tag(name = "设备管理", description = "OTA 相关接口")
|
||||||
@@ -53,7 +52,7 @@ public class OTAController {
|
|||||||
clientId = deviceId;
|
clientId = deviceId;
|
||||||
}
|
}
|
||||||
String macAddress = deviceReportReqDTO.getMacAddress();
|
String macAddress = deviceReportReqDTO.getMacAddress();
|
||||||
boolean macAddressValid = NetworkUtil.isMacAddressValid(macAddress);
|
boolean macAddressValid = isMacAddressValid(macAddress);
|
||||||
// 设备Id和Mac地址应是一致的, 并且必须需要application字段
|
// 设备Id和Mac地址应是一致的, 并且必须需要application字段
|
||||||
if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) {
|
if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) {
|
||||||
return createResponse(DeviceReportRespDTO.createError("Invalid OTA request"));
|
return createResponse(DeviceReportRespDTO.createError("Invalid OTA request"));
|
||||||
@@ -102,4 +101,19 @@ public class OTAController {
|
|||||||
.contentLength(jsonBytes.length)
|
.contentLength(jsonBytes.length)
|
||||||
.body(json);
|
.body(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单判断mac地址是否有效(非严格)
|
||||||
|
*
|
||||||
|
* @param macAddress
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isMacAddressValid(String macAddress) {
|
||||||
|
if (StringUtils.isBlank(macAddress)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// MAC地址通常为12位十六进制数字,可以包含冒号或连字符分隔符
|
||||||
|
String macPattern = "^([0-9A-Za-z]{2}[:-]){5}([0-9A-Za-z]{2})$";
|
||||||
|
return macAddress.matches(macPattern);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package xiaozhi.modules.device.utils;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络工具类
|
|
||||||
*/
|
|
||||||
public class NetworkUtil {
|
|
||||||
/**
|
|
||||||
* MAC地址正则表达式
|
|
||||||
*/
|
|
||||||
private static final Pattern macPattern = Pattern.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断MAC地址是否合法
|
|
||||||
*/
|
|
||||||
public static boolean isMacAddressValid(String mac) {
|
|
||||||
if (StringUtils.isBlank(mac)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 正则校验格式
|
|
||||||
if (!macPattern.matcher(mac).matches()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 校验MAC地址是否为单播地址
|
|
||||||
String normalized = mac.toLowerCase();
|
|
||||||
String[] parts = normalized.split("[:-]");
|
|
||||||
int firstByte = Integer.parseInt(parts[0], 16);
|
|
||||||
return (firstByte & 1) == 0; // 最低位为0表示单播地址,合法
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -241,7 +241,7 @@ class ConnectionHandler:
|
|||||||
)
|
)
|
||||||
private_config["delete_audio"] = bool(self.config.get("delete_audio", True))
|
private_config["delete_audio"] = bool(self.config.get("delete_audio", True))
|
||||||
self.logger.bind(tag=TAG).info(
|
self.logger.bind(tag=TAG).info(
|
||||||
f"{time.time() - begin_time} 秒,获取差异化配置成功: {private_config}"
|
f"{time.time() - begin_time} 秒,获取差异化配置成功: {json.dumps(filter_sensitive_info(private_config), ensure_ascii=False)}"
|
||||||
)
|
)
|
||||||
except DeviceNotFoundException as e:
|
except DeviceNotFoundException as e:
|
||||||
self.need_bind = True
|
self.need_bind = True
|
||||||
@@ -448,7 +448,7 @@ class ConnectionHandler:
|
|||||||
pos = current_text.rfind(punct)
|
pos = current_text.rfind(punct)
|
||||||
prev_char = current_text[pos - 1] if pos - 1 >= 0 else ""
|
prev_char = current_text[pos - 1] if pos - 1 >= 0 else ""
|
||||||
# 如果.前面是数字统一判断为小数
|
# 如果.前面是数字统一判断为小数
|
||||||
if prev_char.isdigit() and punct == '.':
|
if prev_char.isdigit() and punct == ".":
|
||||||
number_flag = False
|
number_flag = False
|
||||||
if pos > last_punct_pos and number_flag:
|
if pos > last_punct_pos and number_flag:
|
||||||
last_punct_pos = pos
|
last_punct_pos = pos
|
||||||
@@ -579,7 +579,7 @@ class ConnectionHandler:
|
|||||||
pos = current_text.rfind(punct)
|
pos = current_text.rfind(punct)
|
||||||
prev_char = current_text[pos - 1] if pos - 1 >= 0 else ""
|
prev_char = current_text[pos - 1] if pos - 1 >= 0 else ""
|
||||||
# 如果.前面是数字统一判断为小数
|
# 如果.前面是数字统一判断为小数
|
||||||
if prev_char.isdigit() and punct == '.':
|
if prev_char.isdigit() and punct == ".":
|
||||||
number_flag = False
|
number_flag = False
|
||||||
if pos > last_punct_pos and number_flag:
|
if pos > last_punct_pos and number_flag:
|
||||||
last_punct_pos = pos
|
last_punct_pos = pos
|
||||||
@@ -945,3 +945,36 @@ class ConnectionHandler:
|
|||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.bind(tag=TAG).error(f"超时检查任务出错: {e}")
|
self.logger.bind(tag=TAG).error(f"超时检查任务出错: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def filter_sensitive_info(config: dict) -> dict:
|
||||||
|
"""
|
||||||
|
过滤配置中的敏感信息
|
||||||
|
Args:
|
||||||
|
config: 原始配置字典
|
||||||
|
Returns:
|
||||||
|
过滤后的配置字典
|
||||||
|
"""
|
||||||
|
sensitive_keys = [
|
||||||
|
"api_key",
|
||||||
|
"personal_access_token",
|
||||||
|
"access_token",
|
||||||
|
"token",
|
||||||
|
"access_key_secret",
|
||||||
|
"secret_key",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _filter_dict(d: dict) -> dict:
|
||||||
|
filtered = {}
|
||||||
|
for k, v in d.items():
|
||||||
|
if any(sensitive in k.lower() for sensitive in sensitive_keys):
|
||||||
|
filtered[k] = "***"
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
filtered[k] = _filter_dict(v)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
filtered[k] = [_filter_dict(i) if isinstance(i, dict) else i for i in v]
|
||||||
|
else:
|
||||||
|
filtered[k] = v
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
return _filter_dict(copy.deepcopy(config))
|
||||||
|
|||||||
@@ -2,51 +2,52 @@ from config.logger import setup_logging
|
|||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from core.utils.util import (
|
from core.utils.util import get_string_no_punctuation_or_emoji, analyze_emotion
|
||||||
get_string_no_punctuation_or_emoji,
|
|
||||||
analyze_emotion
|
|
||||||
)
|
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
emoji_map = {
|
emoji_map = {
|
||||||
'neutral': '😶',
|
"neutral": "😶",
|
||||||
'happy': '🙂',
|
"happy": "🙂",
|
||||||
'laughing': '😆',
|
"laughing": "😆",
|
||||||
'funny': '😂',
|
"funny": "😂",
|
||||||
'sad': '😔',
|
"sad": "😔",
|
||||||
'angry': '😠',
|
"angry": "😠",
|
||||||
'crying': '😭',
|
"crying": "😭",
|
||||||
'loving': '😍',
|
"loving": "😍",
|
||||||
'embarrassed': '😳',
|
"embarrassed": "😳",
|
||||||
'surprised': '😲',
|
"surprised": "😲",
|
||||||
'shocked': '😱',
|
"shocked": "😱",
|
||||||
'thinking': '🤔',
|
"thinking": "🤔",
|
||||||
'winking': '😉',
|
"winking": "😉",
|
||||||
'cool': '😎',
|
"cool": "😎",
|
||||||
'relaxed': '😌',
|
"relaxed": "😌",
|
||||||
'delicious': '🤤',
|
"delicious": "🤤",
|
||||||
'kissy': '😘',
|
"kissy": "😘",
|
||||||
'confident': '😏',
|
"confident": "😏",
|
||||||
'sleepy': '😴',
|
"sleepy": "😴",
|
||||||
'silly': '😜',
|
"silly": "😜",
|
||||||
'confused': '🙄'
|
"confused": "🙄",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def sendAudioMessage(conn, audios, text, text_index=0):
|
async def sendAudioMessage(conn, audios, text, text_index=0):
|
||||||
# 发送句子开始消息
|
# 发送句子开始消息
|
||||||
emotion = analyze_emotion(text)
|
if text is not None:
|
||||||
emoji = emoji_map.get(emotion, '🙂') # 默认使用笑脸
|
emotion = analyze_emotion(text)
|
||||||
await conn.websocket.send(json.dumps(
|
emoji = emoji_map.get(emotion, "🙂") # 默认使用笑脸
|
||||||
{
|
await conn.websocket.send(
|
||||||
"type": "llm",
|
json.dumps(
|
||||||
"text": emoji,
|
{
|
||||||
"emotion": emotion,
|
"type": "llm",
|
||||||
"session_id": conn.session_id,
|
"text": emoji,
|
||||||
}
|
"emotion": emotion,
|
||||||
|
"session_id": conn.session_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
if text_index == conn.tts_first_text_index:
|
if text_index == conn.tts_first_text_index:
|
||||||
logger.bind(tag=TAG).info(f"发送第一段语音: {text}")
|
logger.bind(tag=TAG).info(f"发送第一段语音: {text}")
|
||||||
await send_tts_message(conn, "sentence_start", text)
|
await send_tts_message(conn, "sentence_start", text)
|
||||||
|
|||||||
Reference in New Issue
Block a user