Files
chongming-plugins/aibox_sophon_adapter/edge_app_controller.py
T

223 lines
7.4 KiB
Python
Raw Normal View History

2026-08-07 14:28:39 +08:00
"""
edge_app_controller — 外部 MQTT 请求处理(请求-响应协议)
订阅主题: /edge_app_controller
回复主题: /edge_app_controller_reply
请求消息 (JSON): {"command": "<命令名>", ...业务字段}
兼容协议: {"Event": "/alg_media_fetch", "BoardId": "..."}(Event 取最后一段作为命令)
回复消息 (JSON): {"code": 0, "msg": "ok", "data": {...}}
- code: 0 成功;非 0 失败
-1 缺少 command 字段
-2 未知命令
-3 命令执行异常
不回复约定:
命令处理函数返回 None 时,该命令只处理、不回复(静默命令,如心跳/日志类)。
适用于按命令/Event 控制是否回复,无需修改配置。
命令通过 _COMMANDS 注册表分发,新增命令只需在注册表登记一个 async 处理函数。
"""
import logging
from typing import Any, Callable, Coroutine
logger = logging.getLogger(__name__)
def _reply(code: int, msg: str, data: Any = None) -> dict:
"""统一回复封装"""
return {"code": code, "msg": msg, "data": data}
async def edge_app_controller(payload: dict, topic: str) -> dict | None:
"""
处理来自外部 Broker 的边缘应用控制器请求(订阅 /edge_app_controller)
Args:
payload: 请求消息(已解析为 dict),期望含 command 字段
topic: 实际命中的主题
Returns:
回复消息(统一封装 code / msg / data),由订阅客户端发布到 reply_topic;
命令函数返回 None 时返回 None(不回复)。
"""
command = (
payload.get("command")
or payload.get("cmd")
or payload.get("action")
or payload.get("Event")
)
# Event 可能是路径形式(如 /alg_media_fetch),取最后一段作为命令名
if isinstance(command, str) and command.startswith("/"):
command = command.rsplit("/", 1)[-1]
logger.info(
f"[EdgeAppController] 收到请求 topic={topic}, command={command}, payload={payload}"
)
if command is None:
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
handler = _COMMANDS.get(str(command))
if handler is None:
logger.warning(f"[EdgeAppController] 未知命令: {command}")
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
try:
data = await handler(payload)
# 命令函数返回 None 表示该命令只处理、不回复(如心跳/日志类静默命令)
if data is None:
logger.info(f"[EdgeAppController] 命令 {command} 处理完成(配置为不回复)")
return None
logger.info(f"[EdgeAppController] 命令 {command} 执行成功")
return data
except Exception as e:
logger.error(f"[EdgeAppController] 命令 {command} 执行失败: {e}")
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
# ============================================================
# 内置命令(示例)
# ============================================================
async def _cmd_ping(payload: dict) -> dict | None:
"""存活探测:返回服务信息"""
return {"service": "aibox_sophon_adapter", "alive": True}
async def _cmd_ack(payload: dict) -> None:
"""
确认命令(示例):仅记录日志,不回复
返回 None 表示不回复 —— 需要"某些命令静默处理"时,让命令函数返回 None 即可。
"""
logger.info(f"[EdgeAppController] 命令 ack 已收到(不回复),payload={payload}")
return None
async def _cmd_query_status(payload: dict) -> dict | None:
"""
查询边缘应用/设备状态
复用插件已有的设备查询逻辑(router.list_devices),返回设备在线状态列表。
后续可按需扩展:查询指定应用运行状态、通道状态等。
"""
from .router.list_devices import list_devices
result = await list_devices()
return result
async def _cmd_alg_media_fetch(payload: dict) -> dict | None:
"""
算法媒体抓取:按 BoardId 返回设备最近一次心跳中的算法/通道(媒体)信息
请求: {"Event": "/alg_media_fetch", "BoardId": "..."}
返回: {"board_id": ..., "medias": [...], "tasks": [...]}
"""
2026-08-10 10:16:25 +08:00
from .global_var import (
db,
internal_mqtt,
internal_request_topic,
internal_response_topic,
)
2026-08-07 14:28:39 +08:00
board_id = payload.get("BoardId")
if not board_id:
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
print(f"[alg_media_fetch]Processing for board: {board_id}")
print(f"[alg_media_fetch]Expected payload: {payload}")
if db is None:
2026-08-11 14:47:19 +08:00
return None
2026-08-07 16:40:48 +08:00
if internal_mqtt is None:
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
async with db.get_session() as session:
from .models.orm import BoardHeartbeat
from sqlalchemy import select
stmt = select(BoardHeartbeat).where(BoardHeartbeat.board_id == board_id) # type: ignore
result = await session.execute(stmt)
record: BoardHeartbeat | None = result.scalar_one_or_none()
if record is None:
print(f"[alg_media_fetch]Board {board_id} not found in database.")
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
2026-08-10 10:44:52 +08:00
# 内部请求-响应主题由 [workers.config.internal_mqtt] 配置,与盒子侧约定一致
# (盒子只设根主题 box,子路径固定 aibox_sophon/...,回复主题固定为 ..._reply)。
# 勿用 chongming/{插件}/{操作}:网关 Bridge 订阅 chongming/+/+,会拦截
# 3 级主题并按 Worker RPC 路由,而 edge_app_controller 不是 @rpc_method
# (Worker 无对应 Queryable),会导致 worker_manager 报 "无回复"。
2026-08-07 16:40:48 +08:00
resp = await internal_mqtt.request(
2026-08-10 10:16:25 +08:00
request_topic=internal_request_topic,
response_topic=internal_response_topic,
2026-08-07 16:40:48 +08:00
payload=payload,
timeout=5
)
print(f"[alg_media_fetch]Response from internal_mqtt: {resp}")
2026-08-10 10:16:25 +08:00
if resp is None:
2026-08-11 14:47:19 +08:00
return None
2026-08-07 16:40:48 +08:00
return resp
2026-08-07 14:28:39 +08:00
async def _cmd_alg_task_fetch(payload: dict) -> dict | None:
"""
算法任务抓取:按 BoardId 返回设备最近一次心跳中的算法任务信息
请求: {"Event": "/alg_task_fetch", "BoardId": "..."}
返回: {"board_id": ..., "tasks": [...]}
"""
2026-08-10 11:29:54 +08:00
from .global_var import (
db,
internal_mqtt,
internal_request_topic,
internal_response_topic,
)
2026-08-07 14:28:39 +08:00
board_id = payload.get("BoardId")
if not board_id:
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
print(f"[alg_task_fetch]Processing for board: {board_id}")
print(f"[alg_task_fetch]Expected payload: {payload}")
if db is None:
2026-08-11 14:47:19 +08:00
return None
2026-08-10 11:29:54 +08:00
if internal_mqtt is None:
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
async with db.get_session() as session:
from .models.orm import BoardHeartbeat
from sqlalchemy import select
stmt = select(BoardHeartbeat).where(BoardHeartbeat.board_id == board_id) # type: ignore
result = await session.execute(stmt)
record: BoardHeartbeat | None = result.scalar_one_or_none()
if record is None:
print(f"[alg_task_fetch]Board {board_id} not found in database.")
2026-08-11 14:47:19 +08:00
return None
2026-08-07 14:28:39 +08:00
2026-08-10 11:29:54 +08:00
resp = await internal_mqtt.request(
request_topic=internal_request_topic,
response_topic=internal_response_topic,
payload=payload,
timeout=5
)
2026-08-07 14:28:39 +08:00
2026-08-10 11:29:54 +08:00
return resp
2026-08-07 14:28:39 +08:00
# 命令注册表:command -> async handler(payload) -> data(返回 None 表示不回复)
_COMMANDS: dict[str, Callable[[dict], Coroutine[Any, Any, Any]]] = {
"ping": _cmd_ping,
"ack": _cmd_ack,
"query_status": _cmd_query_status,
"alg_media_fetch": _cmd_alg_media_fetch,
"alg_task_fetch": _cmd_alg_task_fetch,
}