mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
650 lines
26 KiB
Python
650 lines
26 KiB
Python
"""
|
||
工作流执行器模块
|
||
Workflow Executors Module
|
||
|
||
基于单一硬件接口的工作流执行器实现
|
||
支持Modbus、HTTP、PyLabRobot和代理模式
|
||
"""
|
||
import time
|
||
import json
|
||
import asyncio
|
||
from typing import Dict, Any, List, Optional, TYPE_CHECKING
|
||
from abc import ABC, abstractmethod
|
||
|
||
if TYPE_CHECKING:
|
||
from unilabos.devices.work_station.workstation_base import WorkstationBase
|
||
|
||
from unilabos.utils.log import logger
|
||
|
||
|
||
class WorkflowExecutor(ABC):
|
||
"""工作流执行器基类 - 基于单一硬件接口"""
|
||
|
||
def __init__(self, workstation: 'WorkstationBase'):
|
||
self.workstation = workstation
|
||
self.hardware_interface = workstation.hardware_interface
|
||
self.material_management = workstation.material_management
|
||
|
||
@abstractmethod
|
||
def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||
"""执行工作流"""
|
||
pass
|
||
|
||
@abstractmethod
|
||
def stop_workflow(self, emergency: bool = False) -> bool:
|
||
"""停止工作流"""
|
||
pass
|
||
|
||
def call_device(self, method: str, *args, **kwargs) -> Any:
|
||
"""调用设备方法的统一接口"""
|
||
return self.workstation.call_device_method(method, *args, **kwargs)
|
||
|
||
def get_device_status(self) -> Dict[str, Any]:
|
||
"""获取设备状态"""
|
||
return self.workstation.get_device_status()
|
||
|
||
|
||
class ModbusWorkflowExecutor(WorkflowExecutor):
|
||
"""Modbus工作流执行器 - 适配 coin_cell_assembly_system"""
|
||
|
||
def __init__(self, workstation: 'WorkstationBase'):
|
||
super().__init__(workstation)
|
||
|
||
# 验证Modbus接口
|
||
if not (hasattr(self.hardware_interface, 'write_register') and
|
||
hasattr(self.hardware_interface, 'read_register')):
|
||
raise RuntimeError("工作站硬件接口不是有效的Modbus客户端")
|
||
|
||
def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||
"""执行Modbus工作流"""
|
||
if workflow_name == "battery_manufacturing":
|
||
return self._execute_battery_manufacturing(parameters)
|
||
elif workflow_name == "material_loading":
|
||
return self._execute_material_loading(parameters)
|
||
elif workflow_name == "quality_check":
|
||
return self._execute_quality_check(parameters)
|
||
else:
|
||
logger.warning(f"不支持的Modbus工作流: {workflow_name}")
|
||
return False
|
||
|
||
def _execute_battery_manufacturing(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行电池制造工作流"""
|
||
try:
|
||
# 1. 物料准备检查
|
||
available_slot = self._find_available_press_slot()
|
||
if not available_slot:
|
||
raise RuntimeError("没有可用的压制槽")
|
||
|
||
logger.info(f"找到可用压制槽: {available_slot}")
|
||
|
||
# 2. 设置工艺参数(直接调用Modbus接口)
|
||
if "electrolyte_num" in parameters:
|
||
self.hardware_interface.write_register('REG_MSG_ELECTROLYTE_NUM', parameters["electrolyte_num"])
|
||
logger.info(f"设置电解液编号: {parameters['electrolyte_num']}")
|
||
|
||
if "electrolyte_volume" in parameters:
|
||
self.hardware_interface.write_register('REG_MSG_ELECTROLYTE_VOLUME',
|
||
parameters["electrolyte_volume"],
|
||
data_type="FLOAT32")
|
||
logger.info(f"设置电解液体积: {parameters['electrolyte_volume']}")
|
||
|
||
if "assembly_pressure" in parameters:
|
||
self.hardware_interface.write_register('REG_MSG_ASSEMBLY_PRESSURE',
|
||
parameters["assembly_pressure"],
|
||
data_type="FLOAT32")
|
||
logger.info(f"设置装配压力: {parameters['assembly_pressure']}")
|
||
|
||
# 3. 启动制造流程
|
||
self.hardware_interface.write_register('COIL_SYS_START_CMD', True)
|
||
logger.info("启动电池制造流程")
|
||
|
||
# 4. 确认启动成功
|
||
time.sleep(0.5)
|
||
status = self.hardware_interface.read_register('COIL_SYS_START_STATUS', count=1)
|
||
success = status[0] if status else False
|
||
|
||
if success:
|
||
logger.info(f"电池制造工作流启动成功,参数: {parameters}")
|
||
else:
|
||
logger.error("电池制造工作流启动失败")
|
||
|
||
return success
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行电池制造工作流失败: {e}")
|
||
return False
|
||
|
||
def _execute_material_loading(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行物料装载工作流"""
|
||
try:
|
||
material_type = parameters.get('material_type', 'cathode')
|
||
position = parameters.get('position', 'A1')
|
||
|
||
logger.info(f"开始物料装载: {material_type} -> {position}")
|
||
|
||
# 设置物料类型和位置
|
||
self.hardware_interface.write_register('REG_MATERIAL_TYPE', material_type)
|
||
self.hardware_interface.write_register('REG_MATERIAL_POSITION', position)
|
||
|
||
# 启动装载
|
||
self.hardware_interface.write_register('COIL_LOAD_START', True)
|
||
|
||
# 等待装载完成
|
||
timeout = parameters.get('timeout', 30)
|
||
start_time = time.time()
|
||
|
||
while time.time() - start_time < timeout:
|
||
status = self.hardware_interface.read_register('COIL_LOAD_COMPLETE', count=1)
|
||
if status and status[0]:
|
||
logger.info(f"物料装载完成: {material_type} -> {position}")
|
||
return True
|
||
time.sleep(0.5)
|
||
|
||
logger.error(f"物料装载超时: {material_type} -> {position}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行物料装载失败: {e}")
|
||
return False
|
||
|
||
def _execute_quality_check(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行质量检测工作流"""
|
||
try:
|
||
check_type = parameters.get('check_type', 'dimensional')
|
||
|
||
logger.info(f"开始质量检测: {check_type}")
|
||
|
||
# 启动质量检测
|
||
self.hardware_interface.write_register('REG_QC_TYPE', check_type)
|
||
self.hardware_interface.write_register('COIL_QC_START', True)
|
||
|
||
# 等待检测完成
|
||
timeout = parameters.get('timeout', 60)
|
||
start_time = time.time()
|
||
|
||
while time.time() - start_time < timeout:
|
||
status = self.hardware_interface.read_register('COIL_QC_COMPLETE', count=1)
|
||
if status and status[0]:
|
||
# 读取检测结果
|
||
result = self.hardware_interface.read_register('REG_QC_RESULT', count=1)
|
||
passed = result[0] if result else False
|
||
|
||
if passed:
|
||
logger.info(f"质量检测通过: {check_type}")
|
||
return True
|
||
else:
|
||
logger.warning(f"质量检测失败: {check_type}")
|
||
return False
|
||
|
||
time.sleep(1.0)
|
||
|
||
logger.error(f"质量检测超时: {check_type}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行质量检测失败: {e}")
|
||
return False
|
||
|
||
def _find_available_press_slot(self) -> Optional[str]:
|
||
"""查找可用压制槽"""
|
||
try:
|
||
press_slots = self.material_management.find_by_category("battery_press_slot")
|
||
for slot in press_slots:
|
||
if hasattr(slot, 'has_battery') and not slot.has_battery():
|
||
return slot.name
|
||
return None
|
||
except:
|
||
# 如果物料管理系统不可用,返回默认槽位
|
||
return "A1"
|
||
|
||
def stop_workflow(self, emergency: bool = False) -> bool:
|
||
"""停止工作流"""
|
||
try:
|
||
if emergency:
|
||
self.hardware_interface.write_register('COIL_SYS_RESET_CMD', True)
|
||
logger.warning("执行紧急停止")
|
||
else:
|
||
self.hardware_interface.write_register('COIL_SYS_STOP_CMD', True)
|
||
logger.info("执行正常停止")
|
||
|
||
time.sleep(0.5)
|
||
status = self.hardware_interface.read_register('COIL_SYS_STOP_STATUS', count=1)
|
||
return status[0] if status else False
|
||
|
||
except Exception as e:
|
||
logger.error(f"停止Modbus工作流失败: {e}")
|
||
return False
|
||
|
||
|
||
class HttpWorkflowExecutor(WorkflowExecutor):
|
||
"""HTTP工作流执行器 - 适配 reaction_station_bioyong"""
|
||
|
||
def __init__(self, workstation: 'WorkstationBase'):
|
||
super().__init__(workstation)
|
||
|
||
# 验证HTTP接口
|
||
if not (hasattr(self.hardware_interface, 'post') or
|
||
hasattr(self.hardware_interface, 'get')):
|
||
raise RuntimeError("工作站硬件接口不是有效的HTTP客户端")
|
||
|
||
def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||
"""执行HTTP工作流"""
|
||
try:
|
||
if workflow_name == "reaction_synthesis":
|
||
return self._execute_reaction_synthesis(parameters)
|
||
elif workflow_name == "liquid_feeding":
|
||
return self._execute_liquid_feeding(parameters)
|
||
elif workflow_name == "temperature_control":
|
||
return self._execute_temperature_control(parameters)
|
||
else:
|
||
logger.warning(f"不支持的HTTP工作流: {workflow_name}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行HTTP工作流失败: {e}")
|
||
return False
|
||
|
||
def _execute_reaction_synthesis(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行反应合成工作流"""
|
||
try:
|
||
# 1. 设置工作流序列
|
||
sequence = self._build_reaction_sequence(parameters)
|
||
self._call_rpc_method('set_workflow_sequence', json.dumps(sequence))
|
||
|
||
# 2. 设置反应参数
|
||
if parameters.get('temperature'):
|
||
self._call_rpc_method('set_temperature', parameters['temperature'])
|
||
|
||
if parameters.get('pressure'):
|
||
self._call_rpc_method('set_pressure', parameters['pressure'])
|
||
|
||
if parameters.get('stirring_speed'):
|
||
self._call_rpc_method('set_stirring_speed', parameters['stirring_speed'])
|
||
|
||
# 3. 执行工作流
|
||
result = self._call_rpc_method('execute_current_sequence', {
|
||
"task_name": "reaction_synthesis"
|
||
})
|
||
|
||
success = result.get('success', False)
|
||
if success:
|
||
logger.info("反应合成工作流执行成功")
|
||
else:
|
||
logger.error(f"反应合成工作流执行失败: {result.get('error', '未知错误')}")
|
||
|
||
return success
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行反应合成工作流失败: {e}")
|
||
return False
|
||
|
||
def _execute_liquid_feeding(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行液体投料工作流"""
|
||
try:
|
||
reagents = parameters.get('reagents', [])
|
||
volumes = parameters.get('volumes', [])
|
||
|
||
if len(reagents) != len(volumes):
|
||
raise ValueError("试剂列表和体积列表长度不匹配")
|
||
|
||
# 执行投料序列
|
||
for reagent, volume in zip(reagents, volumes):
|
||
result = self._call_rpc_method('feed_liquid', {
|
||
'reagent': reagent,
|
||
'volume': volume
|
||
})
|
||
|
||
if not result.get('success', False):
|
||
logger.error(f"投料失败: {reagent} {volume}mL")
|
||
return False
|
||
|
||
logger.info(f"投料成功: {reagent} {volume}mL")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行液体投料失败: {e}")
|
||
return False
|
||
|
||
def _execute_temperature_control(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行温度控制工作流"""
|
||
try:
|
||
target_temp = parameters.get('temperature', 25)
|
||
hold_time = parameters.get('hold_time', 300) # 秒
|
||
|
||
# 设置目标温度
|
||
result = self._call_rpc_method('set_temperature', target_temp)
|
||
if not result.get('success', False):
|
||
logger.error(f"设置温度失败: {target_temp}°C")
|
||
return False
|
||
|
||
# 等待温度稳定
|
||
logger.info(f"等待温度稳定到 {target_temp}°C")
|
||
|
||
# 保持温度指定时间
|
||
if hold_time > 0:
|
||
logger.info(f"保持温度 {hold_time} 秒")
|
||
time.sleep(hold_time)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行温度控制失败: {e}")
|
||
return False
|
||
|
||
def _build_reaction_sequence(self, parameters: Dict[str, Any]) -> List[str]:
|
||
"""构建反应合成工作流序列"""
|
||
sequence = []
|
||
|
||
# 添加预处理步骤
|
||
if parameters.get('purge_with_inert'):
|
||
sequence.append("purge_inert_gas")
|
||
|
||
# 添加温度设置
|
||
if parameters.get('temperature'):
|
||
sequence.append(f"set_temperature_{parameters['temperature']}")
|
||
|
||
# 添加压力设置
|
||
if parameters.get('pressure'):
|
||
sequence.append(f"set_pressure_{parameters['pressure']}")
|
||
|
||
# 添加搅拌设置
|
||
if parameters.get('stirring_speed'):
|
||
sequence.append(f"set_stirring_{parameters['stirring_speed']}")
|
||
|
||
# 添加反应步骤
|
||
sequence.extend([
|
||
"start_reaction",
|
||
"monitor_progress",
|
||
"complete_reaction"
|
||
])
|
||
|
||
# 添加后处理步骤
|
||
if parameters.get('cooling_required'):
|
||
sequence.append("cool_down")
|
||
|
||
return sequence
|
||
|
||
def _call_rpc_method(self, method: str, params: Any = None) -> Dict[str, Any]:
|
||
"""调用RPC方法"""
|
||
try:
|
||
if hasattr(self.hardware_interface, method):
|
||
# 直接方法调用
|
||
if isinstance(params, dict):
|
||
params = json.dumps(params)
|
||
elif params is None:
|
||
params = ""
|
||
return getattr(self.hardware_interface, method)(params)
|
||
else:
|
||
# HTTP请求调用
|
||
if hasattr(self.hardware_interface, 'post'):
|
||
response = self.hardware_interface.post(f"/api/{method}", json=params)
|
||
return response.json()
|
||
else:
|
||
raise AttributeError(f"HTTP接口不支持方法: {method}")
|
||
except Exception as e:
|
||
logger.error(f"调用RPC方法失败 {method}: {e}")
|
||
return {'success': False, 'error': str(e)}
|
||
|
||
def stop_workflow(self, emergency: bool = False) -> bool:
|
||
"""停止工作流"""
|
||
try:
|
||
if emergency:
|
||
result = self._call_rpc_method('scheduler_reset')
|
||
else:
|
||
result = self._call_rpc_method('scheduler_stop')
|
||
|
||
return result.get('success', False)
|
||
|
||
except Exception as e:
|
||
logger.error(f"停止HTTP工作流失败: {e}")
|
||
return False
|
||
|
||
|
||
class PyLabRobotWorkflowExecutor(WorkflowExecutor):
|
||
"""PyLabRobot工作流执行器 - 适配 prcxi.py"""
|
||
|
||
def __init__(self, workstation: 'WorkstationBase'):
|
||
super().__init__(workstation)
|
||
|
||
# 验证PyLabRobot接口
|
||
if not (hasattr(self.hardware_interface, 'transfer_liquid') or
|
||
hasattr(self.hardware_interface, 'pickup_tips')):
|
||
raise RuntimeError("工作站硬件接口不是有效的PyLabRobot设备")
|
||
|
||
def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||
"""执行PyLabRobot工作流"""
|
||
try:
|
||
if workflow_name == "liquid_transfer":
|
||
return self._execute_liquid_transfer(parameters)
|
||
elif workflow_name == "tip_pickup_drop":
|
||
return self._execute_tip_operations(parameters)
|
||
elif workflow_name == "plate_handling":
|
||
return self._execute_plate_handling(parameters)
|
||
else:
|
||
logger.warning(f"不支持的PyLabRobot工作流: {workflow_name}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行PyLabRobot工作流失败: {e}")
|
||
return False
|
||
|
||
def _execute_liquid_transfer(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行液体转移工作流"""
|
||
try:
|
||
# 1. 解析物料引用
|
||
sources = self._resolve_containers(parameters.get('sources', []))
|
||
targets = self._resolve_containers(parameters.get('targets', []))
|
||
tip_racks = self._resolve_tip_racks(parameters.get('tip_racks', []))
|
||
|
||
if not sources or not targets:
|
||
raise ValueError("液体转移需要指定源容器和目标容器")
|
||
|
||
if not tip_racks:
|
||
logger.warning("未指定枪头架,将尝试自动查找")
|
||
tip_racks = self._find_available_tip_racks()
|
||
|
||
# 2. 执行液体转移
|
||
volumes = parameters.get('volumes', [])
|
||
if not volumes:
|
||
volumes = [100.0] * len(sources) # 默认体积
|
||
|
||
# 如果是同步接口
|
||
if hasattr(self.hardware_interface, 'transfer_liquid'):
|
||
result = self.hardware_interface.transfer_liquid(
|
||
sources=sources,
|
||
targets=targets,
|
||
tip_racks=tip_racks,
|
||
asp_vols=volumes,
|
||
dis_vols=volumes,
|
||
**parameters.get('options', {})
|
||
)
|
||
else:
|
||
# 异步接口需要特殊处理
|
||
asyncio.run(self._async_liquid_transfer(sources, targets, tip_racks, volumes, parameters))
|
||
result = True
|
||
|
||
if result:
|
||
logger.info(f"液体转移工作流完成: {len(sources)}个源 -> {len(targets)}个目标")
|
||
|
||
return bool(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行液体转移失败: {e}")
|
||
return False
|
||
|
||
async def _async_liquid_transfer(self, sources, targets, tip_racks, volumes, parameters):
|
||
"""异步液体转移"""
|
||
await self.hardware_interface.transfer_liquid(
|
||
sources=sources,
|
||
targets=targets,
|
||
tip_racks=tip_racks,
|
||
asp_vols=volumes,
|
||
dis_vols=volumes,
|
||
**parameters.get('options', {})
|
||
)
|
||
|
||
def _execute_tip_operations(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行枪头操作工作流"""
|
||
try:
|
||
operation = parameters.get('operation', 'pickup')
|
||
tip_racks = self._resolve_tip_racks(parameters.get('tip_racks', []))
|
||
|
||
if not tip_racks:
|
||
raise ValueError("枪头操作需要指定枪头架")
|
||
|
||
if operation == 'pickup':
|
||
result = self.hardware_interface.pickup_tips(tip_racks[0])
|
||
logger.info("枪头拾取完成")
|
||
elif operation == 'drop':
|
||
result = self.hardware_interface.drop_tips()
|
||
logger.info("枪头丢弃完成")
|
||
else:
|
||
raise ValueError(f"不支持的枪头操作: {operation}")
|
||
|
||
return bool(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行枪头操作失败: {e}")
|
||
return False
|
||
|
||
def _execute_plate_handling(self, parameters: Dict[str, Any]) -> bool:
|
||
"""执行板类处理工作流"""
|
||
try:
|
||
operation = parameters.get('operation', 'move')
|
||
source_position = parameters.get('source_position')
|
||
target_position = parameters.get('target_position')
|
||
|
||
if operation == 'move' and source_position and target_position:
|
||
# 移动板类
|
||
result = self.hardware_interface.move_plate(source_position, target_position)
|
||
logger.info(f"板类移动完成: {source_position} -> {target_position}")
|
||
else:
|
||
logger.warning(f"不支持的板类操作或参数不完整: {operation}")
|
||
return False
|
||
|
||
return bool(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行板类处理失败: {e}")
|
||
return False
|
||
|
||
def _resolve_containers(self, container_names: List[str]):
|
||
"""解析容器名称为实际容器对象"""
|
||
containers = []
|
||
for name in container_names:
|
||
try:
|
||
container = self.material_management.find_material_by_id(name)
|
||
if container:
|
||
containers.append(container)
|
||
else:
|
||
logger.warning(f"未找到容器: {name}")
|
||
except:
|
||
logger.warning(f"解析容器失败: {name}")
|
||
return containers
|
||
|
||
def _resolve_tip_racks(self, tip_rack_names: List[str]):
|
||
"""解析枪头架名称为实际对象"""
|
||
tip_racks = []
|
||
for name in tip_rack_names:
|
||
try:
|
||
tip_rack = self.material_management.find_by_category("tip_rack")
|
||
matching_racks = [rack for rack in tip_rack if rack.name == name]
|
||
if matching_racks:
|
||
tip_racks.extend(matching_racks)
|
||
else:
|
||
logger.warning(f"未找到枪头架: {name}")
|
||
except:
|
||
logger.warning(f"解析枪头架失败: {name}")
|
||
return tip_racks
|
||
|
||
def _find_available_tip_racks(self):
|
||
"""查找可用的枪头架"""
|
||
try:
|
||
tip_racks = self.material_management.find_by_category("tip_rack")
|
||
available_racks = [rack for rack in tip_racks if hasattr(rack, 'has_tips') and rack.has_tips()]
|
||
return available_racks[:1] # 返回第一个可用的枪头架
|
||
except:
|
||
return []
|
||
|
||
def stop_workflow(self, emergency: bool = False) -> bool:
|
||
"""停止工作流"""
|
||
try:
|
||
if emergency:
|
||
if hasattr(self.hardware_interface, 'emergency_stop'):
|
||
return self.hardware_interface.emergency_stop()
|
||
else:
|
||
logger.warning("设备不支持紧急停止")
|
||
return False
|
||
else:
|
||
if hasattr(self.hardware_interface, 'graceful_stop'):
|
||
return self.hardware_interface.graceful_stop()
|
||
elif hasattr(self.hardware_interface, 'stop'):
|
||
return self.hardware_interface.stop()
|
||
else:
|
||
logger.warning("设备不支持优雅停止")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"停止PyLabRobot工作流失败: {e}")
|
||
return False
|
||
|
||
|
||
class ProxyWorkflowExecutor(WorkflowExecutor):
|
||
"""代理工作流执行器 - 处理代理模式的工作流"""
|
||
|
||
def __init__(self, workstation: 'WorkstationBase'):
|
||
super().__init__(workstation)
|
||
|
||
# 验证代理接口
|
||
if not isinstance(self.hardware_interface, str) or not self.hardware_interface.startswith("proxy:"):
|
||
raise RuntimeError("工作站硬件接口不是有效的代理字符串")
|
||
|
||
self.device_id = self.hardware_interface[6:] # 移除 "proxy:" 前缀
|
||
|
||
def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||
"""执行代理工作流"""
|
||
try:
|
||
# 通过协议节点调用目标设备的工作流
|
||
if self.workstation._protocol_node:
|
||
return self.workstation._protocol_node.call_device_method(
|
||
self.device_id, 'execute_workflow', workflow_name, parameters
|
||
)
|
||
else:
|
||
logger.error("代理模式需要protocol_node")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行代理工作流失败: {e}")
|
||
return False
|
||
|
||
def stop_workflow(self, emergency: bool = False) -> bool:
|
||
"""停止代理工作流"""
|
||
try:
|
||
if self.workstation._protocol_node:
|
||
return self.workstation._protocol_node.call_device_method(
|
||
self.device_id, 'stop_workflow', emergency
|
||
)
|
||
else:
|
||
logger.error("代理模式需要protocol_node")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"停止代理工作流失败: {e}")
|
||
return False
|
||
|
||
|
||
# 辅助函数
|
||
def get_executor_for_interface(hardware_interface) -> str:
|
||
"""根据硬件接口类型获取执行器类型名称"""
|
||
if isinstance(hardware_interface, str) and hardware_interface.startswith("proxy:"):
|
||
return "ProxyWorkflowExecutor"
|
||
elif hasattr(hardware_interface, 'write_register') and hasattr(hardware_interface, 'read_register'):
|
||
return "ModbusWorkflowExecutor"
|
||
elif hasattr(hardware_interface, 'post') or hasattr(hardware_interface, 'get'):
|
||
return "HttpWorkflowExecutor"
|
||
elif hasattr(hardware_interface, 'transfer_liquid') or hasattr(hardware_interface, 'pickup_tips'):
|
||
return "PyLabRobotWorkflowExecutor"
|
||
else:
|
||
return "UnknownExecutor"
|