VWED_server/services/execution/handlers/robot_scheduling.py

784 lines
33 KiB
Python
Raw Normal View History

2025-04-30 16:57:46 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
机器人调度处理器模块
提供与机器人调度操作相关的各种处理器
"""
import json
import asyncio
import aiohttp
import uuid
from typing import Dict, Any, List, Optional
from services.execution.task_context import TaskContext
from .base import BlockHandler, register_handler
from config.settings import settings
from utils.logger import get_logger
from .model.block_name import RobotBlockName
from typing import Tuple
# 获取日志记录器
logger = get_logger("services.execution.handlers.robot_scheduling")
# 提取公共的API调用函数
async def call_robot_api(api_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""
调用机器人调度服务API的通用函数
Args:
api_name: API名称对应API_ENDPOINTS中的键
params: API参数
Returns:
API响应结果
"""
# 获取API端点和方法
endpoint = settings.ROBOT_API_ENDPOINTS.get(api_name)
method = settings.ROBOT_API_METHODS.get(api_name)
if not endpoint or not method:
logger.error(f"未找到API端点或方法{api_name}")
return {
"success": False,
"message": f"未找到API配置: {api_name}"
}
# 检查是否启用测试模式
if settings.ROBOT_API_MOCK_MODE:
logger.info(f"[测试模式] 模拟调用API: {api_name}, 参数: {params}")
# 构造测试模式下的响应
test_response = generate_mock_response(api_name, params)
return test_response
# 构建完整的URL
url = f"{settings.ROBOT_API_BASE_URL}{endpoint}"
# 准备请求头
headers = {"Content-Type": "application/json"}
if settings.ROBOT_API_TOKEN:
headers["Authorization"] = f"Bearer {settings.ROBOT_API_TOKEN}"
logger.info(f"调用外部API {api_name} - {method} {url}, 参数: {params}")
try:
async with aiohttp.ClientSession() as session:
# 根据HTTP方法选择相应的请求方式
if method == "GET":
# 对于GET请求将params转换为URL参数
async with session.get(
url,
params=params,
headers=headers,
timeout=settings.ROBOT_API_TIMEOUT
) as response:
result = await response.json()
elif method == "POST":
# 对于POST请求将params作为JSON数据发送
async with session.post(
url,
json=params,
headers=headers,
timeout=settings.ROBOT_API_TIMEOUT
) as response:
result = await response.json()
elif method == "PUT":
# 对于PUT请求将params作为JSON数据发送
async with session.put(
url,
json=params,
headers=headers,
timeout=settings.ROBOT_API_TIMEOUT
) as response:
result = await response.json()
else:
logger.error(f"不支持的HTTP方法: {method}")
return {
"success": False,
"message": f"不支持的HTTP方法: {method}"
}
# 检查响应状态码
if response.status != 200:
logger.error(f"API调用失败: {url}, 状态码: {response.status}, 响应: {result}")
return {
"success": False,
"message": f"API调用失败, 状态码: {response.status}",
"data": result
}
logger.info(f"API调用成功: {url}, 响应: {result}")
return result
except aiohttp.ClientError as e:
logger.error(f"API调用客户端错误: {url}, 错误: {str(e)}")
return {
"success": False,
"message": f"API调用客户端错误: {str(e)}"
}
except asyncio.TimeoutError:
logger.error(f"API调用超时: {url}")
return {
"success": False,
"message": "API调用超时"
}
except json.JSONDecodeError:
logger.error(f"API响应解析失败: {url}")
return {
"success": False,
"message": "API响应格式错误无法解析JSON"
}
except Exception as e:
logger.error(f"API调用异常: {url}, 错误: {str(e)}")
return {
"success": False,
"message": f"API调用异常: {str(e)}"
}
def generate_mock_response(api_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""
生成测试模式下的模拟响应
Args:
api_name: API名称
params: API参数
Returns:
模拟的API响应
"""
# 基本响应结构
response = {
"success": True,
"message": f"[测试模式] {api_name} 操作成功",
"data": {}
}
# 根据不同API返回不同的模拟数据
if api_name == "agv_operation":
# 机器人通用动作
response["data"] = {
"success": True,
"taskId": f"MOCK-OP-{str(uuid.uuid4())[:8]}"
}
elif api_name == "select_agv":
# 选择执行机器人
response["data"] = {
"agvId": f"MOCK-AGV-{str(uuid.uuid4())[:8]}"
}
elif api_name == "vehicle_station":
# 获取机器人位置
response["data"] = {
"station": f"MOCK-STATION-{str(uuid.uuid4())[:5]}",
"lastStation": f"MOCK-LAST-STATION-{str(uuid.uuid4())[:5]}"
}
elif api_name == "get_battery_level":
# 获取机器人电量
response["data"] = {
"batteryLevel": float(f"{(uuid.uuid4().int % 100) / 100:.2f}")
}
elif api_name == "get_pgv_code":
# 获取机器人PGV码
response["data"] = {
"codeInfo": True if uuid.uuid4().int % 2 == 0 else False
}
# 记录模拟调用结果
logger.info(f"[测试模式] 生成模拟响应: {api_name}, 响应: {response}")
return response
class RobotBlockHandler(BlockHandler):
"""机器人调度处理器基类提供公共的API调用方法"""
async def _call_external_api(self, api_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""调用外部API的通用方法"""
return await call_robot_api(api_name, params)
def _analyze_affected_blocks(self, block: Dict[str, Any], current_block_id: str, current_block_name: str) -> List[Dict[str, Any]]:
"""
分析当前块的结构找出受当前选择机器人块影响的所有下级块
Args:
block: 当前块的定义
current_block_id: 当前块的ID
current_block_name: 当前块的名称
Returns:
List[Dict[str, Any]]: 受影响的块列表每个元素包含块的ID名称类型和与当前块的关系
"""
affected_blocks = []
# 检查当前块是否有子块
if "children" in block and "default" in block["children"]:
children = block["children"]["default"]
logger.info(f"{current_block_name}(ID:{current_block_id}) 有 {len(children)} 个子块")
# 分析每个子块
for child in children:
child_id = child.get("id", "unknown")
child_name = child.get("name", f"b{child_id}")
child_type = child.get("blockType", "unknown")
# 记录子块信息
affected_blocks.append({
"id": child_id,
"name": child_name,
"type": child_type,
"relation": "direct_child",
"parent_id": current_block_id,
"parent_name": current_block_name
})
# 如果子块不是CSelectAgvBp类型则继续分析其子块
# 如果子块是CSelectAgvBp类型则停止分析该分支因为该分支将由子块的CSelectAgvBp负责
if child_type != "CSelectAgvBp" and "children" in child and "default" in child["children"]:
# 递归分析子块的子块
nested_affected = self._analyze_affected_blocks(child, child_id, child_name)
# 将所有嵌套子块添加到结果中
for nested_block in nested_affected:
# 添加到结果列表中,标记为嵌套关系
nested_block["relation"] = "nested_child"
affected_blocks.append(nested_block)
return affected_blocks
def _get_robot_id_for_block(self, block_id: str, block_name: str, context: TaskContext) -> Tuple[Optional[str], Optional[str]]:
"""
获取适用于当前块的机器人ID
首先检查是否有专门为该块设置的机器人ID变量
如果没有则尝试获取全局机器人ID
Args:
block_id: 当前块ID
block_name: 当前块名称
context: 任务上下文
Returns:
Optional[str]: 机器人ID如果没有找到则返回None
"""
# 首先检查是否有专门为该块设置的机器人ID
robot_id = context.get_variable(f"agv_for_block_{block_id}")
agv_task_id = context.get_variable(f"agv_task_id_{block_id}")
if robot_id:
logger.info(f"找到块 {block_name}(ID:{block_id}) 专用的机器人ID: {robot_id}")
return robot_id, agv_task_id
# 检查是否有按名称设置的机器人ID
robot_id = context.get_variable(f"agv_for_{block_name}")
if robot_id:
logger.info(f"找到块 {block_name} 专用的机器人ID: {robot_id}")
return robot_id, agv_task_id
# 如果没有专用设置尝试获取全局机器人ID
robot_id = context.get_variable("selectedAgvId")
if robot_id:
logger.info(f"使用全局机器人ID: {robot_id} 用于块 {block_name}(ID:{block_id})")
return robot_id, agv_task_id
# 如果仍未找到尝试从所有块输出中查找最近的selectedAgvId
# 这是兜底策略,保持向后兼容
for out_block_name, outputs in context.block_outputs.items():
if isinstance(outputs, dict) and "selectedAgvId" in outputs:
robot_id = outputs.get("selectedAgvId")
logger.info(f"从块 {out_block_name} 输出中获取机器人ID: {robot_id} 用于块 {block_name}(ID:{block_id})")
return robot_id, agv_task_id
# 未找到任何机器人ID
logger.warning(f"未找到块 {block_name}(ID:{block_id}) 可用的机器人ID")
return None, None
async def _update_task_record_agv_id(self, task_record_id: str, amr_id: str) -> None:
"""
更新任务记录中的agv_id字段
Args:
task_record_id: 任务记录ID
amr_id: 机器人ID
Returns:
None
"""
try:
from sqlalchemy.ext.asyncio import AsyncSession
from data.session import get_async_session
from data.models.taskrecord import VWEDTaskRecord
from sqlalchemy import select, update
if not amr_id:
logger.warning(f"未提供AMR ID无法更新任务记录 {task_record_id}")
return
# 将多个AMR ID用逗号连接
logger.info(f"准备更新任务 {task_record_id} 的机器人ID: {amr_id}")
# 先查询当前任务记录中是否已有agv_id
async with get_async_session() as session:
session: AsyncSession = session
# 查询当前记录
stmt = select(VWEDTaskRecord.agv_id).where(VWEDTaskRecord.id == task_record_id)
result = await session.execute(stmt)
current_agv_id = result.scalar_one_or_none()
# 确定最终要存储的agv_id值
if current_agv_id:
# 如果已经有值,需要合并并去重
logger.info(f"任务 {task_record_id} 当前已存在的机器人IDs: {current_agv_id}")
current_ids = current_agv_id.split(",")
all_ids = current_ids + [amr_id]
# 去重并过滤空值
unique_ids = list(set([id for id in all_ids if id]))
final_agv_id = ",".join(unique_ids)
logger.info(f"合并后的机器人IDs: {final_agv_id}")
else:
final_agv_id = amr_id
# 更新记录
stmt = update(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id).values(agv_id=final_agv_id)
await session.execute(stmt)
await session.commit()
logger.info(f"成功更新任务记录 {task_record_id} 的agv_id字段: {final_agv_id}")
except Exception as e:
logger.error(f"更新任务记录 {task_record_id} 的agv_id字段时发生错误: {str(e)}")
# 机器人通用动作处理器
@register_handler(RobotBlockName.AGV_OPERATION)
class AgvOperationBlockHandler(RobotBlockHandler):
"""机器人通用动作处理器"""
async def execute(
self,
block: Dict[str, Any],
input_params: Dict[str, Any],
context: TaskContext
) -> Dict[str, Any]:
"""执行机器人通用动作操作"""
from services.sync_service import wait_for_task_block_action_completion
try:
# 获取关键参数用于验证
target_site_label = input_params.get("targetSiteLabel")
script_name = input_params.get("scriptName")
# 参数检查
if not target_site_label:
result = {
"success": False,
"message": "目标站点名不能为空"
}
await self._record_task_log(block, result, context)
return result
# 获取当前块信息
current_block_id = block.get("id", "unknown")
current_block_name = block.get("name", f"b{current_block_id}")
# 如果没有提供尝试获取适用于当前块的机器人ID
vehicle, agv_task_id = self._get_robot_id_for_block(current_block_id, current_block_name, context)
if vehicle:
# 设置到输入参数中
input_params["vehicle"] = vehicle
# 记录使用的机器人ID
if vehicle:
logger.info(f"执行机器人通用动作,块 {current_block_name}(ID:{current_block_id}) 使用机器人: {vehicle}, 目标站点: {target_site_label}")
else:
error_msg = f"执行机器人通用动作失败未指定机器人ID目标站点: {target_site_label}"
logger.error(error_msg)
result = {
"success": False,
"message": error_msg
}
await self._record_task_log(block, result, context)
return result
from services.sync_service import add_action
result = await add_action(
task_id=agv_task_id,
station_name=target_site_label,
action=script_name,
token=context.token
)
# 调用外部API执行机器人通用动作
if result.get("success", False):
# 获取任务ID
task_id = result.get("result", {}).get("id", "")
task_block_result = await wait_for_task_block_action_completion(task_id, context.token)
if task_block_result.get("success", False):
task_block_status = task_block_result.get("result", {}).get("status", "")
if task_block_status == 3:
result["message"] = f"机器人通用动作成功,目标站点: {target_site_label}"
elif task_block_status == 4:
result["message"] = f"机器人通用动作失败,目标站点: {target_site_label}:{task_block_result.get('message', '')}"
result["success"] = False
elif task_block_status == 5:
result["message"] = f"机器人通用动作终止,目标站点: {target_site_label}"
else:
result["message"] = f"机器人通用动作失败: {result.get('message', '未知错误')}"
# 记录执行结果
await self._record_task_log(block, result, context)
return result
except Exception as e:
result = {
"success": False,
"message": f"机器人通用动作执行异常: {str(e)}"
}
# 记录异常
await self._record_task_log(block, result, context)
return result
# 获取机器人位置处理器
@register_handler(RobotBlockName.VEHICLE_STATION)
class VehicleStationBlockHandler(RobotBlockHandler):
"""获取机器人位置处理器"""
async def execute(
self,
block: Dict[str, Any],
input_params: Dict[str, Any],
context: TaskContext
) -> Dict[str, Any]:
"""执行获取机器人位置操作"""
try:
# 获取关键参数用于验证
vehicle = input_params.get("vehicle")
# 参数检查
if not vehicle:
result = {
"success": False,
"message": "指定机器人不能为空"
}
await self._record_task_log(block, result, context)
return result
# 调用外部API获取机器人位置
result = await self._call_external_api("vehicle_station", input_params)
if result.get("success", False):
# 获取站点信息
station = result.get("data", {}).get("station", "")
last_station = result.get("data", {}).get("lastStation", "")
# 设置上下文变量
context.set_variable("station", station)
context.set_variable("lastStation", last_station)
context.set_block_output(block.get("name"), {
"station": station,
"lastStation": last_station
})
result["message"] = f"获取机器人 {vehicle} 位置成功,当前位置: {station}, 上次位置: {last_station}"
else:
result["message"] = f"获取机器人位置失败: {result.get('message', '未知错误')}"
# 记录执行结果
await self._record_task_log(block, result, context)
return result
except Exception as e:
result = {
"success": False,
"message": f"获取机器人位置执行异常: {str(e)}"
}
# 记录异常
await self._record_task_log(block, result, context)
return result
# 获取机器人电量处理器
@register_handler(RobotBlockName.GET_BATTERY_LEVEL)
class GetBatteryLevelBlockHandler(RobotBlockHandler):
"""获取机器人电量处理器"""
async def execute(
self,
block: Dict[str, Any],
input_params: Dict[str, Any],
context: TaskContext
) -> Dict[str, Any]:
"""执行获取机器人电量操作"""
try:
# 获取关键参数用于验证
vehicle = input_params.get("vehicle")
# 参数检查
if not vehicle:
result = {
"success": False,
"message": "机器人ID不能为空"
}
await self._record_task_log(block, result, context)
return result
# 调用外部API获取机器人电量
result = await self._call_external_api("get_battery_level", input_params)
if result.get("success", False):
# 获取电量信息
battery_level = result.get("data", {}).get("batteryLevel", 0.0)
# 设置上下文变量
context.set_variable("batteryLevel", battery_level)
context.set_block_output(block.get("name"), {"batteryLevel": battery_level})
# 格式化电量为百分比
battery_percent = f"{battery_level * 100:.1f}%"
result["message"] = f"获取机器人 {vehicle} 电量成功,当前电量: {battery_percent}"
else:
result["message"] = f"获取机器人电量失败: {result.get('message', '未知错误')}"
# 记录执行结果
await self._record_task_log(block, result, context)
return result
except Exception as e:
result = {
"success": False,
"message": f"获取机器人电量执行异常: {str(e)}"
}
# 记录异常
await self._record_task_log(block, result, context)
return result
# 获取机器人PGV码处理器
@register_handler(RobotBlockName.GET_PGV_CODE)
class GetPGVCodeBlockHandler(RobotBlockHandler):
"""获取机器人PGV码处理器"""
async def execute(
self,
block: Dict[str, Any],
input_params: Dict[str, Any],
context: TaskContext
) -> Dict[str, Any]:
"""执行获取机器人PGV码操作"""
try:
# 获取关键参数用于验证
vehicle = input_params.get("vehicle")
# 参数检查
if not vehicle:
result = {
"success": False,
"message": "机器人ID不能为空"
}
await self._record_task_log(block, result, context)
return result
# 调用外部API获取机器人PGV码
result = await self._call_external_api("get_pgv_code", input_params)
if result.get("success", False):
# 获取PGV码信息
code_info = result.get("data", {}).get("codeInfo", False)
# 设置上下文变量
context.set_variable("codeInfo", code_info)
context.set_block_output(block.get("name"), {"codeInfo": code_info})
code_status = "有效" if code_info else "无效"
result["message"] = f"获取机器人 {vehicle} PGV码成功二维码信息: {code_status}"
else:
result["message"] = f"获取机器人PGV码失败: {result.get('message', '未知错误')}"
# 记录执行结果
await self._record_task_log(block, result, context)
return result
except Exception as e:
result = {
"success": False,
"message": f"获取机器人PGV码执行异常: {str(e)}"
}
# 记录异常
await self._record_task_log(block, result, context)
return result
# 选择执行机器人处理器
@register_handler(RobotBlockName.SELECT_AGV)
class SelectAgvBlockHandler(RobotBlockHandler):
"""选择执行机器人处理器"""
async def execute(
self,
block: Dict[str, Any],
input_params: Dict[str, Any],
context: TaskContext
) -> Dict[str, Any]:
"""执行选择机器人操作"""
try:
# 获取关键参数用于验证
key_route = input_params.get("keyRoute")
priority = input_params.get("priority", 1)
if isinstance(priority, str):
priority = int(priority)
# 参数检查
if not key_route:
result = {
"success": False,
"message": "关键路径不能为空"
}
await self._record_task_log(block, result, context)
return result
# 调用外部API选择执行机器人
# result = await self._call_external_api("select_agv", input_params)
from services.sync_service import create_choose_amr_task, wait_for_amr_selection
result = await create_choose_amr_task(
task_id=context.task_record_id,
key_station_name=key_route,
amr_name=input_params.get("vehicle", ""),
amr_group_name=input_params.get("group", ""),
token=context.token,
priority=priority
)
if result.get("success", False):
# 获取任务块ID
task_block_id = result.get("result", {}).get("id", "")
if not task_block_id:
result = {
"success": False,
"message": "创建选择AMR任务成功但未返回任务块ID"
}
await self._record_task_log(block, result, context)
return result
logger.info(f"开始等待任务块 {task_block_id} 的AMR选择结果")
# 等待AMR选择完成
task_block_result = await wait_for_amr_selection(
task_block_id=task_block_id,
token=context.token
)
if not task_block_result or not task_block_result.get("success", False):
result = {
"success": False,
"message": f"等待AMR选择结果失败或超时任务块ID: {task_block_id}"
}
await self._record_task_log(block, result, context)
return result
# 获取选出的机器人ID
agv_id = task_block_result.get("result", {}).get("amrId", "")
if not agv_id:
result = {
"success": False,
"message": f"未能获取到选择的AMR ID任务块ID: {task_block_id}"
}
await self._record_task_log(block, result, context)
return result
# 获取当前块ID和名称
current_block_id = block.get("id", "unknown")
current_block_name = block.get("name", f"b{current_block_id}")
# 更新任务记录中的agv_id字段
await self._update_task_record_agv_id(context.task_record_id, agv_id)
logger.info(f"选择机器人块 {current_block_name}(ID:{current_block_id}) 选择的机器人: {agv_id}")
# 分析块的层级结构并记录关联关系
affected_blocks = self._analyze_affected_blocks(block, current_block_id, current_block_name)
# 将分析结果记录到日志
logger.info(f"选择机器人块 {current_block_name} 影响的块ID: {[b['id'] for b in affected_blocks]}")
context.set_block_output(current_block_name, {"selectedAgvId": agv_id})
# 为每个受影响的块设置变量记录它应该使用的机器人ID
for affected_block in affected_blocks:
affected_id = affected_block["id"]
affected_name = affected_block["name"]
context.set_variable(f"agv_for_block_{affected_id}", agv_id)
context.set_variable(f"agv_for_{affected_name}", agv_id)
context.set_variable(f"agv_task_id_{affected_id}", task_block_id)
context.set_block_output(affected_name, {"selectedAgvId": agv_id})
# 构造成功消息
vehicle = input_params.get("vehicle", "")
group = input_params.get("group", "")
tag = input_params.get("tag", "")
if vehicle:
result["message"] = f"指定机器人 {vehicle} 选择成功"
elif group:
result["message"] = f"从机器人组 {group} 选择机器人成功: {agv_id}"
elif tag:
result["message"] = f"根据标签 {tag} 选择机器人成功: {agv_id}"
else:
result["message"] = f"选择执行机器人成功: {agv_id}"
# 打印选择结果和影响的块
logger.info(f"选择机器人块 {current_block_name}(ID:{current_block_id}) 选择的机器人: {agv_id}")
logger.info(f"影响的块: {len(affected_blocks)}")
for i, b in enumerate(affected_blocks):
logger.info(f"{i+1}. {b['name']}(ID:{b['id']}, 类型:{b['type']}, 关系:{b['relation']})")
from services.execution.block_executor import BlockExecutor
executor = BlockExecutor(context)
# 检查是否有子块需要执行
has_children = "children" in block and "default" in block.get("children", {}) and len(block.get("children", {}).get("default", [])) > 0
if has_children:
# 执行子块
logger.info(f"开始执行选择机器人块 {current_block_name} 的子块")
loop_result = await executor.execute_children(block, "default")
# 处理子块执行结果
if loop_result.get("success", False):
# 子块执行成功,合并结果
logger.info(f"选择机器人块 {current_block_name} 的子块执行成功")
# 如果有需要合并的输出数据
child_results = loop_result.get("output", {}).get("results", [])
result["childBlockResults"] = child_results
# 保持成功状态
result["success"] = True
result["output"] = {
"selectedAgvId": agv_id,
"affectedBlocks": affected_blocks,
"childrenExecuted": True,
"childrenResult": loop_result
}
# 如果原始消息中没有包含子块执行信息,添加这部分信息
if "子块" not in result["message"]:
result["message"] = f"{result['message']},子块执行成功"
else:
# 子块执行失败,根据失败的子块更新消息
logger.error(f"选择机器人块 {current_block_name} 的子块执行失败: {loop_result.get('message')}")
# 创建包含子块失败信息的结果
error_msg = loop_result.get("message", "未知错误")
failed_block_id = loop_result.get("block_id", "unknown")
result = {
"success": False,
"message": f"选择执行机器人成功,但子块执行失败: {error_msg}失败块ID: {failed_block_id}",
"output": {
"selectedAgvId": agv_id,
"affectedBlocks": affected_blocks,
"childrenExecuted": False,
"childrenResult": loop_result
}
}
# 记录执行结果
from services.sync_service import closure_task
closure_result = await closure_task(
task_id=task_block_id,
token=context.token
)
else:
result["message"] = f"选择执行机器人失败: {result.get('message', '未知错误')}"
await self._record_task_log(block, result, context)
return result
except Exception as e:
result = {
"success": False,
"message": f"选择执行机器人异常: {str(e)}"
}
# 记录异常
await self._record_task_log(block, result, context)
return result