diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml
index e400f199..4acae788 100644
--- a/main/xiaozhi-server/config.yaml
+++ b/main/xiaozhi-server/config.yaml
@@ -41,6 +41,12 @@ server:
# 可选:设备白名单,如果设置了白名单,那么白名单的机器无论是什么token都可以连接。
#allowed_devices:
# - "24:0A:C4:1D:3B:F0" # MAC地址列表
+ # MQTT网关配置,用于通过OTA下发到设备,根据mqtt_gateway的.env文件配置,格式为host:port
+ mqtt_gateway: null
+ # MQTT签名密钥,用于生成MQTT连接密码,根据mqtt_gateway的.env文件配置
+ mqtt_signature_key: null
+ # UDP网关配置
+ udp_gateway: null
log:
# 设置控制台输出的日志格式,时间、日志级别、标签、消息
log_format: "{time:YYMMDD HH:mm:ss}[{version}_{selected_module}][{extra[tag]}]-{level}-{message}"
diff --git a/main/xiaozhi-server/config/assets/wakeup_words/ed76d459636c2481aec828516c1b4f54.wav b/main/xiaozhi-server/config/assets/wakeup_words/ed76d459636c2481aec828516c1b4f54.wav
new file mode 100644
index 00000000..1e7246be
Binary files /dev/null and b/main/xiaozhi-server/config/assets/wakeup_words/ed76d459636c2481aec828516c1b4f54.wav differ
diff --git a/main/xiaozhi-server/core/api/ota_handler.py b/main/xiaozhi-server/core/api/ota_handler.py
index 763c9c49..b93a7ed3 100644
--- a/main/xiaozhi-server/core/api/ota_handler.py
+++ b/main/xiaozhi-server/core/api/ota_handler.py
@@ -1,5 +1,8 @@
import json
import time
+import base64
+import hashlib
+import hmac
from aiohttp import web
from core.utils.util import get_local_ip
from core.api.base_handler import BaseHandler
@@ -10,6 +13,24 @@ TAG = __name__
class OTAHandler(BaseHandler):
def __init__(self, config: dict):
super().__init__(config)
+
+ def generate_password_signature(self, content: str, secret_key: str) -> str:
+ """生成MQTT密码签名
+
+ Args:
+ content: 签名内容 (clientId + '|' + username)
+ secret_key: 密钥
+
+ Returns:
+ str: Base64编码的HMAC-SHA256签名
+ """
+ try:
+ hmac_obj = hmac.new(secret_key.encode('utf-8'), content.encode('utf-8'), hashlib.sha256)
+ signature = hmac_obj.digest()
+ return base64.b64encode(signature).decode('utf-8')
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"生成MQTT密码签名失败: {e}")
+ return ""
def _get_websocket_url(self, local_ip: str, port: int) -> str:
"""获取websocket地址
@@ -58,10 +79,66 @@ class OTAHandler(BaseHandler):
"version": data_json["application"].get("version", "1.0.0"),
"url": "",
},
- "websocket": {
- "url": self._get_websocket_url(local_ip, port),
- },
}
+
+
+ mqtt_gateway_endpoint = server_config.get("mqtt_gateway")
+
+ if mqtt_gateway_endpoint: # 如果配置了非空字符串
+ # 尝试从请求数据中获取设备型号
+ device_model = "default"
+ try:
+ if "device" in data_json and isinstance(data_json["device"], dict):
+ device_model = data_json["device"].get("model", "default")
+ elif "model" in data_json:
+ device_model = data_json["model"]
+ group_id = f"GID_{device_model}".replace(":", "_").replace(" ", "_")
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"获取设备型号失败: {e}")
+ group_id = "GID_default"
+
+ mac_address_safe = device_id.replace(":", "_")
+ mqtt_client_id = f"{group_id}@@@{mac_address_safe}@@@{mac_address_safe}"
+
+ # 构建用户数据
+ user_data = {
+ "ip": "unknown"
+ }
+ try:
+ user_data_json = json.dumps(user_data)
+ username = base64.b64encode(user_data_json.encode('utf-8')).decode('utf-8')
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"生成用户名失败: {e}")
+ username = ""
+
+ # 生成密码
+ password = ""
+ signature_key = server_config.get("mqtt_signature_key", "")
+ if signature_key:
+ password = self.generate_password_signature(mqtt_client_id + "|" + username, signature_key)
+ if not password:
+ password = "" # 签名失败则留空,由设备决定是否允许无密码
+ else:
+ self.logger.bind(tag=TAG).warning("缺少MQTT签名密钥,密码留空")
+
+ # 构建MQTT配置(直接使用 mqtt_gateway 字符串)
+ return_json["mqtt_gateway"] = {
+ "endpoint": mqtt_gateway_endpoint,
+ "client_id": mqtt_client_id,
+ "username": username,
+ "password": password,
+ "publish_topic": "device-server",
+ "subscribe_topic": f"devices/p2p/{mac_address_safe}"
+ }
+ self.logger.bind(tag=TAG).info(f"为设备 {device_id} 下发MQTT网关配置")
+
+
+ else: # 未配置 mqtt_gateway,下发 WebSocket
+ return_json["websocket"] = {
+ "url": self._get_websocket_url(local_ip, port),
+ }
+ self.logger.bind(tag=TAG).info(f"未配置MQTT网关,为设备 {device_id} 下发WebSocket配置")
+
response = web.Response(
text=json.dumps(return_json, separators=(",", ":")),
content_type="application/json",
@@ -90,4 +167,4 @@ class OTAHandler(BaseHandler):
response = web.Response(text="OTA接口异常", content_type="text/plain")
finally:
self._add_cors_headers(response)
- return response
+ return response
\ No newline at end of file