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>
791 lines
32 KiB
Python
791 lines
32 KiB
Python
import json
|
||
import requests
|
||
from typing import List, Dict, Any
|
||
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
|
||
from unilabos.devices.workstation.bioyond_studio.config import (
|
||
WORKFLOW_STEP_IDS,
|
||
WORKFLOW_TO_SECTION_MAP,
|
||
ACTION_NAMES
|
||
)
|
||
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG
|
||
|
||
|
||
class BioyondReactionStation(BioyondWorkstation):
|
||
"""Bioyond反应站类
|
||
|
||
继承自BioyondWorkstation,提供反应站特定的业务方法
|
||
"""
|
||
|
||
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
|
||
"""初始化反应站
|
||
|
||
Args:
|
||
config: 配置字典,应包含workflow_mappings等配置
|
||
deck: Deck对象
|
||
protocol_type: 协议类型(由ROS系统传递,此处忽略)
|
||
**kwargs: 其他可能的参数
|
||
"""
|
||
if deck is None and config:
|
||
deck = config.get('deck')
|
||
|
||
print(f"BioyondReactionStation初始化 - config包含workflow_mappings: {'workflow_mappings' in (config or {})}")
|
||
if config and 'workflow_mappings' in config:
|
||
print(f"workflow_mappings内容: {config['workflow_mappings']}")
|
||
|
||
super().__init__(bioyond_config=config, deck=deck)
|
||
|
||
print(f"BioyondReactionStation初始化完成 - workflow_mappings: {self.workflow_mappings}")
|
||
print(f"workflow_mappings长度: {len(self.workflow_mappings)}")
|
||
|
||
# ==================== 工作流方法 ====================
|
||
|
||
def reactor_taken_out(self):
|
||
"""反应器取出"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_out"}')
|
||
reactor_taken_out_params = {"param_values": {}}
|
||
self.pending_task_params.append(reactor_taken_out_params)
|
||
print(f"成功添加反应器取出工作流")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def reactor_taken_in(
|
||
self,
|
||
assign_material_name: str,
|
||
cutoff: str = "900000",
|
||
temperature: float = -10.00
|
||
):
|
||
"""反应器放入
|
||
|
||
Args:
|
||
assign_material_name: 物料名称(不能为空)
|
||
cutoff: 粘度上限(需为有效数字字符串,默认 "900000")
|
||
temperature: 温度设定(°C,范围:-50.00 至 100.00)
|
||
|
||
Returns:
|
||
str: JSON 字符串,格式为 {"suc": True}
|
||
|
||
Raises:
|
||
ValueError: 若物料名称无效或 cutoff 格式错误
|
||
"""
|
||
if not assign_material_name:
|
||
raise ValueError("物料名称不能为空")
|
||
try:
|
||
float(cutoff)
|
||
except ValueError:
|
||
raise ValueError("cutoff 必须是有效的数字字符串")
|
||
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_in"}')
|
||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
||
if material_id is None:
|
||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
step_id = WORKFLOW_STEP_IDS["reactor_taken_in"]["config"]
|
||
reactor_taken_in_params = {
|
||
"param_values": {
|
||
step_id: {
|
||
ACTION_NAMES["reactor_taken_in"]["config"]: [
|
||
{"m": 0, "n": 3, "Key": "cutoff", "Value": cutoff},
|
||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
|
||
],
|
||
ACTION_NAMES["reactor_taken_in"]["stirring"]: [
|
||
{"m": 0, "n": 3, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(reactor_taken_in_params)
|
||
print(f"成功添加反应器放入参数: material={assign_material_name}->ID:{material_id}, cutoff={cutoff}, temp={temperature:.2f}")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def solid_feeding_vials(
|
||
self,
|
||
material_id: str,
|
||
time: str = "0",
|
||
torque_variation: int = 1,
|
||
assign_material_name: str = None,
|
||
temperature: float = 25.00
|
||
):
|
||
"""固体进料小瓶
|
||
|
||
Args:
|
||
material_id: 粉末类型ID,1=盐(21分钟),2=面粉(27分钟),3=BTDA(38分钟)
|
||
time: 观察时间(分钟)
|
||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||
assign_material_name: 物料名称(用于获取试剂瓶位ID)
|
||
temperature: 温度设定(°C)
|
||
"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Solid_feeding_vials"}')
|
||
material_id_m = self.hardware_interface._get_material_id_by_name(assign_material_name) if assign_material_name else None
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
feeding_step_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["feeding"]
|
||
observe_step_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["observe"]
|
||
|
||
solid_feeding_vials_params = {
|
||
"param_values": {
|
||
feeding_step_id: {
|
||
ACTION_NAMES["solid_feeding_vials"]["feeding"]: [
|
||
{"m": 0, "n": 3, "Key": "materialId", "Value": material_id},
|
||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id_m} if material_id_m else {}
|
||
]
|
||
},
|
||
observe_step_id: {
|
||
ACTION_NAMES["solid_feeding_vials"]["observe"]: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(solid_feeding_vials_params)
|
||
print(f"成功添加固体进料小瓶参数: material_id={material_id}, time={time}min, torque={torque_variation}, temp={temperature:.2f}°C")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def liquid_feeding_vials_non_titration(
|
||
self,
|
||
volume_formula: str,
|
||
assign_material_name: str,
|
||
titration_type: str = "1",
|
||
time: str = "0",
|
||
torque_variation: int = 1,
|
||
temperature: float = 25.00
|
||
):
|
||
"""液体进料小瓶(非滴定)
|
||
|
||
Args:
|
||
volume_formula: 分液公式(μL)
|
||
assign_material_name: 物料名称
|
||
titration_type: 是否滴定(1=否, 2=是)
|
||
time: 观察时间(分钟)
|
||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||
temperature: 温度(°C)
|
||
"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_vials(non-titration)"}')
|
||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
||
if material_id is None:
|
||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["liquid"]
|
||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_step_id: {
|
||
ACTION_NAMES["liquid_feeding_vials_non_titration"]["liquid"]: [
|
||
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volume_formula},
|
||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id},
|
||
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type}
|
||
]
|
||
},
|
||
observe_step_id: {
|
||
ACTION_NAMES["liquid_feeding_vials_non_titration"]["observe"]: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(params)
|
||
print(f"成功添加液体进料小瓶(非滴定)参数: volume={volume_formula}μL, material={assign_material_name}->ID:{material_id}")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def liquid_feeding_solvents(
|
||
self,
|
||
assign_material_name: str,
|
||
volume: str = None,
|
||
solvents = None,
|
||
titration_type: str = "1",
|
||
time: str = "360",
|
||
torque_variation: int = 2,
|
||
temperature: float = 25.00
|
||
):
|
||
"""液体进料-溶剂
|
||
|
||
Args:
|
||
assign_material_name: 物料名称
|
||
volume: 分液量(μL),直接指定体积(可选,如果提供solvents则自动计算)
|
||
solvents: 溶剂信息的字典或JSON字符串(可选),格式如下:
|
||
{
|
||
"additional_solvent": 33.55092503597727, # 溶剂体积(mL)
|
||
"total_liquid_volume": 48.00916988195499
|
||
}
|
||
如果提供solvents,则从中提取additional_solvent并转换为μL
|
||
titration_type: 是否滴定(1=否, 2=是)
|
||
time: 观察时间(分钟)
|
||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||
temperature: 温度设定(°C)
|
||
"""
|
||
# 处理 volume 参数:优先使用直接传入的 volume,否则从 solvents 中提取
|
||
if not volume and solvents is not None:
|
||
# 参数类型转换:如果是字符串则解析为字典
|
||
if isinstance(solvents, str):
|
||
try:
|
||
solvents = json.loads(solvents)
|
||
except json.JSONDecodeError as e:
|
||
raise ValueError(f"solvents参数JSON解析失败: {str(e)}")
|
||
|
||
# 参数验证
|
||
if not isinstance(solvents, dict):
|
||
raise ValueError("solvents 必须是字典类型或有效的JSON字符串")
|
||
|
||
# 提取 additional_solvent 值
|
||
additional_solvent = solvents.get("additional_solvent")
|
||
if additional_solvent is None:
|
||
raise ValueError("solvents 中没有找到 additional_solvent 字段")
|
||
|
||
# 转换为微升(μL) - 从毫升(mL)转换
|
||
volume = str(float(additional_solvent) * 1000)
|
||
elif volume is None:
|
||
raise ValueError("必须提供 volume 或 solvents 参数之一")
|
||
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_solvents"}')
|
||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
||
if material_id is None:
|
||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["liquid"]
|
||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_step_id: {
|
||
ACTION_NAMES["liquid_feeding_solvents"]["liquid"]: [
|
||
{"m": 0, "n": 1, "Key": "titrationType", "Value": titration_type},
|
||
{"m": 0, "n": 1, "Key": "volume", "Value": volume},
|
||
{"m": 0, "n": 1, "Key": "assignMaterialName", "Value": material_id}
|
||
]
|
||
},
|
||
observe_step_id: {
|
||
ACTION_NAMES["liquid_feeding_solvents"]["observe"]: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(params)
|
||
print(f"成功添加液体进料溶剂参数: material={assign_material_name}->ID:{material_id}, volume={volume}μL")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def liquid_feeding_titration(
|
||
self,
|
||
volume_formula: str,
|
||
assign_material_name: str,
|
||
titration_type: str = "1",
|
||
time: str = "90",
|
||
torque_variation: int = 2,
|
||
temperature: float = 25.00
|
||
):
|
||
"""液体进料(滴定)
|
||
|
||
Args:
|
||
volume_formula: 分液公式(μL)
|
||
assign_material_name: 物料名称
|
||
titration_type: 是否滴定(1=否, 2=是)
|
||
time: 观察时间(分钟)
|
||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||
temperature: 温度(°C)
|
||
"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}')
|
||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
||
if material_id is None:
|
||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["liquid"]
|
||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_step_id: {
|
||
ACTION_NAMES["liquid_feeding_titration"]["liquid"]: [
|
||
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volume_formula},
|
||
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type},
|
||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
|
||
]
|
||
},
|
||
observe_step_id: {
|
||
ACTION_NAMES["liquid_feeding_titration"]["observe"]: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(params)
|
||
print(f"成功添加液体进料滴定参数: volume={volume_formula}μL, material={assign_material_name}->ID:{material_id}")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def liquid_feeding_beaker(
|
||
self,
|
||
volume: str = "350",
|
||
assign_material_name: str = "BAPP",
|
||
time: str = "0",
|
||
torque_variation: int = 1,
|
||
titration_type: str = "1",
|
||
temperature: float = 25.00
|
||
):
|
||
"""液体进料烧杯
|
||
|
||
Args:
|
||
volume: 分液质量(g)
|
||
assign_material_name: 物料名称(试剂瓶位)
|
||
time: 观察时间(分钟)
|
||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||
titration_type: 是否滴定(1=否, 2=是)
|
||
temperature: 温度设定(°C)
|
||
"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "liquid_feeding_beaker"}')
|
||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
||
if material_id is None:
|
||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["liquid"]
|
||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_step_id: {
|
||
ACTION_NAMES["liquid_feeding_beaker"]["liquid"]: [
|
||
{"m": 0, "n": 2, "Key": "volume", "Value": volume},
|
||
{"m": 0, "n": 2, "Key": "assignMaterialName", "Value": material_id},
|
||
{"m": 0, "n": 2, "Key": "titrationType", "Value": titration_type}
|
||
]
|
||
},
|
||
observe_step_id: {
|
||
ACTION_NAMES["liquid_feeding_beaker"]["observe"]: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(params)
|
||
print(f"成功添加液体进料烧杯参数: volume={volume}μL, material={assign_material_name}->ID:{material_id}")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def drip_back(
|
||
self,
|
||
assign_material_name: str,
|
||
volume: str,
|
||
titration_type: str = "1",
|
||
time: str = "90",
|
||
torque_variation: int = 2,
|
||
temperature: float = 25.00
|
||
):
|
||
"""滴回去
|
||
|
||
Args:
|
||
assign_material_name: 物料名称(液体种类)
|
||
volume: 分液量(μL)
|
||
titration_type: 是否滴定(1=否, 2=是)
|
||
time: 观察时间(分钟)
|
||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||
temperature: 温度(°C)
|
||
"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "drip_back"}')
|
||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
||
if material_id is None:
|
||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_step_id = WORKFLOW_STEP_IDS["drip_back"]["liquid"]
|
||
observe_step_id = WORKFLOW_STEP_IDS["drip_back"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_step_id: {
|
||
ACTION_NAMES["drip_back"]["liquid"]: [
|
||
{"m": 0, "n": 1, "Key": "titrationType", "Value": titration_type},
|
||
{"m": 0, "n": 1, "Key": "assignMaterialName", "Value": material_id},
|
||
{"m": 0, "n": 1, "Key": "volume", "Value": volume}
|
||
]
|
||
},
|
||
observe_step_id: {
|
||
ACTION_NAMES["drip_back"]["observe"]: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(params)
|
||
print(f"成功添加滴回去参数: material={assign_material_name}->ID:{material_id}, volume={volume}μL")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
# ==================== 工作流管理方法 ====================
|
||
|
||
def get_workflow_sequence(self) -> List[str]:
|
||
"""获取当前工作流执行顺序
|
||
|
||
Returns:
|
||
工作流名称列表
|
||
"""
|
||
id_to_name = {workflow_id: name for name, workflow_id in self.workflow_mappings.items()}
|
||
workflow_names = []
|
||
for workflow_id in self.workflow_sequence:
|
||
workflow_name = id_to_name.get(workflow_id, workflow_id)
|
||
workflow_names.append(workflow_name)
|
||
print(f"工作流序列: {workflow_names}")
|
||
return workflow_names
|
||
|
||
def workflow_step_query(self, workflow_id: str) -> dict:
|
||
"""查询工作流步骤参数
|
||
|
||
Args:
|
||
workflow_id: 工作流ID
|
||
|
||
Returns:
|
||
工作流步骤参数字典
|
||
"""
|
||
return self.hardware_interface.workflow_step_query(workflow_id)
|
||
|
||
def create_order(self, json_str: str) -> dict:
|
||
"""创建订单
|
||
|
||
Args:
|
||
json_str: 订单参数的JSON字符串
|
||
|
||
Returns:
|
||
创建结果
|
||
"""
|
||
return self.hardware_interface.create_order(json_str)
|
||
|
||
# ==================== 工作流执行核心方法 ====================
|
||
|
||
def process_web_workflows(self, web_workflow_json: str) -> List[Dict[str, str]]:
|
||
"""处理网页工作流列表
|
||
|
||
Args:
|
||
web_workflow_json: JSON 格式的网页工作流列表
|
||
|
||
Returns:
|
||
List[Dict[str, str]]: 包含工作流 ID 和名称的字典列表
|
||
"""
|
||
try:
|
||
web_workflow_data = json.loads(web_workflow_json)
|
||
web_workflow_list = web_workflow_data.get("web_workflow_list", [])
|
||
workflows_result = []
|
||
for name in web_workflow_list:
|
||
workflow_id = self.workflow_mappings.get(name, "")
|
||
if not workflow_id:
|
||
print(f"警告:未找到工作流名称 {name} 对应的 ID")
|
||
continue
|
||
workflows_result.append({"id": workflow_id, "name": name})
|
||
print(f"process_web_workflows 输出: {workflows_result}")
|
||
return workflows_result
|
||
except json.JSONDecodeError as e:
|
||
print(f"错误:无法解析 web_workflow_json: {e}")
|
||
return []
|
||
except Exception as e:
|
||
print(f"错误:处理工作流失败: {e}")
|
||
return []
|
||
|
||
def process_and_execute_workflow(self, workflow_name: str, task_name: str) -> dict:
|
||
"""
|
||
一站式处理工作流程:解析网页工作流列表,合并工作流(带参数),然后发布任务
|
||
|
||
Args:
|
||
workflow_name: 合并后的工作流名称
|
||
task_name: 任务名称
|
||
|
||
Returns:
|
||
任务创建结果
|
||
"""
|
||
web_workflow_list = self.get_workflow_sequence()
|
||
print(f"\n{'='*60}")
|
||
print(f"📋 处理网页工作流列表: {web_workflow_list}")
|
||
print(f"{'='*60}")
|
||
|
||
web_workflow_json = json.dumps({"web_workflow_list": web_workflow_list})
|
||
workflows_result = self.process_web_workflows(web_workflow_json)
|
||
|
||
if not workflows_result:
|
||
return self._create_error_result("处理网页工作流列表失败", "process_web_workflows")
|
||
|
||
print(f"workflows_result 类型: {type(workflows_result)}")
|
||
print(f"workflows_result 内容: {workflows_result}")
|
||
|
||
workflows_with_params = self._build_workflows_with_parameters(workflows_result)
|
||
|
||
merge_data = {
|
||
"name": workflow_name,
|
||
"workflows": workflows_with_params
|
||
}
|
||
|
||
# print(f"\n🔄 合并工作流(带参数),名称: {workflow_name}")
|
||
merged_workflow = self.merge_workflow_with_parameters(json.dumps(merge_data))
|
||
|
||
if not merged_workflow:
|
||
return self._create_error_result("合并工作流失败", "merge_workflow_with_parameters")
|
||
|
||
workflow_id = merged_workflow.get("subWorkflows", [{}])[0].get("id", "")
|
||
# print(f"\n📤 使用工作流创建任务: {workflow_name} (ID: {workflow_id})")
|
||
|
||
order_params = [{
|
||
"orderCode": f"task_{self.hardware_interface.get_current_time_iso8601()}",
|
||
"orderName": task_name,
|
||
"workFlowId": workflow_id,
|
||
"borderNumber": 1,
|
||
"paramValues": {}
|
||
}]
|
||
|
||
result = self.create_order(json.dumps(order_params))
|
||
|
||
if not result:
|
||
return self._create_error_result("创建任务失败", "create_order")
|
||
|
||
# 清空工作流序列和参数,防止下次执行时累积重复
|
||
self.pending_task_params = []
|
||
self.clear_workflows() # 清空工作流序列,避免重复累积
|
||
|
||
# print(f"\n✅ 任务创建成功: {result}")
|
||
# print(f"\n✅ 任务创建成功")
|
||
print(f"{'='*60}\n")
|
||
|
||
# 返回结果,包含合并后的工作流数据和订单参数
|
||
return json.dumps({
|
||
"success": True,
|
||
"result": result,
|
||
"merged_workflow": merged_workflow,
|
||
"order_params": order_params
|
||
})
|
||
|
||
def _build_workflows_with_parameters(self, workflows_result: list) -> list:
|
||
"""
|
||
构建带参数的工作流列表
|
||
|
||
Args:
|
||
workflows_result: 处理后的工作流列表(应为包含 id 和 name 的字典列表)
|
||
|
||
Returns:
|
||
符合新接口格式的工作流参数结构
|
||
"""
|
||
workflows_with_params = []
|
||
total_params = 0
|
||
successful_params = 0
|
||
failed_params = []
|
||
|
||
for idx, workflow_info in enumerate(workflows_result):
|
||
if not isinstance(workflow_info, dict):
|
||
print(f"错误:workflows_result[{idx}] 不是字典,而是 {type(workflow_info)}: {workflow_info}")
|
||
continue
|
||
workflow_id = workflow_info.get("id")
|
||
if not workflow_id:
|
||
print(f"警告:workflows_result[{idx}] 缺少 'id' 键")
|
||
continue
|
||
workflow_name = workflow_info.get("name", "")
|
||
# print(f"\n🔧 处理工作流 [{idx}]: {workflow_name} (ID: {workflow_id})")
|
||
|
||
if idx >= len(self.pending_task_params):
|
||
# print(f" ⚠️ 无对应参数,跳过")
|
||
workflows_with_params.append({"id": workflow_id})
|
||
continue
|
||
|
||
param_data = self.pending_task_params[idx]
|
||
param_values = param_data.get("param_values", {})
|
||
if not param_values:
|
||
# print(f" ⚠️ 参数为空,跳过")
|
||
workflows_with_params.append({"id": workflow_id})
|
||
continue
|
||
|
||
step_parameters = {}
|
||
for step_id, actions_dict in param_values.items():
|
||
# print(f" 📍 步骤ID: {step_id}")
|
||
for action_name, param_list in actions_dict.items():
|
||
# print(f" 🔹 模块: {action_name}, 参数数量: {len(param_list)}")
|
||
if step_id not in step_parameters:
|
||
step_parameters[step_id] = {}
|
||
if action_name not in step_parameters[step_id]:
|
||
step_parameters[step_id][action_name] = []
|
||
for param_item in param_list:
|
||
param_key = param_item.get("Key", "")
|
||
param_value = param_item.get("Value", "")
|
||
total_params += 1
|
||
step_parameters[step_id][action_name].append({
|
||
"Key": param_key,
|
||
"DisplayValue": param_value,
|
||
"Value": param_value
|
||
})
|
||
successful_params += 1
|
||
# print(f" ✓ {param_key} = {param_value}")
|
||
|
||
workflows_with_params.append({
|
||
"id": workflow_id,
|
||
"stepParameters": step_parameters
|
||
})
|
||
|
||
self._print_mapping_stats(total_params, successful_params, failed_params)
|
||
return workflows_with_params
|
||
|
||
def _print_mapping_stats(self, total: int, success: int, failed: list):
|
||
"""打印参数映射统计"""
|
||
print(f"\n{'='*20} 参数映射统计 {'='*20}")
|
||
print(f"📊 总参数数量: {total}")
|
||
print(f"✅ 成功映射: {success}")
|
||
print(f"❌ 映射失败: {len(failed)}")
|
||
if not failed:
|
||
print("🎉 成功映射所有参数!")
|
||
else:
|
||
print(f"⚠️ 失败的参数: {', '.join(failed)}")
|
||
success_rate = (success/total*100) if total > 0 else 0
|
||
print(f"📈 映射成功率: {success_rate:.1f}%")
|
||
print("="*60)
|
||
|
||
def _create_error_result(self, error_msg: str, step: str) -> str:
|
||
"""创建统一的错误返回格式"""
|
||
print(f"❌ {error_msg}")
|
||
return json.dumps({
|
||
"success": False,
|
||
"error": f"process_and_execute_workflow: {error_msg}",
|
||
"method": "process_and_execute_workflow",
|
||
"step": step
|
||
})
|
||
|
||
def merge_workflow_with_parameters(self, json_str: str) -> dict:
|
||
"""
|
||
调用新接口:合并工作流并传递参数
|
||
|
||
Args:
|
||
json_str: JSON格式的字符串,包含:
|
||
- name: 工作流名称
|
||
- workflows: [{"id": "工作流ID", "stepParameters": {...}}]
|
||
|
||
Returns:
|
||
合并后的工作流信息
|
||
"""
|
||
try:
|
||
data = json.loads(json_str)
|
||
|
||
# 在工作流名称后面添加时间戳,避免重复
|
||
if "name" in data and data["name"]:
|
||
timestamp = self.hardware_interface.get_current_time_iso8601().replace(":", "-").replace(".", "-")
|
||
original_name = data["name"]
|
||
data["name"] = f"{original_name}_{timestamp}"
|
||
print(f"🕒 工作流名称已添加时间戳: {original_name} -> {data['name']}")
|
||
|
||
request_data = {
|
||
"apiKey": API_CONFIG["api_key"],
|
||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
||
"data": data
|
||
}
|
||
print(f"\n📤 发送合并请求:")
|
||
print(f" 工作流名称: {data.get('name')}")
|
||
print(f" 子工作流数量: {len(data.get('workflows', []))}")
|
||
|
||
# 打印完整的POST请求内容
|
||
print(f"\n🔍 POST请求详细内容:")
|
||
print(f" URL: {self.hardware_interface.host}/api/lims/workflow/merge-workflow-with-parameters")
|
||
print(f" Headers: {{'Content-Type': 'application/json'}}")
|
||
print(f" Request Data:")
|
||
print(f" {json.dumps(request_data, indent=4, ensure_ascii=False)}")
|
||
#
|
||
response = requests.post(
|
||
f"{self.hardware_interface.host}/api/lims/workflow/merge-workflow-with-parameters",
|
||
json=request_data,
|
||
headers={"Content-Type": "application/json"},
|
||
timeout=30
|
||
)
|
||
|
||
# # 打印响应详细内容
|
||
# print(f"\n📥 POST响应详细内容:")
|
||
# print(f" 状态码: {response.status_code}")
|
||
# print(f" 响应头: {dict(response.headers)}")
|
||
# print(f" 响应体: {response.text}")
|
||
# #
|
||
try:
|
||
result = response.json()
|
||
# #
|
||
# print(f"\n📋 解析后的响应JSON:")
|
||
# print(f" {json.dumps(result, indent=4, ensure_ascii=False)}")
|
||
# #
|
||
except json.JSONDecodeError:
|
||
print(f"❌ 服务器返回非 JSON 格式响应: {response.text}")
|
||
return None
|
||
|
||
if result.get("code") == 1:
|
||
print(f"✅ 工作流合并成功(带参数)")
|
||
return result.get("data", {})
|
||
else:
|
||
error_msg = result.get('message', '未知错误')
|
||
print(f"❌ 工作流合并失败: {error_msg}")
|
||
return None
|
||
|
||
except requests.exceptions.Timeout:
|
||
print(f"❌ 合并工作流请求超时")
|
||
return None
|
||
except requests.exceptions.RequestException as e:
|
||
print(f"❌ 合并工作流网络异常: {str(e)}")
|
||
return None
|
||
except json.JSONDecodeError as e:
|
||
print(f"❌ 合并工作流响应解析失败: {str(e)}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"❌ 合并工作流异常: {str(e)}")
|
||
return None
|
||
|
||
def _validate_and_refresh_workflow_if_needed(self, workflow_name: str) -> bool:
|
||
"""验证工作流ID是否有效,如果无效则重新合并
|
||
|
||
Args:
|
||
workflow_name: 工作流名称
|
||
|
||
Returns:
|
||
bool: 验证或刷新是否成功
|
||
"""
|
||
print(f"\n🔍 验证工作流ID有效性...")
|
||
if not self.workflow_sequence:
|
||
print(f" ⚠️ 工作流序列为空,需要重新合并")
|
||
return False
|
||
first_workflow_id = self.workflow_sequence[0]
|
||
try:
|
||
structure = self.workflow_step_query(first_workflow_id)
|
||
if structure:
|
||
print(f" ✅ 工作流ID有效")
|
||
return True
|
||
else:
|
||
print(f" ⚠️ 工作流ID已过期,需要重新合并")
|
||
return False
|
||
except Exception as e:
|
||
print(f" ❌ 工作流ID验证失败: {e}")
|
||
print(f" 💡 将重新合并工作流")
|
||
return False
|