update:增加单模块mqtt-gateway支持

This commit is contained in:
FAN-yeB
2025-09-22 14:18:03 +08:00
parent 00e8478f61
commit 559699ec19
2 changed files with 123 additions and 3 deletions
+18
View File
@@ -41,6 +41,24 @@ server:
# 可选:设备白名单,如果设置了白名单,那么白名单的机器无论是什么token都可以连接。
#allowed_devices:
# - "24:0A:C4:1D:3B:F0" # MAC地址列表
# MQTT网关配置,用于通过OTA下发到设备
mqtt_gateway:
# MQTT服务器地址以及端口,根据mqtt_gateway的.env文件配置,为null或者不填写时使用websocket协议
host: null
port: null
# group_id会从设备型号动态生成,格式为GID_设备型号
# 此配置仅作为当获取设备型号失败时可以填写的回退值
# group_id:
# MQTT签名密钥,用于生成MQTT连接密码,根据mqtt_gateway的.env文件配置
mqtt_signature_key: null
# UDP网关配置,根据mqtt_gateway的.env文件配置
udp_gateway:
# UDP服务器地址
host: null
# UDP服务器端口
port: null
log:
# 设置控制台输出的日志格式,时间、日志级别、标签、消息
log_format: "<green>{time:YYMMDD HH:mm:ss}</green>[{version}_{selected_module}][<light-blue>{extra[tag]}</light-blue>]-<level>{level}</level>-<light-green>{message}</light-green>"
+105 -3
View File
@@ -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,91 @@ class OTAHandler(BaseHandler):
"version": data_json["application"].get("version", "1.0.0"),
"url": "",
},
"websocket": {
"url": self._get_websocket_url(local_ip, port),
},
}
mqtt_gateway_config = server_config.get("mqtt_gateway", {})
mqtt_gateway_host = mqtt_gateway_config.get("host", "")
mqtt_gateway_port = mqtt_gateway_config.get("port", "")
if mqtt_gateway_host: # 配置了mqtt_gateway,使用MQTT和UDP协议传输
# 从设备型号获取group_id,与manager-api保持一致
# 客户端ID格式:groupId@@@macAddress@@@macAddress
# 尝试从请求数据中获取设备型号
device_model = "default"
try:
# 假设设备型号在request数据的某个字段中
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"]
# 为了保证格式一致性,进行与manager-api相同的处理
group_id = f"GID_{device_model}".replace(":", "_").replace(" ", "_")
except Exception as e:
self.logger.bind(tag=TAG).error(f"获取设备型号失败: {e}")
# 如果获取失败,使用配置文件中的默认值
group_id = mqtt_gateway_config.get("group_id", "GID_default").replace(":", "_")
mac_address_safe = device_id.replace(":", "_")
mqtt_client_id = f"{group_id}@@@{mac_address_safe}@@@{mac_address_safe}"
# 构建用户数据(包含IP等信息)
user_data = {
"ip": "unknown"
}
# 将用户数据编码为Base64 JSON
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 = ""
# 获取MQTT签名密钥
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 = mqtt_gateway_config.get("password", "")
else:
# 如果没有签名密钥,使用配置文件中的密码
password = mqtt_gateway_config.get("password", "")
self.logger.bind(tag=TAG).warning("缺少MQTT签名密钥,使用配置文件中的密码")
# 构建MQTT配置
endpoint = f"{mqtt_gateway_host}:{mqtt_gateway_port}"
return_json["mqtt_gateway"] = {
"endpoint": 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网关配置")
# 添加UDP网关配置
if "udp_gateway" in server_config:
udp_config = server_config["udp_gateway"]
udp_host = udp_config.get("host", "")
udp_port = udp_config.get("port", "")
if udp_host:
return_json["udp_gateway"] = {
"host": udp_host,
"port": udp_port
}
self.logger.bind(tag=TAG).info(f"为设备 {device_id} 下发UDP网关配置")
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",