update:从vision_url配置里读取域名和端口号

This commit is contained in:
hrz
2025-12-18 17:37:20 +08:00
parent f5565f6700
commit 6e7c86e159
4 changed files with 99 additions and 67 deletions
+9 -1
View File
@@ -10,7 +10,15 @@ class BaseHandler:
def _add_cors_headers(self, response):
"""添加CORS头信息"""
response.headers["Access-Control-Allow-Headers"] = (
"client-id, content-type, device-id"
"client-id, content-type, device-id, authorization"
)
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Origin"] = "*"
async def handle_options(self, request):
"""处理OPTIONS请求,添加CORS头信息"""
response = web.Response(body=b"", content_type="text/plain")
self._add_cors_headers(response)
# 添加允许的方法
response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
return response
+38 -25
View File
@@ -9,10 +9,8 @@ import glob
from typing import Dict, List, Tuple
from aiohttp import web
from urllib.parse import urlparse
from core.auth import AuthManager
from core.utils.util import get_local_ip
from core.utils.util import get_local_ip, get_vision_url
from core.api.base_handler import BaseHandler
TAG = __name__
@@ -59,12 +57,18 @@ class OTAHandler(BaseHandler):
# firmware storage
self.bin_dir = os.path.join(os.getcwd(), "data", "bin")
# cache structure: { 'updated_at': timestamp, 'ttl': seconds, 'files_by_model': { model: [(version, filename), ...] } }
self._bin_cache: Dict = {"updated_at": 0, "ttl": config.get("firmware_cache_ttl", 30), "files_by_model": {}}
self._bin_cache: Dict = {
"updated_at": 0,
"ttl": config.get("firmware_cache_ttl", 30),
"files_by_model": {},
}
def _refresh_bin_cache_if_needed(self):
now = int(time.time())
ttl = int(self._bin_cache.get("ttl", 30))
if now - int(self._bin_cache.get("updated_at", 0)) < ttl and self._bin_cache.get("files_by_model"):
if now - int(
self._bin_cache.get("updated_at", 0)
) < ttl and self._bin_cache.get("files_by_model"):
return
files_by_model: Dict[str, List[Tuple[str, str]]] = {}
@@ -91,7 +95,9 @@ class OTAHandler(BaseHandler):
self._bin_cache["files_by_model"] = files_by_model
self._bin_cache["updated_at"] = now
self.logger.bind(tag=TAG).info(f"Firmware cache refreshed: {len(files_by_model)} models")
self.logger.bind(tag=TAG).info(
f"Firmware cache refreshed: {len(files_by_model)} models"
)
except Exception as e:
self.logger.bind(tag=TAG).error(f"刷新固件缓存失败: {e}")
# keep previous cache if any
@@ -175,18 +181,6 @@ class OTAHandler(BaseHandler):
http_port = int(server_config.get("http_port", 8003))
local_ip = get_local_ip()
ota_addr = ""
websocket_addr = self._get_websocket_url(local_ip, websocket_port)
parsedurl = urlparse(websocket_addr)
netloc = parsedurl.netloc
if netloc:
host_part = netloc.split(":")[0]
ota_addr = host_part if host_part else ""
if ota_addr == "":
ota_addr = server_config.get("ota_addr", "")
if ota_addr == "":
ota_addr = local_ip
# Determine device model (prefer headers)
device_model = ""
# header candidates
@@ -208,7 +202,13 @@ class OTAHandler(BaseHandler):
# Determine device current version (prefer headers)
device_version = ""
for h in ("device-version", "device_version", "firmware-version", "app-version", "application-version"):
for h in (
"device-version",
"device_version",
"firmware-version",
"app-version",
"application-version",
):
if h in request.headers:
device_version = request.headers.get(h, "").strip()
break
@@ -303,7 +303,9 @@ class OTAHandler(BaseHandler):
files_by_model = self._bin_cache.get("files_by_model", {})
candidates = files_by_model.get(device_model, [])
self.logger.bind(tag=TAG).info(f"查找型号 {device_model} 的固件,找到 {len(candidates)} 个候选")
self.logger.bind(tag=TAG).info(
f"查找型号 {device_model} 的固件,找到 {len(candidates)} 个候选"
)
chosen_url = ""
chosen_version = device_version
@@ -313,16 +315,24 @@ class OTAHandler(BaseHandler):
if _is_higher_version(ver, device_version):
# build download url (only allow download via our download endpoint)
chosen_version = ver
# use local_ip and http_port to construct url
chosen_url = f"http://{ota_addr}:{http_port}/xiaozhi/ota/download/{fname}"
# Use get_vision_url to get the base URL and replace the path
vision_url = get_vision_url(self.config)
# Replace the path from "/mcp/vision/explain" to "/xiaozhi/ota/download/{fname}"
chosen_url = vision_url.replace(
"/mcp/vision/explain", f"/xiaozhi/ota/download/{fname}"
)
break
if chosen_url:
return_json["firmware"]["version"] = chosen_version
return_json["firmware"]["url"] = chosen_url
self.logger.bind(tag=TAG).info(f"为设备 {device_id} 下发固件 {chosen_version} -> {chosen_url}")
self.logger.bind(tag=TAG).info(
f"为设备 {device_id} 下发固件 {chosen_version} [如果地址前缀有误,请检查配置文件中的server.vision_explain]-> {chosen_url} "
)
else:
self.logger.bind(tag=TAG).info(f"设备 {device_id} 固件已是最新: {device_version}")
self.logger.bind(tag=TAG).info(
f"设备 {device_id} 固件已是最新: {device_version}"
)
except Exception as e:
self.logger.bind(tag=TAG).error(f"检查固件版本时出错: {e}")
@@ -381,7 +391,10 @@ class OTAHandler(BaseHandler):
# ensure realpath is under bin_dir
file_real = os.path.realpath(file_path)
bin_dir_real = os.path.realpath(self.bin_dir)
if not file_real.startswith(bin_dir_real + os.sep) and file_real != bin_dir_real:
if (
not file_real.startswith(bin_dir_real + os.sep)
and file_real != bin_dir_real
):
raise web.HTTPForbidden(text="forbidden")
if not os.path.isfile(file_real):
+3 -11
View File
@@ -2,6 +2,7 @@ import json
import copy
from aiohttp import web
from config.logger import setup_logging
from core.api.base_handler import BaseHandler
from core.utils.util import get_vision_url, is_valid_image_file
from core.utils.vllm import create_instance
from config.config_loader import get_private_config_from_api
@@ -16,10 +17,9 @@ TAG = __name__
MAX_FILE_SIZE = 5 * 1024 * 1024
class VisionHandler:
class VisionHandler(BaseHandler):
def __init__(self, config: dict):
self.config = config
self.logger = setup_logging()
super().__init__(config)
# 初始化认证工具
self.auth = AuthToken(config["server"]["auth_key"])
@@ -172,11 +172,3 @@ class VisionHandler:
finally:
self._add_cors_headers(response)
return response
def _add_cors_headers(self, response):
"""添加CORS头信息"""
response.headers["Access-Control-Allow-Headers"] = (
"client-id, content-type, device-id"
)
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Origin"] = "*"
+49 -30
View File
@@ -33,41 +33,60 @@ class SimpleHttpServer:
return f"ws://{local_ip}:{port}/xiaozhi/v1/"
async def start(self):
server_config = self.config["server"]
read_config_from_api = self.config.get("read_config_from_api", False)
host = server_config.get("ip", "0.0.0.0")
port = int(server_config.get("http_port", 8003))
try:
server_config = self.config["server"]
read_config_from_api = self.config.get("read_config_from_api", False)
host = server_config.get("ip", "0.0.0.0")
port = int(server_config.get("http_port", 8003))
if port:
app = web.Application()
if port:
app = web.Application()
if not read_config_from_api:
# 如果没有开启智控台,只是单模块运行,就需要再添加简单OTA接口,用于下发websocket接口
if not read_config_from_api:
# 如果没有开启智控台,只是单模块运行,就需要再添加简单OTA接口,用于下发websocket接口
app.add_routes(
[
web.get("/xiaozhi/ota/", self.ota_handler.handle_get),
web.post("/xiaozhi/ota/", self.ota_handler.handle_post),
web.options(
"/xiaozhi/ota/", self.ota_handler.handle_options
),
# 下载接口,仅提供 data/bin/*.bin 下载
web.get(
"/xiaozhi/ota/download/{filename}",
self.ota_handler.handle_download,
),
web.options(
"/xiaozhi/ota/download/{filename}",
self.ota_handler.handle_options,
),
]
)
# 添加路由
app.add_routes(
[
web.get("/xiaozhi/ota/", self.ota_handler.handle_get),
web.post("/xiaozhi/ota/", self.ota_handler.handle_post),
web.options("/xiaozhi/ota/", self.ota_handler.handle_post),
# 下载接口,仅提供 data/bin/*.bin 下载
web.get("/xiaozhi/ota/download/{filename}", self.ota_handler.handle_download),
web.options("/xiaozhi/ota/download/{filename}", self.ota_handler.handle_download),
web.get("/mcp/vision/explain", self.vision_handler.handle_get),
web.post(
"/mcp/vision/explain", self.vision_handler.handle_post
),
web.options(
"/mcp/vision/explain", self.vision_handler.handle_options
),
]
)
# 添加路由
app.add_routes(
[
web.get("/mcp/vision/explain", self.vision_handler.handle_get),
web.post("/mcp/vision/explain", self.vision_handler.handle_post),
web.options("/mcp/vision/explain", self.vision_handler.handle_post),
]
)
# 运行服务
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, host, port)
await site.start()
# 运行服务
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, host, port)
await site.start()
# 保持服务运行
while True:
await asyncio.sleep(3600) # 每隔 1 小时检查一次
# 保持服务运行
while True:
await asyncio.sleep(3600) # 每隔 1 小时检查一次
except Exception as e:
self.logger.bind(tag=TAG).error(f"HTTP服务器启动失败: {e}")
import traceback
self.logger.bind(tag=TAG).error(f"错误堆栈: {traceback.format_exc()}")
raise