mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-15 13:44:39 +00:00
* 更新Bioyond工作站配置,添加新的物料类型映射和载架定义,优化物料查询逻辑 * 添加Bioyond实验配置文件,定义物料类型映射和设备配置 * 更新bioyond_warehouse_reagent_stack方法,修正试剂堆栈尺寸和布局描述 * 更新Bioyond实验配置,修正物料类型映射,优化设备配置 * 更新Bioyond资源同步逻辑,优化物料入库流程,增强错误处理和日志记录 * 更新Bioyond资源,添加配液站和反应站专用载架,优化仓库工厂函数的排序方式 * 更新Bioyond资源,添加配液站和反应站相关载架,优化试剂瓶和样品瓶配置 * 更新Bioyond实验配置,修正试剂瓶载架ID,确保与设备匹配 * 更新Bioyond资源,移除反应站单烧杯载架,添加反应站单烧瓶载架分类 * Refactor Bioyond resource synchronization and update bottle carrier definitions - Removed traceback printing in error handling for Bioyond synchronization. - Enhanced logging for existing Bioyond material ID usage during synchronization. - Added new bottle carrier definitions for single flask and updated existing ones. - Refactored dispensing station and reaction station bottle definitions for clarity and consistency. - Improved resource mapping and error handling in graphio for Bioyond resource conversion. - Introduced layout parameter in warehouse factory for better warehouse configuration. * 更新Bioyond仓库工厂,添加排序方式支持,优化坐标计算逻辑 * 更新Bioyond载架和甲板配置,调整样品板尺寸和仓库坐标 * 更新Bioyond资源同步,增强占用位置日志信息,修正坐标转换逻辑 * 更新Bioyond反应站和分配站配置,调整材料类型映射和ID,移除不必要的项 * support name change during materials change * fix json dumps * correct tip * 优化调度器API路径,更新相关方法描述 * 更新 BIOYOND 载架相关文档,调整 API 以支持自带试剂瓶的载架类型,修复资源获取时的子物料处理逻辑 * 实现资源删除时的同步处理,优化出库操作逻辑 * 修复 ItemizedCarrier 中的可见性逻辑 * 保存 Bioyond 原始信息到 unilabos_extra,以便出库时查询 * 根据 resource.capacity 判断是试剂瓶(载架)还是多瓶载架,走不同的奔曜转换 * Fix bioyond bottle_carriers ordering * 优化 Bioyond 物料同步逻辑,增强坐标解析和位置更新处理 * disable slave connect websocket * correct remove_resource stats * change uuid logger to trace level * enable slave mode * refactor(bioyond): 统一资源命名并优化物料同步逻辑 - 将DispensingStation和ReactionStation资源统一为PolymerStation命名 - 优化物料同步逻辑,支持耗材类型(typeMode=0)的查询 - 添加物料默认参数配置功能 - 调整仓库坐标布局 - 清理废弃资源定义 * feat(warehouses): 为仓库函数添加col_offset和layout参数 * refactor: 更新实验配置中的物料类型映射命名 将DispensingStation和ReactionStation的物料类型映射统一更名为PolymerStation,保持命名一致性 * fix: 更新实验配置中的载体名称从6VialCarrier到6StockCarrier * feat(bioyond): 实现物料创建与入库分离逻辑 将物料同步流程拆分为两个独立阶段:transfer阶段只创建物料,add阶段执行入库 简化状态检查接口,仅返回连接状态 * fix(reaction_station): 修正液体进料烧杯体积单位并增强返回结果 将液体进料烧杯的体积单位从μL改为g以匹配实际使用场景 在返回结果中添加merged_workflow和order_params字段,提供更完整的工作流信息 * feat(dispensing_station): 在任务创建返回结果中添加order_params信息 在create_order方法返回结果中增加order_params字段,以便调用方获取完整的任务参数 * fix(dispensing_station): 修改90%物料分配逻辑从分成3份改为直接使用 原逻辑将主称固体平均分成3份作为90%物料,现改为直接使用main_portion * feat(bioyond): 添加任务编码和任务ID的输出,支持批量任务创建后的状态监控 * refactor(registry): 简化设备配置中的任务结果处理逻辑 将多个单独的任务编码和ID字段合并为统一的return_info字段 更新相关描述以反映新的数据结构 * feat(工作站): 添加HTTP报送服务和任务完成状态跟踪 - 在graphio.py中添加API必需字段 - 实现工作站HTTP服务启动和停止逻辑 - 添加任务完成状态跟踪字典和等待方法 - 重写任务完成报送处理方法记录状态 - 支持批量任务完成等待和报告获取 * refactor(dispensing_station): 移除wait_for_order_completion_and_get_report功能 该功能已被wait_for_multiple_orders_and_get_reports替代,简化代码结构 * fix: 更新任务报告API错误 * fix(workstation_http_service): 修复状态查询中device_id获取逻辑 处理状态查询时安全获取device_id,避免因属性不存在导致的异常 * fix(bioyond_studio): 改进物料入库失败时的错误处理和日志记录 在物料入库API调用失败时,添加更详细的错误信息打印 同时修正station.py中对空响应和失败情况的判断逻辑 * refactor(bioyond): 优化瓶架载体的分配逻辑和注释说明 重构瓶架载体的分配逻辑,使用嵌套循环替代硬编码索引分配 添加更详细的坐标映射说明,明确PLR与Bioyond坐标的对应关系 * fix(bioyond_rpc): 修复物料入库成功时无data字段返回空的问题 当API返回成功但无data字段时,返回包含success标识的字典而非空字典 --------- Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Co-authored-by: Junhan Chang <changjh@dp.tech>
1476 lines
62 KiB
Python
1476 lines
62 KiB
Python
from datetime import datetime
|
||
import json
|
||
import time
|
||
from typing import Optional, Dict, Any
|
||
|
||
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondException
|
||
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
|
||
|
||
|
||
class BioyondDispensingStation(BioyondWorkstation):
|
||
def __init__(
|
||
self,
|
||
config,
|
||
# 桌子
|
||
deck,
|
||
*args,
|
||
**kwargs,
|
||
):
|
||
super().__init__(config, deck, *args, **kwargs)
|
||
# self.config = config
|
||
# self.api_key = config["api_key"]
|
||
# self.host = config["api_host"]
|
||
#
|
||
# # 使用简单的Logger替代原来的logger
|
||
# self._logger = SimpleLogger()
|
||
# self.is_running = False
|
||
|
||
# 用于跟踪任务完成状态的字典: {orderCode: {status, order_id, timestamp}}
|
||
self.order_completion_status = {}
|
||
|
||
# 90%10%小瓶投料任务创建方法
|
||
def create_90_10_vial_feeding_task(self,
|
||
order_name: str = None,
|
||
speed: str = None,
|
||
temperature: str = None,
|
||
delay_time: str = None,
|
||
percent_90_1_assign_material_name: str = None,
|
||
percent_90_1_target_weigh: str = None,
|
||
percent_90_2_assign_material_name: str = None,
|
||
percent_90_2_target_weigh: str = None,
|
||
percent_90_3_assign_material_name: str = None,
|
||
percent_90_3_target_weigh: str = None,
|
||
percent_10_1_assign_material_name: str = None,
|
||
percent_10_1_target_weigh: str = None,
|
||
percent_10_1_volume: str = None,
|
||
percent_10_1_liquid_material_name: str = None,
|
||
percent_10_2_assign_material_name: str = None,
|
||
percent_10_2_target_weigh: str = None,
|
||
percent_10_2_volume: str = None,
|
||
percent_10_2_liquid_material_name: str = None,
|
||
percent_10_3_assign_material_name: str = None,
|
||
percent_10_3_target_weigh: str = None,
|
||
percent_10_3_volume: str = None,
|
||
percent_10_3_liquid_material_name: str = None,
|
||
hold_m_name: str = None) -> dict:
|
||
"""
|
||
创建90%10%小瓶投料任务
|
||
|
||
参数说明:
|
||
- order_name: 任务名称,如果为None则使用默认名称
|
||
- speed: 搅拌速度,如果为None则使用默认值400
|
||
- temperature: 温度,如果为None则使用默认值40
|
||
- delay_time: 延迟时间,如果为None则使用默认值600
|
||
- percent_90_1_assign_material_name: 90%_1物料名称
|
||
- percent_90_1_target_weigh: 90%_1目标重量
|
||
- percent_90_2_assign_material_name: 90%_2物料名称
|
||
- percent_90_2_target_weigh: 90%_2目标重量
|
||
- percent_90_3_assign_material_name: 90%_3物料名称
|
||
- percent_90_3_target_weigh: 90%_3目标重量
|
||
- percent_10_1_assign_material_name: 10%_1固体物料名称
|
||
- percent_10_1_target_weigh: 10%_1固体目标重量
|
||
- percent_10_1_volume: 10%_1液体体积
|
||
- percent_10_1_liquid_material_name: 10%_1液体物料名称
|
||
- percent_10_2_assign_material_name: 10%_2固体物料名称
|
||
- percent_10_2_target_weigh: 10%_2固体目标重量
|
||
- percent_10_2_volume: 10%_2液体体积
|
||
- percent_10_2_liquid_material_name: 10%_2液体物料名称
|
||
- percent_10_3_assign_material_name: 10%_3固体物料名称
|
||
- percent_10_3_target_weigh: 10%_3固体目标重量
|
||
- percent_10_3_volume: 10%_3液体体积
|
||
- percent_10_3_liquid_material_name: 10%_3液体物料名称
|
||
- hold_m_name: 库位名称,如"C01",用于查找对应的holdMId
|
||
|
||
返回: 任务创建结果
|
||
|
||
异常:
|
||
- BioyondException: 各种错误情况下的统一异常
|
||
"""
|
||
try:
|
||
# 1. 参数验证
|
||
if not hold_m_name:
|
||
raise BioyondException("hold_m_name 是必填参数")
|
||
|
||
# 检查90%物料参数的完整性
|
||
# 90%_1物料:如果有物料名称或目标重量,就必须有全部参数
|
||
if percent_90_1_assign_material_name or percent_90_1_target_weigh:
|
||
if not percent_90_1_assign_material_name:
|
||
raise BioyondException("90%_1物料:如果提供了目标重量,必须同时提供物料名称")
|
||
if not percent_90_1_target_weigh:
|
||
raise BioyondException("90%_1物料:如果提供了物料名称,必须同时提供目标重量")
|
||
|
||
# 90%_2物料:如果有物料名称或目标重量,就必须有全部参数
|
||
if percent_90_2_assign_material_name or percent_90_2_target_weigh:
|
||
if not percent_90_2_assign_material_name:
|
||
raise BioyondException("90%_2物料:如果提供了目标重量,必须同时提供物料名称")
|
||
if not percent_90_2_target_weigh:
|
||
raise BioyondException("90%_2物料:如果提供了物料名称,必须同时提供目标重量")
|
||
|
||
# 90%_3物料:如果有物料名称或目标重量,就必须有全部参数
|
||
if percent_90_3_assign_material_name or percent_90_3_target_weigh:
|
||
if not percent_90_3_assign_material_name:
|
||
raise BioyondException("90%_3物料:如果提供了目标重量,必须同时提供物料名称")
|
||
if not percent_90_3_target_weigh:
|
||
raise BioyondException("90%_3物料:如果提供了物料名称,必须同时提供目标重量")
|
||
|
||
# 检查10%物料参数的完整性
|
||
# 10%_1物料:如果有物料名称、目标重量、体积或液体物料名称中的任何一个,就必须有全部参数
|
||
if any([percent_10_1_assign_material_name, percent_10_1_target_weigh, percent_10_1_volume, percent_10_1_liquid_material_name]):
|
||
if not percent_10_1_assign_material_name:
|
||
raise BioyondException("10%_1物料:如果提供了其他参数,必须同时提供固体物料名称")
|
||
if not percent_10_1_target_weigh:
|
||
raise BioyondException("10%_1物料:如果提供了其他参数,必须同时提供固体目标重量")
|
||
if not percent_10_1_volume:
|
||
raise BioyondException("10%_1物料:如果提供了其他参数,必须同时提供液体体积")
|
||
if not percent_10_1_liquid_material_name:
|
||
raise BioyondException("10%_1物料:如果提供了其他参数,必须同时提供液体物料名称")
|
||
|
||
# 10%_2物料:如果有物料名称、目标重量、体积或液体物料名称中的任何一个,就必须有全部参数
|
||
if any([percent_10_2_assign_material_name, percent_10_2_target_weigh, percent_10_2_volume, percent_10_2_liquid_material_name]):
|
||
if not percent_10_2_assign_material_name:
|
||
raise BioyondException("10%_2物料:如果提供了其他参数,必须同时提供固体物料名称")
|
||
if not percent_10_2_target_weigh:
|
||
raise BioyondException("10%_2物料:如果提供了其他参数,必须同时提供固体目标重量")
|
||
if not percent_10_2_volume:
|
||
raise BioyondException("10%_2物料:如果提供了其他参数,必须同时提供液体体积")
|
||
if not percent_10_2_liquid_material_name:
|
||
raise BioyondException("10%_2物料:如果提供了其他参数,必须同时提供液体物料名称")
|
||
|
||
# 10%_3物料:如果有物料名称、目标重量、体积或液体物料名称中的任何一个,就必须有全部参数
|
||
if any([percent_10_3_assign_material_name, percent_10_3_target_weigh, percent_10_3_volume, percent_10_3_liquid_material_name]):
|
||
if not percent_10_3_assign_material_name:
|
||
raise BioyondException("10%_3物料:如果提供了其他参数,必须同时提供固体物料名称")
|
||
if not percent_10_3_target_weigh:
|
||
raise BioyondException("10%_3物料:如果提供了其他参数,必须同时提供固体目标重量")
|
||
if not percent_10_3_volume:
|
||
raise BioyondException("10%_3物料:如果提供了其他参数,必须同时提供液体体积")
|
||
if not percent_10_3_liquid_material_name:
|
||
raise BioyondException("10%_3物料:如果提供了其他参数,必须同时提供液体物料名称")
|
||
|
||
# 2. 生成任务编码和设置默认值
|
||
order_code = "task_vial_" + str(int(datetime.now().timestamp()))
|
||
if order_name is None:
|
||
order_name = "90%10%小瓶投料任务"
|
||
if speed is None:
|
||
speed = "400"
|
||
if temperature is None:
|
||
temperature = "40"
|
||
if delay_time is None:
|
||
delay_time = "600"
|
||
|
||
# 3. 工作流ID
|
||
workflow_id = "3a19310d-16b9-9d81-b109-0748e953694b"
|
||
|
||
# 4. 查询工作流对应的holdMID
|
||
material_info = self.hardware_interface.material_id_query(workflow_id)
|
||
if not material_info:
|
||
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
|
||
|
||
# 获取locations列表
|
||
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
|
||
if not locations:
|
||
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
|
||
|
||
# 查找指定名称的库位
|
||
hold_mid = None
|
||
for location in locations:
|
||
if location.get("holdMName") == hold_m_name:
|
||
hold_mid = location.get("holdMId")
|
||
break
|
||
|
||
if not hold_mid:
|
||
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
|
||
|
||
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
|
||
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
|
||
|
||
# 5. 构建任务参数
|
||
order_data = {
|
||
"orderCode": order_code,
|
||
"orderName": order_name,
|
||
"workflowId": workflow_id,
|
||
"borderNumber": 1,
|
||
"paramValues": {},
|
||
"ExtendProperties": extend_properties
|
||
}
|
||
|
||
# 添加搅拌参数
|
||
order_data["paramValues"]["e8264e47-c319-d9d9-8676-4dd5cb382b11"] = [
|
||
{"m": 0, "n": 3, "Key": "speed", "Value": speed},
|
||
{"m": 0, "n": 3, "Key": "temperature", "Value": temperature}
|
||
]
|
||
|
||
# 添加延迟时间参数
|
||
order_data["paramValues"]["dc5dba79-5e4b-8eae-cbc5-e93482e43b1f"] = [
|
||
{"m": 0, "n": 4, "Key": "DelayTime", "Value": delay_time}
|
||
]
|
||
|
||
# 添加90%_1参数
|
||
if percent_90_1_assign_material_name is not None and percent_90_1_target_weigh is not None:
|
||
order_data["paramValues"]["e7d3c0a3-25c2-c42d-c84b-860c4a5ef844"] = [
|
||
{"m": 15, "n": 1, "Key": "targetWeigh", "Value": percent_90_1_target_weigh},
|
||
{"m": 15, "n": 1, "Key": "assignMaterialName", "Value": percent_90_1_assign_material_name}
|
||
]
|
||
|
||
# 添加90%_2参数
|
||
if percent_90_2_assign_material_name is not None and percent_90_2_target_weigh is not None:
|
||
order_data["paramValues"]["50b912c4-6c81-0734-1c8b-532428b2a4a5"] = [
|
||
{"m": 18, "n": 1, "Key": "targetWeigh", "Value": percent_90_2_target_weigh},
|
||
{"m": 18, "n": 1, "Key": "assignMaterialName", "Value": percent_90_2_assign_material_name}
|
||
]
|
||
|
||
# 添加90%_3参数
|
||
if percent_90_3_assign_material_name is not None and percent_90_3_target_weigh is not None:
|
||
order_data["paramValues"]["9c3674b3-c7cb-946e-fa03-fa2861d8aec4"] = [
|
||
{"m": 21, "n": 1, "Key": "targetWeigh", "Value": percent_90_3_target_weigh},
|
||
{"m": 21, "n": 1, "Key": "assignMaterialName", "Value": percent_90_3_assign_material_name}
|
||
]
|
||
|
||
# 添加10%_1固体参数
|
||
if percent_10_1_assign_material_name is not None and percent_10_1_target_weigh is not None:
|
||
order_data["paramValues"]["73a0bfd8-1967-45e9-4bab-c07ccd1a2727"] = [
|
||
{"m": 3, "n": 1, "Key": "targetWeigh", "Value": percent_10_1_target_weigh},
|
||
{"m": 3, "n": 1, "Key": "assignMaterialName", "Value": percent_10_1_assign_material_name}
|
||
]
|
||
|
||
# 添加10%_1液体参数
|
||
if percent_10_1_liquid_material_name is not None and percent_10_1_volume is not None:
|
||
order_data["paramValues"]["39634d40-c623-473a-8e5f-bc301aca2522"] = [
|
||
{"m": 3, "n": 3, "Key": "volume", "Value": percent_10_1_volume},
|
||
{"m": 3, "n": 3, "Key": "assignMaterialName", "Value": percent_10_1_liquid_material_name}
|
||
]
|
||
|
||
# 添加10%_2固体参数
|
||
if percent_10_2_assign_material_name is not None and percent_10_2_target_weigh is not None:
|
||
order_data["paramValues"]["2d9c16fa-2a19-cd47-a67b-3cadff9e3e3d"] = [
|
||
{"m": 7, "n": 1, "Key": "targetWeigh", "Value": percent_10_2_target_weigh},
|
||
{"m": 7, "n": 1, "Key": "assignMaterialName", "Value": percent_10_2_assign_material_name}
|
||
]
|
||
|
||
# 添加10%_2液体参数
|
||
if percent_10_2_liquid_material_name is not None and percent_10_2_volume is not None:
|
||
order_data["paramValues"]["e60541bb-ed68-e839-7305-2b4abe38a13d"] = [
|
||
{"m": 7, "n": 3, "Key": "volume", "Value": percent_10_2_volume},
|
||
{"m": 7, "n": 3, "Key": "assignMaterialName", "Value": percent_10_2_liquid_material_name}
|
||
]
|
||
|
||
# 添加10%_3固体参数
|
||
if percent_10_3_assign_material_name is not None and percent_10_3_target_weigh is not None:
|
||
order_data["paramValues"]["27494733-0f71-a916-7cd2-1929a0125f17"] = [
|
||
{"m": 11, "n": 1, "Key": "targetWeigh", "Value": percent_10_3_target_weigh},
|
||
{"m": 11, "n": 1, "Key": "assignMaterialName", "Value": percent_10_3_assign_material_name}
|
||
]
|
||
|
||
# 添加10%_3液体参数
|
||
if percent_10_3_liquid_material_name is not None and percent_10_3_volume is not None:
|
||
order_data["paramValues"]["c8798c29-786f-6858-7d7f-5330b890f2a6"] = [
|
||
{"m": 11, "n": 3, "Key": "volume", "Value": percent_10_3_volume},
|
||
{"m": 11, "n": 3, "Key": "assignMaterialName", "Value": percent_10_3_liquid_material_name}
|
||
]
|
||
|
||
# 6. 转换为JSON字符串并创建任务
|
||
json_str = json.dumps([order_data], ensure_ascii=False)
|
||
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务参数: {json_str}")
|
||
|
||
# 7. 调用create_order方法创建任务
|
||
result = self.hardware_interface.create_order(json_str)
|
||
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务结果: {result}")
|
||
|
||
# 8. 解析结果获取order_id
|
||
order_id = None
|
||
if isinstance(result, str):
|
||
# result 格式: "{'3a1d895c-4d39-d504-1398-18f5a40bac1e': [{'id': '...', ...}]}"
|
||
# 第一个键就是order_id (UUID)
|
||
try:
|
||
# 尝试解析字符串为字典
|
||
import ast
|
||
result_dict = ast.literal_eval(result)
|
||
# 获取第一个键作为order_id
|
||
if result_dict and isinstance(result_dict, dict):
|
||
first_key = list(result_dict.keys())[0]
|
||
order_id = first_key
|
||
self.hardware_interface._logger.info(f"✓ 成功提取order_id: {order_id}")
|
||
else:
|
||
self.hardware_interface._logger.warning(f"result_dict格式异常: {result_dict}")
|
||
except Exception as e:
|
||
self.hardware_interface._logger.error(f"✗ 无法从结果中提取order_id: {e}, result类型={type(result)}")
|
||
elif isinstance(result, dict):
|
||
# 如果已经是字典
|
||
if result:
|
||
first_key = list(result.keys())[0]
|
||
order_id = first_key
|
||
self.hardware_interface._logger.info(f"✓ 成功提取order_id(dict): {order_id}")
|
||
|
||
if not order_id:
|
||
self.hardware_interface._logger.warning(
|
||
f"⚠ 未能提取order_id,result={result[:100] if isinstance(result, str) else result}"
|
||
)
|
||
|
||
# 返回成功结果和构建的JSON数据
|
||
return json.dumps({
|
||
"suc": True,
|
||
"order_code": order_code,
|
||
"order_id": order_id,
|
||
"result": result,
|
||
"order_params": order_data
|
||
})
|
||
|
||
except BioyondException:
|
||
# 重新抛出BioyondException
|
||
raise
|
||
except Exception as e:
|
||
# 捕获其他未预期的异常,转换为BioyondException
|
||
error_msg = f"创建90%10%小瓶投料任务时发生未预期的错误: {str(e)}"
|
||
self.hardware_interface._logger.error(error_msg)
|
||
raise BioyondException(error_msg)
|
||
|
||
# 二胺溶液配置任务创建方法
|
||
def create_diamine_solution_task(self,
|
||
order_name: str = None,
|
||
material_name: str = None,
|
||
target_weigh: str = None,
|
||
volume: str = None,
|
||
liquid_material_name: str = "NMP",
|
||
speed: str = None,
|
||
temperature: str = None,
|
||
delay_time: str = None,
|
||
hold_m_name: str = None) -> dict:
|
||
"""
|
||
创建二胺溶液配置任务
|
||
|
||
参数说明:
|
||
- order_name: 任务名称,如果为None则使用默认名称
|
||
- material_name: 固体物料名称,必填
|
||
- target_weigh: 固体目标重量,必填
|
||
- volume: 液体体积,必填
|
||
- liquid_material_name: 液体物料名称,默认为NMP
|
||
- speed: 搅拌速度,如果为None则使用默认值400
|
||
- temperature: 温度,如果为None则使用默认值20
|
||
- delay_time: 延迟时间,如果为None则使用默认值600
|
||
- hold_m_name: 库位名称,如"ODA-1",用于查找对应的holdMId
|
||
|
||
返回: 任务创建结果
|
||
|
||
异常:
|
||
- BioyondException: 各种错误情况下的统一异常
|
||
"""
|
||
try:
|
||
# 1. 参数验证
|
||
if not material_name:
|
||
raise BioyondException("material_name 是必填参数")
|
||
if not target_weigh:
|
||
raise BioyondException("target_weigh 是必填参数")
|
||
if not volume:
|
||
raise BioyondException("volume 是必填参数")
|
||
if not hold_m_name:
|
||
raise BioyondException("hold_m_name 是必填参数")
|
||
|
||
|
||
# 2. 生成任务编码和设置默认值
|
||
order_code = "task_oda_" + str(int(datetime.now().timestamp()))
|
||
if order_name is None:
|
||
order_name = f"二胺溶液配置-{material_name}"
|
||
if speed is None:
|
||
speed = "400"
|
||
if temperature is None:
|
||
temperature = "20"
|
||
if delay_time is None:
|
||
delay_time = "600"
|
||
|
||
# 3. 工作流ID - 二胺溶液配置工作流
|
||
workflow_id = "3a15d4a1-3bbe-76f9-a458-292896a338f5"
|
||
|
||
# 4. 查询工作流对应的holdMID
|
||
material_info = self.hardware_interface.material_id_query(workflow_id)
|
||
if not material_info:
|
||
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
|
||
|
||
# 获取locations列表
|
||
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
|
||
if not locations:
|
||
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
|
||
|
||
# 查找指定名称的库位
|
||
hold_mid = None
|
||
for location in locations:
|
||
if location.get("holdMName") == hold_m_name:
|
||
hold_mid = location.get("holdMId")
|
||
break
|
||
|
||
if not hold_mid:
|
||
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
|
||
|
||
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
|
||
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
|
||
|
||
# 5. 构建任务参数
|
||
order_data = {
|
||
"orderCode": order_code,
|
||
"orderName": order_name,
|
||
"workflowId": workflow_id,
|
||
"borderNumber": 1,
|
||
"paramValues": {
|
||
# 固体物料参数
|
||
"3a15d4a1-3bde-f5bc-053f-1ae0bf1f357e": [
|
||
{"m": 3, "n": 2, "Key": "targetWeigh", "Value": target_weigh},
|
||
{"m": 3, "n": 2, "Key": "assignMaterialName", "Value": material_name}
|
||
],
|
||
# 液体物料参数
|
||
"3a15d4a1-3bde-d584-b309-e661ae8f1c01": [
|
||
{"m": 3, "n": 3, "Key": "volume", "Value": volume},
|
||
{"m": 3, "n": 3, "Key": "assignMaterialName", "Value": liquid_material_name}
|
||
],
|
||
# 搅拌参数
|
||
"3a15d4a1-3bde-8ec4-1ced-92efc97ed73d": [
|
||
{"m": 3, "n": 6, "Key": "speed", "Value": speed},
|
||
{"m": 3, "n": 6, "Key": "temperature", "Value": temperature}
|
||
],
|
||
# 延迟时间参数
|
||
"3a15d4a1-3bde-3b92-83ff-8923a0addbbc": [
|
||
{"m": 3, "n": 7, "Key": "DelayTime", "Value": delay_time}
|
||
]
|
||
},
|
||
"ExtendProperties": extend_properties
|
||
}
|
||
|
||
# 6. 转换为JSON字符串并创建任务
|
||
json_str = json.dumps([order_data], ensure_ascii=False)
|
||
self.hardware_interface._logger.info(f"创建二胺溶液配置任务参数: {json_str}")
|
||
|
||
# 7. 调用create_order方法创建任务
|
||
result = self.hardware_interface.create_order(json_str)
|
||
self.hardware_interface._logger.info(f"创建二胺溶液配置任务结果: {result}")
|
||
|
||
# 8. 解析结果获取order_id
|
||
order_id = None
|
||
if isinstance(result, str):
|
||
try:
|
||
import ast
|
||
result_dict = ast.literal_eval(result)
|
||
if result_dict and isinstance(result_dict, dict):
|
||
first_key = list(result_dict.keys())[0]
|
||
order_id = first_key
|
||
self.hardware_interface._logger.info(f"✓ 成功提取order_id: {order_id}")
|
||
else:
|
||
self.hardware_interface._logger.warning(f"result_dict格式异常: {result_dict}")
|
||
except Exception as e:
|
||
self.hardware_interface._logger.error(f"✗ 无法从结果中提取order_id: {e}")
|
||
elif isinstance(result, dict):
|
||
if result:
|
||
first_key = list(result.keys())[0]
|
||
order_id = first_key
|
||
self.hardware_interface._logger.info(f"✓ 成功提取order_id(dict): {order_id}")
|
||
|
||
if not order_id:
|
||
self.hardware_interface._logger.warning(f"⚠ 未能提取order_id")
|
||
|
||
# 返回成功结果和构建的JSON数据
|
||
return json.dumps({
|
||
"suc": True,
|
||
"order_code": order_code,
|
||
"order_id": order_id,
|
||
"result": result,
|
||
"order_params": order_data
|
||
})
|
||
|
||
except BioyondException:
|
||
# 重新抛出BioyondException
|
||
raise
|
||
except Exception as e:
|
||
# 捕获其他未预期的异常,转换为BioyondException
|
||
error_msg = f"创建二胺溶液配置任务时发生未预期的错误: {str(e)}"
|
||
self.hardware_interface._logger.error(error_msg)
|
||
raise BioyondException(error_msg)
|
||
|
||
# 批量创建二胺溶液配置任务
|
||
def batch_create_diamine_solution_tasks(self,
|
||
solutions,
|
||
liquid_material_name: str = "NMP",
|
||
speed: str = None,
|
||
temperature: str = None,
|
||
delay_time: str = None) -> str:
|
||
"""
|
||
批量创建二胺溶液配置任务
|
||
|
||
参数说明:
|
||
- solutions: 溶液列表(数组)或JSON字符串,格式如下:
|
||
[
|
||
{
|
||
"name": "MDA",
|
||
"order": 0,
|
||
"solid_mass": 5.0,
|
||
"solvent_volume": 20,
|
||
...
|
||
},
|
||
...
|
||
]
|
||
- liquid_material_name: 液体物料名称,默认为"NMP"
|
||
- speed: 搅拌速度,如果为None则使用默认值400
|
||
- temperature: 温度,如果为None则使用默认值20
|
||
- delay_time: 延迟时间,如果为None则使用默认值600
|
||
|
||
返回: JSON字符串格式的任务创建结果
|
||
|
||
异常:
|
||
- BioyondException: 各种错误情况下的统一异常
|
||
"""
|
||
try:
|
||
# 参数类型转换:如果是字符串则解析为列表
|
||
if isinstance(solutions, str):
|
||
try:
|
||
solutions = json.loads(solutions)
|
||
except json.JSONDecodeError as e:
|
||
raise BioyondException(f"solutions JSON解析失败: {str(e)}")
|
||
|
||
# 参数验证
|
||
if not isinstance(solutions, list):
|
||
raise BioyondException("solutions 必须是列表类型或有效的JSON数组字符串")
|
||
|
||
if not solutions:
|
||
raise BioyondException("solutions 列表不能为空")
|
||
|
||
# 批量创建任务
|
||
results = []
|
||
success_count = 0
|
||
failed_count = 0
|
||
|
||
for idx, solution in enumerate(solutions):
|
||
try:
|
||
# 提取参数
|
||
name = solution.get("name")
|
||
solid_mass = solution.get("solid_mass")
|
||
solvent_volume = solution.get("solvent_volume")
|
||
order = solution.get("order")
|
||
|
||
if not all([name, solid_mass is not None, solvent_volume is not None]):
|
||
self.hardware_interface._logger.warning(
|
||
f"跳过第 {idx + 1} 个溶液:缺少必要参数"
|
||
)
|
||
results.append({
|
||
"index": idx + 1,
|
||
"name": name,
|
||
"success": False,
|
||
"error": "缺少必要参数"
|
||
})
|
||
failed_count += 1
|
||
continue
|
||
|
||
# 生成库位名称(直接使用物料名称)
|
||
# 如果需要其他命名规则,可以在这里调整
|
||
hold_m_name = name
|
||
|
||
# 调用单个任务创建方法
|
||
result = self.create_diamine_solution_task(
|
||
order_name=f"二胺溶液配置-{name}",
|
||
material_name=name,
|
||
target_weigh=str(solid_mass),
|
||
volume=str(solvent_volume),
|
||
liquid_material_name=liquid_material_name,
|
||
speed=speed,
|
||
temperature=temperature,
|
||
delay_time=delay_time,
|
||
hold_m_name=hold_m_name
|
||
)
|
||
|
||
# 解析返回结果以获取order_code和order_id
|
||
result_data = json.loads(result) if isinstance(result, str) else result
|
||
order_code = result_data.get("order_code")
|
||
order_id = result_data.get("order_id")
|
||
order_params = result_data.get("order_params", {})
|
||
|
||
results.append({
|
||
"index": idx + 1,
|
||
"name": name,
|
||
"success": True,
|
||
"order_code": order_code,
|
||
"order_id": order_id,
|
||
"hold_m_name": hold_m_name,
|
||
"order_params": order_params
|
||
})
|
||
success_count += 1
|
||
self.hardware_interface._logger.info(
|
||
f"成功创建二胺溶液配置任务: {name}, order_code={order_code}, order_id={order_id}"
|
||
)
|
||
|
||
except BioyondException as e:
|
||
results.append({
|
||
"index": idx + 1,
|
||
"name": solution.get("name", "unknown"),
|
||
"success": False,
|
||
"error": str(e)
|
||
})
|
||
failed_count += 1
|
||
self.hardware_interface._logger.error(
|
||
f"创建第 {idx + 1} 个任务失败: {str(e)}"
|
||
)
|
||
except Exception as e:
|
||
results.append({
|
||
"index": idx + 1,
|
||
"name": solution.get("name", "unknown"),
|
||
"success": False,
|
||
"error": f"未知错误: {str(e)}"
|
||
})
|
||
failed_count += 1
|
||
self.hardware_interface._logger.error(
|
||
f"创建第 {idx + 1} 个任务时发生未知错误: {str(e)}"
|
||
)
|
||
|
||
# 提取所有成功任务的order_code和order_id
|
||
order_codes = [r["order_code"] for r in results if r["success"]]
|
||
order_ids = [r["order_id"] for r in results if r["success"]]
|
||
|
||
# 返回汇总结果
|
||
summary = {
|
||
"total": len(solutions),
|
||
"success": success_count,
|
||
"failed": failed_count,
|
||
"order_codes": order_codes,
|
||
"order_ids": order_ids,
|
||
"details": results
|
||
}
|
||
|
||
self.hardware_interface._logger.info(
|
||
f"批量创建二胺溶液配置任务完成: 总数={len(solutions)}, "
|
||
f"成功={success_count}, 失败={failed_count}"
|
||
)
|
||
|
||
# 构建返回结果
|
||
summary["return_info"] = {
|
||
"order_codes": order_codes,
|
||
"order_ids": order_ids,
|
||
}
|
||
|
||
return summary
|
||
|
||
except BioyondException:
|
||
raise
|
||
except Exception as e:
|
||
error_msg = f"批量创建二胺溶液配置任务时发生未预期的错误: {str(e)}"
|
||
self.hardware_interface._logger.error(error_msg)
|
||
raise BioyondException(error_msg)
|
||
|
||
# 批量创建90%10%小瓶投料任务
|
||
def batch_create_90_10_vial_feeding_tasks(self,
|
||
titration,
|
||
hold_m_name: str = None,
|
||
speed: str = None,
|
||
temperature: str = None,
|
||
delay_time: str = None,
|
||
liquid_material_name: str = "NMP") -> str:
|
||
"""
|
||
批量创建90%10%小瓶投料任务(仅创建1个任务,但包含所有90%和10%物料)
|
||
|
||
参数说明:
|
||
- titration: 滴定信息的字典或JSON字符串,格式如下:
|
||
{
|
||
"name": "BTDA",
|
||
"main_portion": 1.9152351915461294, # 主称固体质量(g) -> 90%物料
|
||
"titration_portion": 0.05923407808905555, # 滴定固体质量(g) -> 10%物料固体
|
||
"titration_solvent": 3.050555021586361 # 滴定溶液体积(mL) -> 10%物料液体
|
||
}
|
||
- hold_m_name: 库位名称,如"C01"。必填参数
|
||
- speed: 搅拌速度,如果为None则使用默认值400
|
||
- temperature: 温度,如果为None则使用默认值40
|
||
- delay_time: 延迟时间,如果为None则使用默认值600
|
||
- liquid_material_name: 10%物料的液体物料名称,默认为"NMP"
|
||
|
||
返回: JSON字符串格式的任务创建结果
|
||
|
||
异常:
|
||
- BioyondException: 各种错误情况下的统一异常
|
||
"""
|
||
try:
|
||
# 参数类型转换:如果是字符串则解析为字典
|
||
if isinstance(titration, str):
|
||
try:
|
||
titration = json.loads(titration)
|
||
except json.JSONDecodeError as e:
|
||
raise BioyondException(f"titration参数JSON解析失败: {str(e)}")
|
||
|
||
# 参数验证
|
||
if not isinstance(titration, dict):
|
||
raise BioyondException("titration 必须是字典类型或有效的JSON字符串")
|
||
|
||
if not hold_m_name:
|
||
raise BioyondException("hold_m_name 是必填参数")
|
||
|
||
if not titration:
|
||
raise BioyondException("titration 参数不能为空")
|
||
|
||
# 提取滴定数据
|
||
name = titration.get("name")
|
||
main_portion = titration.get("main_portion") # 主称固体质量
|
||
titration_portion = titration.get("titration_portion") # 滴定固体质量
|
||
titration_solvent = titration.get("titration_solvent") # 滴定溶液体积
|
||
|
||
if not all([name, main_portion is not None, titration_portion is not None, titration_solvent is not None]):
|
||
raise BioyondException("titration 数据缺少必要参数")
|
||
|
||
# 调用单个任务创建方法
|
||
result = self.create_90_10_vial_feeding_task(
|
||
order_name=f"90%10%小瓶投料-{name}",
|
||
speed=speed,
|
||
temperature=temperature,
|
||
delay_time=delay_time,
|
||
# 90%物料 - 主称固体直接使用main_portion
|
||
percent_90_1_assign_material_name=name,
|
||
percent_90_1_target_weigh=str(round(main_portion, 6)),
|
||
# 10%物料 - 滴定固体 + 滴定溶剂(只使用第1个10%小瓶)
|
||
percent_10_1_assign_material_name=name,
|
||
percent_10_1_target_weigh=str(round(titration_portion, 6)),
|
||
percent_10_1_volume=str(round(titration_solvent, 6)),
|
||
percent_10_1_liquid_material_name=liquid_material_name,
|
||
hold_m_name=hold_m_name
|
||
)
|
||
|
||
# 解析返回结果以获取order_code和order_id
|
||
result_data = json.loads(result) if isinstance(result, str) else result
|
||
order_code = result_data.get("order_code")
|
||
order_id = result_data.get("order_id")
|
||
order_params = result_data.get("order_params", {})
|
||
|
||
# 构建详细信息(保持原有结构)
|
||
detail = {
|
||
"index": 1,
|
||
"name": name,
|
||
"success": True,
|
||
"order_code": order_code,
|
||
"order_id": order_id,
|
||
"hold_m_name": hold_m_name,
|
||
"90_vials": {
|
||
"count": 1,
|
||
"weight_per_vial": round(main_portion, 6),
|
||
"total_weight": round(main_portion, 6)
|
||
},
|
||
"10_vials": {
|
||
"count": 1,
|
||
"solid_weight": round(titration_portion, 6),
|
||
"liquid_volume": round(titration_solvent, 6)
|
||
},
|
||
"order_params": order_params
|
||
}
|
||
|
||
# 构建批量结果格式(与diamine_solution_tasks保持一致)
|
||
summary = {
|
||
"total": 1,
|
||
"success": 1,
|
||
"failed": 0,
|
||
"order_codes": [order_code],
|
||
"order_ids": [order_id],
|
||
"details": [detail]
|
||
}
|
||
|
||
self.hardware_interface._logger.info(
|
||
f"成功创建90%10%小瓶投料任务: {name}, order_code={order_code}, order_id={order_id}"
|
||
)
|
||
|
||
# 构建返回结果
|
||
summary["return_info"] = {
|
||
"order_codes": [order_code],
|
||
"order_ids": [order_id],
|
||
}
|
||
|
||
return summary
|
||
|
||
except BioyondException:
|
||
raise
|
||
except Exception as e:
|
||
error_msg = f"批量创建90%10%小瓶投料任务时发生未预期的错误: {str(e)}"
|
||
self.hardware_interface._logger.error(error_msg)
|
||
raise BioyondException(error_msg)
|
||
|
||
|
||
|
||
def wait_for_multiple_orders_and_get_reports(self,
|
||
batch_create_result: str = None,
|
||
timeout: int = 7200,
|
||
check_interval: int = 10) -> Dict[str, Any]:
|
||
"""
|
||
同时等待多个任务完成并获取实验报告
|
||
|
||
参数说明:
|
||
- batch_create_result: 批量创建任务的返回结果JSON字符串,包含order_codes和order_ids数组
|
||
- timeout: 超时时间(秒),默认7200秒(2小时)
|
||
- check_interval: 检查间隔(秒),默认10秒
|
||
|
||
返回: 包含所有任务状态和报告的字典
|
||
{
|
||
"total": 2,
|
||
"completed": 2,
|
||
"timeout": 0,
|
||
"elapsed_time": 120.5,
|
||
"reports": [
|
||
{
|
||
"order_code": "task_vial_1",
|
||
"order_id": "uuid1",
|
||
"status": "completed",
|
||
"completion_status": 30,
|
||
"report": {...}
|
||
},
|
||
...
|
||
]
|
||
}
|
||
|
||
异常:
|
||
- BioyondException: 所有任务都超时或发生错误
|
||
"""
|
||
try:
|
||
# 参数类型转换
|
||
timeout = int(timeout) if timeout else 7200
|
||
check_interval = int(check_interval) if check_interval else 10
|
||
|
||
# 验证batch_create_result参数
|
||
if not batch_create_result or batch_create_result == "":
|
||
raise BioyondException("batch_create_result参数为空,请确保从batch_create节点正确连接handle")
|
||
|
||
# 解析batch_create_result JSON对象
|
||
try:
|
||
# 清理可能存在的截断标记 [...]
|
||
if isinstance(batch_create_result, str) and '[...]' in batch_create_result:
|
||
batch_create_result = batch_create_result.replace('[...]', '[]')
|
||
|
||
result_obj = json.loads(batch_create_result) if isinstance(batch_create_result, str) else batch_create_result
|
||
|
||
# 兼容外层包装格式 {error, suc, return_value}
|
||
if isinstance(result_obj, dict) and "return_value" in result_obj:
|
||
inner = result_obj.get("return_value")
|
||
if isinstance(inner, str):
|
||
result_obj = json.loads(inner)
|
||
elif isinstance(inner, dict):
|
||
result_obj = inner
|
||
|
||
# 从summary对象中提取order_codes和order_ids
|
||
order_codes = result_obj.get("order_codes", [])
|
||
order_ids = result_obj.get("order_ids", [])
|
||
|
||
except json.JSONDecodeError as e:
|
||
raise BioyondException(f"解析batch_create_result失败: {e}")
|
||
except Exception as e:
|
||
raise BioyondException(f"处理batch_create_result时出错: {e}")
|
||
|
||
# 验证提取的数据
|
||
if not order_codes:
|
||
raise BioyondException("batch_create_result中未找到order_codes字段或为空")
|
||
if not order_ids:
|
||
raise BioyondException("batch_create_result中未找到order_ids字段或为空")
|
||
|
||
# 确保order_codes和order_ids是列表类型
|
||
if not isinstance(order_codes, list):
|
||
order_codes = [order_codes] if order_codes else []
|
||
if not isinstance(order_ids, list):
|
||
order_ids = [order_ids] if order_ids else []
|
||
|
||
codes_list = order_codes
|
||
ids_list = order_ids
|
||
|
||
if len(codes_list) != len(ids_list):
|
||
raise BioyondException(
|
||
f"order_codes数量({len(codes_list)})与order_ids数量({len(ids_list)})不匹配"
|
||
)
|
||
|
||
if not codes_list or not ids_list:
|
||
raise BioyondException("order_codes和order_ids不能为空")
|
||
|
||
# 初始化跟踪变量
|
||
total = len(codes_list)
|
||
pending_orders = {code: {"order_id": ids_list[i], "completed": False}
|
||
for i, code in enumerate(codes_list)}
|
||
reports = []
|
||
|
||
start_time = time.time()
|
||
self.hardware_interface._logger.info(
|
||
f"开始等待 {total} 个任务完成: {', '.join(codes_list)}"
|
||
)
|
||
|
||
# 轮询检查任务状态
|
||
while pending_orders:
|
||
elapsed_time = time.time() - start_time
|
||
|
||
# 检查超时
|
||
if elapsed_time > timeout:
|
||
# 收集超时任务
|
||
timeout_orders = list(pending_orders.keys())
|
||
self.hardware_interface._logger.error(
|
||
f"等待任务完成超时,剩余未完成任务: {', '.join(timeout_orders)}"
|
||
)
|
||
|
||
# 为超时任务添加记录
|
||
for order_code in timeout_orders:
|
||
reports.append({
|
||
"order_code": order_code,
|
||
"order_id": pending_orders[order_code]["order_id"],
|
||
"status": "timeout",
|
||
"completion_status": None,
|
||
"report": None,
|
||
"elapsed_time": elapsed_time
|
||
})
|
||
|
||
break
|
||
|
||
# 检查每个待完成的任务
|
||
completed_in_this_round = []
|
||
for order_code in list(pending_orders.keys()):
|
||
order_id = pending_orders[order_code]["order_id"]
|
||
|
||
# 检查任务是否完成
|
||
if order_code in self.order_completion_status:
|
||
completion_info = self.order_completion_status[order_code]
|
||
self.hardware_interface._logger.info(
|
||
f"检测到任务 {order_code} 已完成,状态: {completion_info.get('status')}"
|
||
)
|
||
|
||
# 获取实验报告
|
||
try:
|
||
report_query = json.dumps({"order_id": order_id})
|
||
report = self.hardware_interface.order_report(report_query)
|
||
|
||
if not report:
|
||
self.hardware_interface._logger.warning(
|
||
f"任务 {order_code} 已完成但无法获取报告"
|
||
)
|
||
report = {"error": "无法获取报告"}
|
||
else:
|
||
self.hardware_interface._logger.info(
|
||
f"成功获取任务 {order_code} 的实验报告"
|
||
)
|
||
|
||
reports.append({
|
||
"order_code": order_code,
|
||
"order_id": order_id,
|
||
"status": "completed",
|
||
"completion_status": completion_info.get('status'),
|
||
"report": report,
|
||
"elapsed_time": elapsed_time
|
||
})
|
||
|
||
# 标记为已完成
|
||
completed_in_this_round.append(order_code)
|
||
|
||
# 清理完成状态记录
|
||
del self.order_completion_status[order_code]
|
||
|
||
except Exception as e:
|
||
self.hardware_interface._logger.error(
|
||
f"查询任务 {order_code} 报告失败: {str(e)}"
|
||
)
|
||
reports.append({
|
||
"order_code": order_code,
|
||
"order_id": order_id,
|
||
"status": "error",
|
||
"completion_status": completion_info.get('status'),
|
||
"report": None,
|
||
"error": str(e),
|
||
"elapsed_time": elapsed_time
|
||
})
|
||
completed_in_this_round.append(order_code)
|
||
|
||
# 从待完成列表中移除已完成的任务
|
||
for order_code in completed_in_this_round:
|
||
del pending_orders[order_code]
|
||
|
||
# 如果还有待完成的任务,等待后继续
|
||
if pending_orders:
|
||
time.sleep(check_interval)
|
||
|
||
# 每分钟记录一次等待状态
|
||
new_elapsed_time = time.time() - start_time
|
||
if int(new_elapsed_time) % 60 == 0 and new_elapsed_time > 0:
|
||
self.hardware_interface._logger.info(
|
||
f"批量等待任务中... 已完成 {len(reports)}/{total}, "
|
||
f"待完成: {', '.join(pending_orders.keys())}, "
|
||
f"已等待 {int(new_elapsed_time/60)} 分钟"
|
||
)
|
||
|
||
# 统计结果
|
||
completed_count = sum(1 for r in reports if r['status'] == 'completed')
|
||
timeout_count = sum(1 for r in reports if r['status'] == 'timeout')
|
||
error_count = sum(1 for r in reports if r['status'] == 'error')
|
||
|
||
final_elapsed_time = time.time() - start_time
|
||
|
||
summary = {
|
||
"total": total,
|
||
"completed": completed_count,
|
||
"timeout": timeout_count,
|
||
"error": error_count,
|
||
"elapsed_time": round(final_elapsed_time, 2),
|
||
"reports": reports
|
||
}
|
||
|
||
self.hardware_interface._logger.info(
|
||
f"批量等待任务完成: 总数={total}, 成功={completed_count}, "
|
||
f"超时={timeout_count}, 错误={error_count}, 耗时={final_elapsed_time:.1f}秒"
|
||
)
|
||
|
||
# 返回字典格式,在顶层包含统计信息
|
||
return {
|
||
"return_info": json.dumps(summary, ensure_ascii=False)
|
||
}
|
||
|
||
except BioyondException:
|
||
raise
|
||
except Exception as e:
|
||
error_msg = f"批量等待任务完成时发生未预期的错误: {str(e)}"
|
||
self.hardware_interface._logger.error(error_msg)
|
||
raise BioyondException(error_msg)
|
||
|
||
def process_order_finish_report(self, report_request, used_materials) -> Dict[str, Any]:
|
||
"""
|
||
重写父类方法,处理任务完成报送并记录到 order_completion_status
|
||
|
||
Args:
|
||
report_request: WorkstationReportRequest 对象,包含任务完成信息
|
||
used_materials: 物料使用记录列表
|
||
|
||
Returns:
|
||
Dict[str, Any]: 处理结果
|
||
"""
|
||
try:
|
||
# 调用父类方法
|
||
result = super().process_order_finish_report(report_request, used_materials)
|
||
|
||
# 记录任务完成状态
|
||
data = report_request.data
|
||
order_code = data.get('orderCode')
|
||
|
||
if order_code:
|
||
self.order_completion_status[order_code] = {
|
||
'status': data.get('status'),
|
||
'order_name': data.get('orderName'),
|
||
'timestamp': datetime.now().isoformat(),
|
||
'start_time': data.get('startTime'),
|
||
'end_time': data.get('endTime')
|
||
}
|
||
|
||
self.hardware_interface._logger.info(
|
||
f"已记录任务完成状态: {order_code}, status={data.get('status')}"
|
||
)
|
||
|
||
return result
|
||
|
||
except Exception as e:
|
||
self.hardware_interface._logger.error(f"处理任务完成报送失败: {e}")
|
||
return {"processed": False, "error": str(e)}
|
||
|
||
|
||
if __name__ == "__main__":
|
||
bioyond = BioyondDispensingStation(config={
|
||
"api_key": "DE9BDDA0",
|
||
"api_host": "http://192.168.1.200:44388"
|
||
})
|
||
|
||
# ============ 原有示例代码 ============
|
||
|
||
# 示例1:使用material_id_query查询工作流对应的holdMID
|
||
workflow_id_1 = "3a15d4a1-3bbe-76f9-a458-292896a338f5" # 二胺溶液配置工作流ID
|
||
workflow_id_2 = "3a19310d-16b9-9d81-b109-0748e953694b" # 90%10%小瓶投料工作流ID
|
||
|
||
#示例2:创建二胺溶液配置任务 - ODA,指定库位名称
|
||
# bioyond.create_diamine_solution_task(
|
||
# order_code="task_oda_" + str(int(datetime.now().timestamp())),
|
||
# order_name="二胺溶液配置-ODA",
|
||
# material_name="ODA-1",
|
||
# target_weigh="12.000",
|
||
# volume="60",
|
||
# liquid_material_name= "NMP",
|
||
# speed="400",
|
||
# temperature="20",
|
||
# delay_time="600",
|
||
# hold_m_name="烧杯ODA"
|
||
# )
|
||
|
||
# bioyond.create_diamine_solution_task(
|
||
# order_code="task_pda_" + str(int(datetime.now().timestamp())),
|
||
# order_name="二胺溶液配置-PDA",
|
||
# material_name="PDA-1",
|
||
# target_weigh="4.178",
|
||
# volume="60",
|
||
# liquid_material_name= "NMP",
|
||
# speed="400",
|
||
# temperature="20",
|
||
# delay_time="600",
|
||
# hold_m_name="烧杯PDA-2"
|
||
# )
|
||
|
||
# bioyond.create_diamine_solution_task(
|
||
# order_code="task_mpda_" + str(int(datetime.now().timestamp())),
|
||
# order_name="二胺溶液配置-MPDA",
|
||
# material_name="MPDA-1",
|
||
# target_weigh="3.298",
|
||
# volume="50",
|
||
# liquid_material_name= "NMP",
|
||
# speed="400",
|
||
# temperature="20",
|
||
# delay_time="600",
|
||
# hold_m_name="烧杯MPDA"
|
||
# )
|
||
|
||
bioyond.material_id_query("3a19310d-16b9-9d81-b109-0748e953694b")
|
||
bioyond.material_id_query("3a15d4a1-3bbe-76f9-a458-292896a338f5")
|
||
|
||
|
||
#示例4:创建90%10%小瓶投料任务
|
||
# vial_result = bioyond.create_90_10_vial_feeding_task(
|
||
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
|
||
# order_name="90%10%小瓶投料-1",
|
||
# percent_90_1_assign_material_name="BTDA-1",
|
||
# percent_90_1_target_weigh="7.392",
|
||
# percent_90_2_assign_material_name="BTDA-1",
|
||
# percent_90_2_target_weigh="7.392",
|
||
# percent_90_3_assign_material_name="BTDA-2",
|
||
# percent_90_3_target_weigh="7.392",
|
||
# percent_10_1_assign_material_name="BTDA-2",
|
||
# percent_10_1_target_weigh="1.500",
|
||
# percent_10_1_volume="20",
|
||
# percent_10_1_liquid_material_name="NMP",
|
||
# # percent_10_2_assign_material_name="BTDA-c",
|
||
# # percent_10_2_target_weigh="1.2",
|
||
# # percent_10_2_volume="20",
|
||
# # percent_10_2_liquid_material_name="NMP",
|
||
# speed="400",
|
||
# temperature="60",
|
||
# delay_time="1200",
|
||
# hold_m_name="8.4分装板-1"
|
||
# )
|
||
|
||
# vial_result = bioyond.create_90_10_vial_feeding_task(
|
||
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
|
||
# order_name="90%10%小瓶投料-2",
|
||
# percent_90_1_assign_material_name="BPDA-1",
|
||
# percent_90_1_target_weigh="5.006",
|
||
# percent_90_2_assign_material_name="PMDA-1",
|
||
# percent_90_2_target_weigh="3.810",
|
||
# percent_90_3_assign_material_name="BPDA-1",
|
||
# percent_90_3_target_weigh="8.399",
|
||
# percent_10_1_assign_material_name="BPDA-1",
|
||
# percent_10_1_target_weigh="1.200",
|
||
# percent_10_1_volume="20",
|
||
# percent_10_1_liquid_material_name="NMP",
|
||
# percent_10_2_assign_material_name="BPDA-1",
|
||
# percent_10_2_target_weigh="1.200",
|
||
# percent_10_2_volume="20",
|
||
# percent_10_2_liquid_material_name="NMP",
|
||
# speed="400",
|
||
# temperature="60",
|
||
# delay_time="1200",
|
||
# hold_m_name="8.4分装板-2"
|
||
# )
|
||
|
||
#启动调度器
|
||
#bioyond.scheduler_start()
|
||
|
||
#继续调度器
|
||
#bioyond.scheduler_continue()
|
||
|
||
result0 = bioyond.stock_material('{"typeMode": 0, "includeDetail": true}')
|
||
result1 = bioyond.stock_material('{"typeMode": 1, "includeDetail": true}')
|
||
result2 = bioyond.stock_material('{"typeMode": 2, "includeDetail": true}')
|
||
|
||
matpos1 = bioyond.query_warehouse_by_material_type("3a14196e-b7a0-a5da-1931-35f3000281e9")
|
||
matpos2 = bioyond.query_warehouse_by_material_type("3a14196e-5dfe-6e21-0c79-fe2036d052c4")
|
||
matpos3 = bioyond.query_warehouse_by_material_type("3a14196b-24f2-ca49-9081-0cab8021bf1a")
|
||
|
||
#样品板(里面有样品瓶)
|
||
material_data_yp = {
|
||
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
|
||
#"code": "物料编码001",
|
||
#"barCode": "物料条码001",
|
||
"name": "8.4样品板",
|
||
"unit": "个",
|
||
"quantity": 1,
|
||
"details": [
|
||
{
|
||
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
|
||
#"code": "物料编码001",
|
||
"name": "BTDA-1",
|
||
"quantity": 20,
|
||
"x": 1,
|
||
"y": 1,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
|
||
#"code": "物料编码001",
|
||
"name": "BPDA-1",
|
||
"quantity": 20,
|
||
"x": 2,
|
||
"y": 1, #x1y2是A02
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
|
||
#"code": "物料编码001",
|
||
"name": "BTDA-2",
|
||
"quantity": 20,
|
||
"x": 1,
|
||
"y": 2, #x1y2是A02
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
|
||
#"code": "物料编码001",
|
||
"name": "PMDA-1",
|
||
"quantity": 20,
|
||
"x": 2,
|
||
"y": 2, #x1y2是A02
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
}
|
||
],
|
||
"Parameters":"{}"
|
||
}
|
||
|
||
material_data_yp = {
|
||
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
|
||
#"code": "物料编码001",
|
||
#"barCode": "物料条码001",
|
||
"name": "8.7样品板",
|
||
"unit": "个",
|
||
"quantity": 1,
|
||
"details": [
|
||
{
|
||
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
|
||
#"code": "物料编码001",
|
||
"name": "mianfen",
|
||
"quantity": 13,
|
||
"x": 1,
|
||
"y": 1,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
|
||
#"code": "物料编码001",
|
||
"name": "mianfen2",
|
||
"quantity": 13,
|
||
"x": 1,
|
||
"y": 2, #x1y2是A02
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
}
|
||
],
|
||
"Parameters":"{}"
|
||
}
|
||
|
||
#分装板
|
||
material_data_fzb_1 = {
|
||
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
|
||
#"code": "物料编码001",
|
||
#"barCode": "物料条码001",
|
||
"name": "8.7分装板",
|
||
"unit": "个",
|
||
"quantity": 1,
|
||
"details": [
|
||
{
|
||
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
|
||
#"code": "物料编码001",
|
||
"name": "10%小瓶1",
|
||
"quantity": 1,
|
||
"x": 1,
|
||
"y": 1,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
|
||
#"code": "物料编码001",
|
||
"name": "10%小瓶2",
|
||
"quantity": 1,
|
||
"x": 1,
|
||
"y": 2,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
|
||
#"code": "物料编码001",
|
||
"name": "10%小瓶3",
|
||
"quantity": 1,
|
||
"x": 1,
|
||
"y": 3,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
|
||
#"code": "物料编码001",
|
||
"name": "90%小瓶1",
|
||
"quantity": 1,
|
||
"x": 2,
|
||
"y": 1, #x1y2是A02
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
|
||
#"code": "物料编码001",
|
||
"name": "90%小瓶2",
|
||
"quantity": 1,
|
||
"x": 2,
|
||
"y": 2,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
|
||
#"code": "物料编码001",
|
||
"name": "90%小瓶3",
|
||
"quantity": 1,
|
||
"x": 2,
|
||
"y": 3,
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
}
|
||
],
|
||
"Parameters":"{}"
|
||
}
|
||
|
||
material_data_fzb_2 = {
|
||
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
|
||
#"code": "物料编码001",
|
||
#"barCode": "物料条码001",
|
||
"name": "8.4分装板-2",
|
||
"unit": "个",
|
||
"quantity": 1,
|
||
"details": [
|
||
{
|
||
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
|
||
#"code": "物料编码001",
|
||
"name": "10%小瓶1",
|
||
"quantity": 1,
|
||
"x": 1,
|
||
"y": 1,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
|
||
#"code": "物料编码001",
|
||
"name": "10%小瓶2",
|
||
"quantity": 1,
|
||
"x": 1,
|
||
"y": 2,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
|
||
#"code": "物料编码001",
|
||
"name": "10%小瓶3",
|
||
"quantity": 1,
|
||
"x": 1,
|
||
"y": 3,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
|
||
#"code": "物料编码001",
|
||
"name": "90%小瓶1",
|
||
"quantity": 1,
|
||
"x": 2,
|
||
"y": 1, #x1y2是A02
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
|
||
#"code": "物料编码001",
|
||
"name": "90%小瓶2",
|
||
"quantity": 1,
|
||
"x": 2,
|
||
"y": 2,
|
||
#"unit": "单位"
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
},
|
||
{
|
||
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
|
||
#"code": "物料编码001",
|
||
"name": "90%小瓶3",
|
||
"quantity": 1,
|
||
"x": 2,
|
||
"y": 3,
|
||
"molecular": 1,
|
||
"Parameters":"{\"molecular\": 1}"
|
||
}
|
||
],
|
||
"Parameters":"{}"
|
||
}
|
||
|
||
#烧杯
|
||
material_data_sb_oda = {
|
||
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
|
||
#"code": "物料编码001",
|
||
#"barCode": "物料条码001",
|
||
"name": "mianfen1",
|
||
"unit": "个",
|
||
"quantity": 1,
|
||
"Parameters":"{}"
|
||
}
|
||
|
||
material_data_sb_pda_2 = {
|
||
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
|
||
#"code": "物料编码001",
|
||
#"barCode": "物料条码001",
|
||
"name": "mianfen2",
|
||
"unit": "个",
|
||
"quantity": 1,
|
||
"Parameters":"{}"
|
||
}
|
||
|
||
# material_data_sb_mpda = {
|
||
# "typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
|
||
# #"code": "物料编码001",
|
||
# #"barCode": "物料条码001",
|
||
# "name": "烧杯MPDA",
|
||
# "unit": "个",
|
||
# "quantity": 1,
|
||
# "Parameters":"{}"
|
||
# }
|
||
|
||
|
||
#result_1 = bioyond.add_material(json.dumps(material_data_yp, ensure_ascii=False))
|
||
#result_2 = bioyond.add_material(json.dumps(material_data_fzb_1, ensure_ascii=False))
|
||
# result_3 = bioyond.add_material(json.dumps(material_data_fzb_2, ensure_ascii=False))
|
||
# result_4 = bioyond.add_material(json.dumps(material_data_sb_oda, ensure_ascii=False))
|
||
# result_5 = bioyond.add_material(json.dumps(material_data_sb_pda_2, ensure_ascii=False))
|
||
# #result会返回id
|
||
# #样品板1id:3a1b3e7d-339d-0291-dfd3-13e2a78fe521
|
||
|
||
|
||
# #将指定物料入库到指定库位
|
||
#bioyond.material_inbound(result_1, "3a14198e-6929-31f0-8a22-0f98f72260df")
|
||
#bioyond.material_inbound(result_2, "3a14198e-6929-46fe-841e-03dd753f1e4a")
|
||
# bioyond.material_inbound(result_3, "3a14198e-6929-72ac-32ce-9b50245682b8")
|
||
# bioyond.material_inbound(result_4, "3a14198e-d724-e036-afdc-2ae39a7f3383")
|
||
# bioyond.material_inbound(result_5, "3a14198e-d724-d818-6d4f-5725191a24b5")
|
||
|
||
#bioyond.material_outbound(result_1, "3a14198e-6929-31f0-8a22-0f98f72260df")
|
||
|
||
# bioyond.stock_material('{"typeMode": 2, "includeDetail": true}')
|
||
|
||
query_order = {"status":"100", "pageCount": "10"}
|
||
bioyond.order_query(json.dumps(query_order, ensure_ascii=False))
|
||
|
||
# id = "3a1bce3c-4f31-c8f3-5525-f3b273bc34dc"
|
||
# bioyond.sample_waste_removal(id)
|