#!/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