modify: chongming-mqtt 应用
This commit is contained in:
@@ -20,6 +20,7 @@ import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from chongming_worker import auto_bind_models
|
||||
from chongming_worker.runtime import create_workers as _create_workers
|
||||
@@ -27,8 +28,102 @@ from chongming_worker.runtime import create_workers as _create_workers
|
||||
from .router import *
|
||||
from .routes import router # 自定义 FastAPI 路由(网关自动发现)
|
||||
|
||||
# 外部 MQTT 订阅请求处理函数(订阅 /edge_app_controller 等主题的 handler)
|
||||
from .edge_app_controller import edge_app_controller # noqa: F401
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _build_subscribe_routes(topics_cfg: list) -> list:
|
||||
"""
|
||||
根据配置的订阅主题列表构建 MQTTSubscribeClient 路由。
|
||||
|
||||
topics_cfg: [{"topic": "...", "handler": "board_ping", "reply_topic": "..."}, ...]
|
||||
- topic: 订阅主题(支持 + / # 通配符)
|
||||
- handler: 处理函数名,按序从插件 router 模块、插件包自身查找
|
||||
- reply_topic: 可选,回复主题;处理后返回值将发布到该主题(支持 {1}{2} 占位符)
|
||||
|
||||
适配约定(按首个位置参数类型):
|
||||
- Pydantic 模型: 用 payload 构造模型实例后调用(如 board_ping -> BoardPingRequest(**payload))
|
||||
- dict: 直接传整个 payload 和 topic(如 edge_app_controller(payload, topic))
|
||||
- 其他: 将 payload 作为关键字参数展开调用
|
||||
|
||||
返回: [{"topic": ..., "handler": async callable(payload, topic), "reply_topic": ...}]
|
||||
"""
|
||||
import importlib
|
||||
from typing import get_origin
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 注意: 不能用 "from . import router",因为 __init__.py 中
|
||||
# "from .routes import router" 已将 router 设为包级属性(FastAPI APIRouter 对象),
|
||||
# 会遮蔽子模块包 aibox_sophon_adapter.router,故用 importlib 显式按名导入。
|
||||
router_mod = importlib.import_module(f"{__name__}.router")
|
||||
pkg_mod = importlib.import_module(__name__)
|
||||
|
||||
routes = []
|
||||
for t in topics_cfg or []:
|
||||
topic = t.get("topic", "")
|
||||
handler_name = t.get("handler", "")
|
||||
reply_topic = t.get("reply_topic")
|
||||
if not topic or not handler_name:
|
||||
logger.warning(f"[MQTTSubscribe] 跳过无效订阅项: {t}")
|
||||
continue
|
||||
|
||||
fn = getattr(router_mod, handler_name, None)
|
||||
if fn is None:
|
||||
fn = getattr(pkg_mod, handler_name, None)
|
||||
if fn is None:
|
||||
logger.warning(
|
||||
f"[MQTTSubscribe] 未知 handler '{handler_name}',跳过 {topic}"
|
||||
)
|
||||
continue
|
||||
|
||||
# 探测首个位置参数的类型注解
|
||||
hints = getattr(fn, "__annotations__", {})
|
||||
first_type: Any = None
|
||||
for pname, ptype in hints.items():
|
||||
if pname in ("worker_id", "return"):
|
||||
continue
|
||||
first_type = ptype
|
||||
break
|
||||
|
||||
if (
|
||||
isinstance(first_type, type)
|
||||
and issubclass(first_type, BaseModel)
|
||||
):
|
||||
model_cls = first_type
|
||||
|
||||
async def _cb_model(data, topic, _fn=fn, _model=model_cls):
|
||||
return await _fn(_model(**data))
|
||||
routes.append({
|
||||
"topic": topic,
|
||||
"handler": _cb_model,
|
||||
"reply_topic": reply_topic,
|
||||
})
|
||||
elif first_type is dict or get_origin(first_type) is dict:
|
||||
# 处理函数期望整个 dict 作为第一个参数(如 edge_app_controller(payload, topic))
|
||||
async def _cb_payload(data, topic, _fn=fn):
|
||||
return await _fn(data, topic)
|
||||
routes.append({
|
||||
"topic": topic,
|
||||
"handler": _cb_payload,
|
||||
"reply_topic": reply_topic,
|
||||
})
|
||||
else:
|
||||
# 其他签名:将 payload 按关键字参数展开调用
|
||||
async def _cb_kwargs(data, topic, _fn=fn):
|
||||
if isinstance(data, dict):
|
||||
return await _fn(**data)
|
||||
return await _fn()
|
||||
routes.append({
|
||||
"topic": topic,
|
||||
"handler": _cb_kwargs,
|
||||
"reply_topic": reply_topic,
|
||||
})
|
||||
|
||||
return routes
|
||||
|
||||
# 自动绑定 Pydantic 模型(用于 gen_models 生成的模型)
|
||||
auto_bind_models("aibox_sophon_adapter")
|
||||
|
||||
@@ -138,7 +233,7 @@ async def create_workers(session, config, count=1, worker_manager=None):
|
||||
mqtt_fwd_enabled = mqtt_fwd.get("enabled", False)
|
||||
mqtt_fwd_client = None
|
||||
if mqtt_fwd_enabled:
|
||||
from .mqtt_forward import MQTTForwardClient
|
||||
from chongming_mqtt.mqtt_forward import MQTTForwardClient
|
||||
mqtt_fwd_uri = mqtt_fwd.get("uri", "")
|
||||
mqtt_fwd_topic = mqtt_fwd.get("topic", "/board_ping")
|
||||
logger.warning(f"[MQTTForward] 配置已加载: enabled={mqtt_fwd_enabled}, uri={mqtt_fwd_uri}, topic={mqtt_fwd_topic}")
|
||||
@@ -153,12 +248,65 @@ async def create_workers(session, config, count=1, worker_manager=None):
|
||||
else:
|
||||
logger.warning(f"[MQTTForward] mqtt_forward 未启用,配置内容: {mqtt_fwd}")
|
||||
|
||||
# 初始化外部 MQTT 订阅客户端(本插件直接订阅外部 Broker,不走网关 Bridge)
|
||||
mqtt_sub = config.get("mqtt_subscribe", {})
|
||||
mqtt_sub_enabled = mqtt_sub.get("enabled", False)
|
||||
mqtt_sub_client = None
|
||||
if mqtt_sub_enabled:
|
||||
from chongming_mqtt.mqtt_subscribe import MQTTSubscribeClient
|
||||
|
||||
mqtt_sub_uri = mqtt_sub.get("uri", "")
|
||||
mqtt_sub_topics = mqtt_sub.get("topics", []) or []
|
||||
logger.warning(
|
||||
f"[MQTTSubscribe] 配置已加载: enabled={mqtt_sub_enabled}, "
|
||||
f"uri={mqtt_sub_uri}, topics={mqtt_sub_topics}"
|
||||
)
|
||||
if mqtt_sub_uri and mqtt_sub_topics:
|
||||
routes = _build_subscribe_routes(mqtt_sub_topics)
|
||||
mqtt_sub_client = MQTTSubscribeClient(uri=mqtt_sub_uri, routes=routes)
|
||||
try:
|
||||
await mqtt_sub_client.start()
|
||||
logger.warning(f"[MQTTSubscribe] 外部订阅已启用: {mqtt_sub_uri}")
|
||||
except Exception as e:
|
||||
logger.warning(f"[MQTTSubscribe] 启动失败,将跳过订阅: {e}")
|
||||
mqtt_sub_client = None
|
||||
else:
|
||||
logger.warning(
|
||||
"[MQTTSubscribe] 未配置 uri 或 topics,跳过外部订阅"
|
||||
)
|
||||
else:
|
||||
logger.warning(f"[MQTTSubscribe] mqtt_subscribe 未启用,配置内容: {mqtt_sub}")
|
||||
|
||||
# 初始化内部 MQTT 请求-响应客户端
|
||||
# (handler 内对任意应用进程发起请求-响应,主题显式传入,默认连内部 127.0.0.1:1883)
|
||||
int_mqtt_cfg = config.get("internal_mqtt", {})
|
||||
int_mqtt_enabled = int_mqtt_cfg.get("enabled", False)
|
||||
int_mqtt_client = None
|
||||
if int_mqtt_enabled:
|
||||
from chongming_mqtt.internal_mqtt import InternalMQTTClient
|
||||
|
||||
int_mqtt_uri = int_mqtt_cfg.get("uri", "mqtt://127.0.0.1:1883")
|
||||
logger.warning(
|
||||
f"[InternalMQTT] 配置已加载: enabled={int_mqtt_enabled}, uri={int_mqtt_uri}"
|
||||
)
|
||||
int_mqtt_client = InternalMQTTClient(uri=int_mqtt_uri)
|
||||
try:
|
||||
await int_mqtt_client.start()
|
||||
logger.warning(f"[InternalMQTT] 内部请求-响应客户端已启用: {int_mqtt_uri}")
|
||||
except Exception as e:
|
||||
logger.warning(f"[InternalMQTT] 启动失败,将跳过: {e}")
|
||||
int_mqtt_client = None
|
||||
else:
|
||||
logger.warning(f"[InternalMQTT] internal_mqtt 未启用,配置内容: {int_mqtt_cfg}")
|
||||
|
||||
set_global_var("db", db)
|
||||
set_global_var("heartbeat_timeout", hb_timeout)
|
||||
set_global_var("heartbeat_interval", hb_interval)
|
||||
set_global_var("stream_manager", stream_manager)
|
||||
set_global_var("link_path", link_path)
|
||||
set_global_var("mqtt_forward", mqtt_fwd_client)
|
||||
set_global_var("mqtt_subscribe", mqtt_sub_client)
|
||||
set_global_var("internal_mqtt", int_mqtt_client)
|
||||
set_global_var("upload_root", upload_root_path)
|
||||
set_global_var("alarm_max_records", alarm_max)
|
||||
set_global_var("doubao_analysis_enabled", doubao_enabled)
|
||||
@@ -186,7 +334,9 @@ async def cleanup(worker_item: str):
|
||||
|
||||
在 Worker 关闭时调用,关闭数据库连接和后台任务。
|
||||
"""
|
||||
from .global_var import db, stream_manager, mqtt_forward
|
||||
from .global_var import (
|
||||
db, stream_manager, mqtt_forward, mqtt_subscribe, internal_mqtt,
|
||||
)
|
||||
|
||||
global _offline_check_task
|
||||
if _offline_check_task is not None and not _offline_check_task.done():
|
||||
@@ -206,4 +356,10 @@ async def cleanup(worker_item: str):
|
||||
if mqtt_forward is not None:
|
||||
await mqtt_forward.stop()
|
||||
|
||||
if mqtt_subscribe is not None:
|
||||
await mqtt_subscribe.stop()
|
||||
|
||||
if internal_mqtt is not None:
|
||||
await internal_mqtt.stop()
|
||||
|
||||
logger.info(f"[aibox_sophon_adapter] {worker_item} 清理完毕")
|
||||
|
||||
Reference in New Issue
Block a user