mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-18 05:21:19 +00:00
* Cleanup registry to be easy-understanding (#76) * delete deprecated mock devices * rename categories * combine chromatographic devices * rename rviz simulation nodes * organic virtual devices * parse vessel_id * run registry completion before merge --------- Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com> * fix: workstation handlers and vessel_id parsing * fix: working dir error when input config path feat: report publish topic when error * modify default discovery_interval to 15s * feat: add trace log level * feat: 添加ChinWe设备控制类,支持串口通信和电机控制功能 (#79) * fix: drop_tips not using auto resource select * fix: discard_tips error * fix: discard_tips * fix: prcxi_res * add: prcxi res fix: startup slow * feat: workstation example * fix pumps and liquid_handler handle * feat: 优化protocol node节点运行日志 * fix all protocol_compilers and remove deprecated devices * feat: 新增use_remote_resource参数 * fix and remove redundant info * bugfixes on organic protocols * fix filter protocol * fix protocol node * 临时兼容错误的driver写法 * fix: prcxi import error * use call_async in all service to avoid deadlock * fix: figure_resource * Update recipe.yaml * add workstation template and battery example * feat: add sk & ak * update workstation base * Create workstation_architecture.md * refactor: workstation_base 重构为仅含业务逻辑,通信和子设备管理交给 ProtocolNode * refactor: ProtocolNode→WorkstationNode * Add:msgs.action (#83) * update: Workstation dev 将版本号从 0.10.3 更新为 0.10.4 (#84) * Add:msgs.action * update: 将版本号从 0.10.3 更新为 0.10.4 * simplify resource system * uncompleted refactor * example for use WorkstationBase * feat: websocket * feat: websocket test * feat: workstation example * feat: action status * fix: station自己的方法注册错误 * fix: 还原protocol node处理方法 * fix: build * fix: missing job_id key * ws test version 1 * ws test version 2 * ws protocol * 增加物料关系上传日志 * 增加物料关系上传日志 * 修正物料关系上传 * 修复工站的tracker实例追踪失效问题 * 增加handle检测,增加material edge关系上传 * 修复event loop错误 * 修复edge上报错误 * 修复async错误 * 更新schema的title字段 * 主机节点信息等支持自动刷新 * 注册表编辑器 * 修复status密集发送时,消息出错 * 增加addr参数 * fix: addr param * fix: addr param * 取消labid 和 强制config输入 * Add action definitions for LiquidHandlerSetGroup and LiquidHandlerTransferGroup - Created LiquidHandlerSetGroup.action with fields for group name, wells, and volumes. - Created LiquidHandlerTransferGroup.action with fields for source and target group names and unit volume. - Both actions include response fields for return information and success status. * Add LiquidHandlerSetGroup and LiquidHandlerTransferGroup actions to CMakeLists * Add set_group and transfer_group methods to PRCXI9300Handler and update liquid_handler.yaml * result_info改为字典类型 * 新增uat的地址替换 * runze multiple pump support (cherry picked from commit49354fcf39) * remove runze multiple software obtainer (cherry picked from commit8bcc92a394) * support multiple backbone (cherry picked from commit4771ff2347) * Update runze pump format * Correct runze multiple backbone * Update runze_multiple_backbone * Correct runze pump multiple receive method. * Correct runze pump multiple receive method. * 对于PRCXI9320的transfer_group,一对多和多对多 * 移除MQTT,更新launch文档,提供注册表示例文件,更新到0.10.5 * fix import error * fix dupe upload registry * refactor ws client * add server timeout * Fix: run-column with correct vessel id (#86) * fix run_column * Update run_column_protocol.py (cherry picked from commite5aa4d940a) * resource_update use resource_add * 新增版位推荐功能 * 重新规定了版位推荐的入参 * update registry with nested obj * fix protocol node log_message, added create_resource return value * fix protocol node log_message, added create_resource return value * try fix add protocol * fix resource_add * 修复移液站错误的aspirate注册表 * Feature/xprbalance-zhida (#80) * feat(devices): add Zhida GC/MS pretreatment automation workstation * feat(devices): add mettler_toledo xpr balance * balance * 重新补全zhida注册表 * PRCXI9320 json * PRCXI9320 json * PRCXI9320 json * fix resource download * remove class for resource * bump version to 0.10.6 * 更新所有注册表 * 修复protocolnode的兼容性 * 修复protocolnode的兼容性 * Update install md * Add Defaultlayout * 更新物料接口 * fix dict to tree/nested-dict converter * coin_cell_station draft * refactor: rename "station_resource" to "deck" * add standardized BIOYOND resources: bottle_carrier, bottle * refactor and add BIOYOND resources tests * add BIOYOND deck assignment and pass all tests * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92) * feat: 新威电池测试系统驱动与注册文件 * feat: bring neware driver & battery.json into workstation_dev_YB2 * add bioyond studio draft * bioyond station with communication init and resource sync * fix bioyond station and registry * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * frontend_docs * create/update resources with POST/PUT for big amount/ small amount data * create/update resources with POST/PUT for big amount/ small amount data * refactor: add itemized_carrier instead of carrier consists of ResourceHolder * create warehouse by factory func * update bioyond launch json * add child_size for itemized_carrier * fix bioyond resource io * Workstation templates: Resources and its CRUD, and workstation tasks (#95) * coin_cell_station draft * refactor: rename "station_resource" to "deck" * add standardized BIOYOND resources: bottle_carrier, bottle * refactor and add BIOYOND resources tests * add BIOYOND deck assignment and pass all tests * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92) * feat: 新威电池测试系统驱动与注册文件 * feat: bring neware driver & battery.json into workstation_dev_YB2 * add bioyond studio draft * bioyond station with communication init and resource sync * fix bioyond station and registry * create/update resources with POST/PUT for big amount/ small amount data * refactor: add itemized_carrier instead of carrier consists of ResourceHolder * create warehouse by factory func * update bioyond launch json * add child_size for itemized_carrier * fix bioyond resource io --------- Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com> Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com> * 更新物料接口 * Workstation dev yb2 (#100) * Refactor and extend reaction station action messages * Refactor dispensing station tasks to enhance parameter clarity and add batch processing capabilities - Updated `create_90_10_vial_feeding_task` to include detailed parameters for 90%/10% vial feeding, improving clarity and usability. - Introduced `create_batch_90_10_vial_feeding_task` for batch processing of 90%/10% vial feeding tasks with JSON formatted input. - Added `create_batch_diamine_solution_task` for batch preparation of diamine solution, also utilizing JSON formatted input. - Refined `create_diamine_solution_task` to include additional parameters for better task configuration. - Enhanced schema descriptions and default values for improved user guidance. * 修复to_plr_resources * add update remove * 支持选择器注册表自动生成 支持转运物料 * 修复资源添加 * 修复transfer_resource_to_another生成 * 更新transfer_resource_to_another参数,支持spot入参 * 新增test_resource动作 * fix host_node error * fix host_node test_resource error * fix host_node test_resource error * 过滤本地动作 * 移动内部action以兼容host node * 修复同步任务报错不显示的bug * feat: 允许返回非本节点物料,后面可以通过decoration进行区分,就不进行warning了 * update todo * modify bioyond/plr converter, bioyond resource registry, and tests * pass the tests * update todo * add conda-pack-build.yml * add auto install script for conda-pack-build.yml (cherry picked from commit172599adcf) * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * Add version in __init__.py Update conda-pack-build.yml Add create_zip_archive.py * Update conda-pack-build.yml * Update conda-pack-build.yml (with mamba) * Update conda-pack-build.yml * Fix FileNotFoundError * Try fix 'charmap' codec can't encode characters in position 16-23: character maps to <undefined> * Fix unilabos msgs search error * Fix environment_check.py * Update recipe.yaml * Update registry. Update uuid loop figure method. Update install docs. * Fix nested conda pack * Fix one-key installation path error * Bump version to 0.10.7 * Workshop bj (#99) * Add LaiYu Liquid device integration and tests Introduce LaiYu Liquid device implementation, including backend, controllers, drivers, configuration, and resource files. Add hardware connection, tip pickup, and simplified test scripts, as well as experiment and registry configuration for LaiYu Liquid. Documentation and .gitignore for the device are also included. * feat(LaiYu_Liquid): 重构设备模块结构并添加硬件文档 refactor: 重新组织LaiYu_Liquid模块目录结构 docs: 添加SOPA移液器和步进电机控制指令文档 fix: 修正设备配置中的最大体积默认值 test: 新增工作台配置测试用例 chore: 删除过时的测试脚本和配置文件 * add * 重构: 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py 并更新所有导入引用 - 使用 git mv 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py - 更新所有相关文件中的导入引用 - 保持代码功能不变,仅改善命名一致性 - 测试确认所有导入正常工作 * 修复: 在 core/__init__.py 中添加 LaiYuLiquidBackend 导出 - 添加 LaiYuLiquidBackend 到导入列表 - 添加 LaiYuLiquidBackend 到 __all__ 导出列表 - 确保所有主要类都可以正确导入 * 修复大小写文件夹名字 * 电池装配工站二次开发教程(带目录)上传至dev (#94) * 电池装配工站二次开发教程 * Update intro.md * 物料教程 * 更新物料教程,json格式注释 * Update prcxi driver & fix transfer_liquid mix_times (#90) * Update prcxi driver & fix transfer_liquid mix_times * fix: correct mix_times type * Update liquid_handler registry * test: prcxi.py * Update registry from pr * fix ony-key script not exist * clean files --------- Co-authored-by: Junhan Chang <changjh@dp.tech> Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com> Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com> Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com> Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com> Co-authored-by: LccLink <1951855008@qq.com> Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com> Co-authored-by: shiyubo0410 <shiyubo@dp.tech>
1952 lines
78 KiB
Python
1952 lines
78 KiB
Python
# bioyond_rpc.py
|
||
"""
|
||
BioyondV1RPC类定义 - 包含所有RPC接口和业务逻辑
|
||
"""
|
||
|
||
from enum import Enum
|
||
from datetime import datetime, timezone
|
||
from unilabos.device_comms.rpc import BaseRequest
|
||
from typing import Optional, List, Dict, Any
|
||
import json
|
||
from unilabos.devices.workstation.bioyond_studio.config import WORKFLOW_TO_SECTION_MAP, WORKFLOW_STEP_IDS, LOCATION_MAPPING
|
||
|
||
|
||
class SimpleLogger:
|
||
"""简单的日志记录器"""
|
||
def info(self, msg): print(f"[INFO] {msg}")
|
||
def error(self, msg): print(f"[ERROR] {msg}")
|
||
def debug(self, msg): print(f"[DEBUG] {msg}")
|
||
def warning(self, msg): print(f"[WARNING] {msg}")
|
||
def critical(self, msg): print(f"[CRITICAL] {msg}")
|
||
|
||
|
||
class MachineState(Enum):
|
||
INITIAL = 0
|
||
STOPPED = 1
|
||
RUNNING = 2
|
||
PAUSED = 3
|
||
ERROR_PAUSED = 4
|
||
ERROR_STOPPED = 5
|
||
|
||
|
||
class MaterialType(Enum):
|
||
Consumables = 0
|
||
Sample = 1
|
||
Reagent = 2
|
||
Product = 3
|
||
|
||
|
||
class BioyondV1RPC(BaseRequest):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
print("开始初始化")
|
||
self.config = config
|
||
self.api_key = config["api_key"]
|
||
self.host = config["api_host"]
|
||
self._logger = SimpleLogger()
|
||
self.is_running = False
|
||
self.workflow_mappings = {}
|
||
self.workflow_sequence = []
|
||
self.pending_task_params = []
|
||
self.material_cache = {}
|
||
self._load_material_cache()
|
||
|
||
if "workflow_mappings" in config:
|
||
self._set_workflow_mappings(config["workflow_mappings"])
|
||
|
||
def _set_workflow_mappings(self, mappings: Dict[str, str]):
|
||
self.workflow_mappings = mappings
|
||
print(f"设置工作流映射配置: {mappings}")
|
||
|
||
def _get_workflow(self, web_workflow_name: str) -> str:
|
||
if web_workflow_name not in self.workflow_mappings:
|
||
print(f"未找到工作流映射配置: {web_workflow_name}")
|
||
return ""
|
||
workflow_id = self.workflow_mappings[web_workflow_name]
|
||
print(f"获取工作流: {web_workflow_name} -> {workflow_id}")
|
||
return workflow_id
|
||
|
||
def process_web_workflows(self, json_str: str) -> Dict[str, str]:
|
||
try:
|
||
data = json.loads(json_str)
|
||
web_workflow_list = data.get("web_workflow_list", [])
|
||
except json.JSONDecodeError:
|
||
print(f"无效的JSON字符串: {json_str}")
|
||
return {}
|
||
|
||
result = {}
|
||
self.workflow_sequence = []
|
||
|
||
for web_name in web_workflow_list:
|
||
workflow_id = self._get_workflow(web_name)
|
||
if workflow_id:
|
||
result[web_name] = workflow_id
|
||
self.workflow_sequence.append(workflow_id)
|
||
else:
|
||
print(f"无法获取工作流ID: {web_name}")
|
||
|
||
print(f"工作流执行顺序: {self.workflow_sequence}")
|
||
return result
|
||
|
||
def get_workflow_sequence(self) -> List[str]:
|
||
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_names.append(id_to_name.get(workflow_id, workflow_id))
|
||
return workflow_names
|
||
|
||
def append_to_workflow_sequence(self, json_str: str) -> bool:
|
||
try:
|
||
data = json.loads(json_str)
|
||
web_workflow_name = data.get("web_workflow_name", "")
|
||
except:
|
||
return False
|
||
|
||
workflow_id = self._get_workflow(web_workflow_name)
|
||
if workflow_id:
|
||
self.workflow_sequence.append(workflow_id)
|
||
print(f"添加工作流到执行顺序: {web_workflow_name} -> {workflow_id}")
|
||
|
||
def set_workflow_sequence(self, json_str: str) -> List[str]:
|
||
try:
|
||
data = json.loads(json_str)
|
||
web_workflow_names = data.get("web_workflow_names", [])
|
||
except:
|
||
return []
|
||
|
||
sequence = []
|
||
for web_name in web_workflow_names:
|
||
workflow_id = self._get_workflow(web_name)
|
||
if workflow_id:
|
||
sequence.append(workflow_id)
|
||
|
||
self.workflow_sequence = sequence
|
||
print(f"设置工作流执行顺序: {self.workflow_sequence}")
|
||
return self.workflow_sequence.copy()
|
||
|
||
def get_all_workflows(self) -> Dict[str, str]:
|
||
return self.workflow_mappings.copy()
|
||
|
||
def clear_workflows(self):
|
||
self.workflow_sequence = []
|
||
print("清空工作流执行顺序")
|
||
|
||
def get_current_time_iso8601(self) -> str:
|
||
current_time = datetime.now(timezone.utc).isoformat(timespec='milliseconds')
|
||
return current_time.replace("+00:00", "Z")
|
||
|
||
# 物料查询接口
|
||
def stock_material(self, json_str: str) -> list:
|
||
try:
|
||
params = json.loads(json_str)
|
||
except:
|
||
return []
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/stock-material',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return []
|
||
return response.get("data", [])
|
||
|
||
# 工作流列表查询
|
||
def query_workflow(self, json_str: str) -> dict:
|
||
try:
|
||
params = json.loads(json_str)
|
||
except:
|
||
return {}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/workflow/work-flow-list',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
def validate_workflow_parameters(self, workflows: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||
"""验证工作流参数格式
|
||
|
||
Args:
|
||
workflows (List[Dict[str, Any]]): 工作流列表
|
||
|
||
Returns:
|
||
Dict[str, Any]: 验证结果
|
||
"""
|
||
try:
|
||
validation_errors = []
|
||
|
||
for i, workflow in enumerate(workflows):
|
||
workflow_errors = []
|
||
|
||
# 检查基本结构
|
||
if not isinstance(workflow, dict):
|
||
workflow_errors.append("工作流必须是字典类型")
|
||
continue
|
||
|
||
if "id" not in workflow:
|
||
workflow_errors.append("缺少必要的 'id' 字段")
|
||
|
||
# 检查 stepParameters(如果存在)
|
||
if "stepParameters" in workflow:
|
||
step_params = workflow["stepParameters"]
|
||
|
||
if not isinstance(step_params, dict):
|
||
workflow_errors.append("stepParameters 必须是字典类型")
|
||
else:
|
||
# 验证参数结构
|
||
for step_id, modules in step_params.items():
|
||
if not isinstance(modules, dict):
|
||
workflow_errors.append(f"步骤 {step_id} 的模块配置必须是字典类型")
|
||
continue
|
||
|
||
for module_name, params in modules.items():
|
||
if not isinstance(params, list):
|
||
workflow_errors.append(f"步骤 {step_id} 模块 {module_name} 的参数必须是列表类型")
|
||
continue
|
||
|
||
for j, param in enumerate(params):
|
||
if not isinstance(param, dict):
|
||
workflow_errors.append(f"步骤 {step_id} 模块 {module_name} 参数 {j} 必须是字典类型")
|
||
elif "key" not in param or "value" not in param:
|
||
workflow_errors.append(f"步骤 {step_id} 模块 {module_name} 参数 {j} 必须包含 key 和 value")
|
||
|
||
if workflow_errors:
|
||
validation_errors.append({
|
||
"workflow_index": i,
|
||
"workflow_id": workflow.get("id", "unknown"),
|
||
"errors": workflow_errors
|
||
})
|
||
|
||
if validation_errors:
|
||
return {
|
||
"valid": False,
|
||
"errors": validation_errors,
|
||
"message": f"发现 {len(validation_errors)} 个工作流存在验证错误"
|
||
}
|
||
else:
|
||
return {
|
||
"valid": True,
|
||
"message": f"所有 {len(workflows)} 个工作流验证通过"
|
||
}
|
||
|
||
except Exception as e:
|
||
return {
|
||
"valid": False,
|
||
"errors": [{"general_error": str(e)}],
|
||
"message": f"验证过程中发生异常: {str(e)}"
|
||
}
|
||
|
||
def get_workflow_parameter_template(self) -> Dict[str, Any]:
|
||
"""获取工作流参数模板
|
||
|
||
Returns:
|
||
Dict[str, Any]: 参数模板和说明
|
||
"""
|
||
return {
|
||
"template": {
|
||
"name": "拼接后的长工作流的名称",
|
||
"workflows": [
|
||
{
|
||
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||
"stepParameters": {
|
||
"步骤ID (UUID)": {
|
||
"模块名称": [
|
||
{
|
||
"key": "参数键名",
|
||
"value": "参数值或变量引用 {{index-m-n}}"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"parameter_descriptions": {
|
||
"name": "拼接后的长工作流名称",
|
||
"workflows": "待合并的子工作流列表",
|
||
"id": "子工作流 ID,对应工作流列表中 workflows 数组中每个对象的 id 字段",
|
||
"stepParameters": "步骤参数配置,如果子工作流没有参数则不需要填写"
|
||
},
|
||
"common_modules": {
|
||
"反应模块-开始搅拌": {
|
||
"description": "反应模块搅拌控制",
|
||
"common_parameters": ["temperature"]
|
||
},
|
||
"通量-配置": {
|
||
"description": "通量配置模块",
|
||
"common_parameters": ["cutoff", "assignMaterialName"]
|
||
},
|
||
"烧杯溶液放置位-烧杯吸液分液": {
|
||
"description": "烧杯液体处理模块",
|
||
"common_parameters": ["titrationType", "assignMaterialName", "volume"]
|
||
}
|
||
},
|
||
"variable_reference_format": {
|
||
"format": "{{index-m-n}}",
|
||
"description": {
|
||
"index": "该步骤所在子工作流的拼接顺序(从 1 开始)",
|
||
"m": "拼接前该步骤在子工作流内部的 m 值",
|
||
"n": "拼接前该步骤在子工作流内部的 n 值"
|
||
}
|
||
}
|
||
}
|
||
|
||
# 工作流步骤查询接口
|
||
def workflow_step_query(self, json_str: str) -> dict:
|
||
try:
|
||
data = json.loads(json_str)
|
||
workflow_id = data.get("workflow_id", "")
|
||
except:
|
||
return {}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/workflow/sub-workflow-step-parameters',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": workflow_id,
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
# 任务推送接口
|
||
def create_order(self, json_str: str) -> dict:
|
||
try:
|
||
params = json.loads(json_str)
|
||
except Exception as e:
|
||
result = str({"success": False, "error": f"create_order:处理JSON时出错: {str(e)}", "method": "create_order"})
|
||
return result
|
||
|
||
print('===============', json.dumps(params))
|
||
|
||
request_params = {
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/order/order',
|
||
params=request_params)
|
||
|
||
if response['code'] != 1:
|
||
print(f"create order error: {response.get('message')}")
|
||
|
||
print(f"create order data: {response['data']}")
|
||
result = str(response.get("data", {}))
|
||
return result
|
||
|
||
# 查询任务列表
|
||
def order_query(self, json_str: str) -> dict:
|
||
try:
|
||
params = json.loads(json_str)
|
||
except:
|
||
return {}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/order/order-list',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
# 任务明细查询
|
||
def order_report(self, json_str: str) -> dict:
|
||
try:
|
||
data = json.loads(json_str)
|
||
order_id = data.get("order_id", "")
|
||
except:
|
||
return {}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/order/order-report',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": order_id,
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
def material_id_query(self, json_str: str) -> dict:
|
||
"""
|
||
查询物料id
|
||
json_str 格式为JSON字符串:
|
||
'{"material123"}'
|
||
"""
|
||
params = json_str
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/workflow-sample-locations',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response:
|
||
return {}
|
||
|
||
if response['code'] != 1:
|
||
print(f"material_id_query error: {response.get('message')}")
|
||
return {}
|
||
|
||
print(f"material_id_query data: {response['data']}")
|
||
return response.get("data", {})
|
||
|
||
# 任务取出接口
|
||
def order_takeout(self, json_str: str) -> int:
|
||
try:
|
||
data = json.loads(json_str)
|
||
params = {
|
||
"orderId": data.get("order_id", ""),
|
||
"preintakeId": data.get("preintake_id", "")
|
||
}
|
||
except:
|
||
return 0
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/order/order-takeout',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params,
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return 0
|
||
return response.get("code", 0)
|
||
|
||
# 设备列表查询
|
||
def device_list(self, json_str: str = "") -> list:
|
||
device_no = None
|
||
if json_str:
|
||
try:
|
||
data = json.loads(json_str)
|
||
device_no = data.get("device_no", None)
|
||
except:
|
||
pass
|
||
|
||
url = f'{self.host}/api/lims/device/device-list'
|
||
if device_no:
|
||
url += f'/{device_no}'
|
||
|
||
response = self.post(
|
||
url=url,
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return []
|
||
return response.get("data", [])
|
||
|
||
# 设备操作
|
||
def device_operation(self, json_str: str) -> int:
|
||
try:
|
||
data = json.loads(json_str)
|
||
params = {
|
||
"deviceNo": data.get("device_no", ""),
|
||
"operationType": data.get("operation_type", 0),
|
||
"operationParams": data.get("operation_params", {})
|
||
}
|
||
except:
|
||
return 0
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/device/device-operation',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params,
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return 0
|
||
return response.get("code", 0)
|
||
|
||
# 调度器状态查询
|
||
def scheduler_status(self) -> dict:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/scheduler/scheduler-status',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
# 调度器启动
|
||
def scheduler_start(self) -> int:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/scheduler/start',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return 0
|
||
return response.get("code", 0)
|
||
|
||
# 调度器暂停
|
||
def scheduler_pause(self) -> int:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/scheduler/scheduler-pause',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return 0
|
||
return response.get("code", 0)
|
||
|
||
# 调度器继续
|
||
def scheduler_continue(self) -> int:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/scheduler/scheduler-continue',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return 0
|
||
return response.get("code", 0)
|
||
|
||
# 调度器停止
|
||
def scheduler_stop(self) -> int:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/scheduler/scheduler-stop',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return 0
|
||
return response.get("code", 0)
|
||
|
||
# 调度器重置
|
||
def scheduler_reset(self) -> int:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/scheduler/scheduler-reset',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return 0
|
||
return response.get("code", 0)
|
||
|
||
# 取消任务
|
||
def cancel_order(self, json_str: str) -> bool:
|
||
try:
|
||
data = json.loads(json_str)
|
||
order_id = data.get("order_id", "")
|
||
except:
|
||
return False
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/order/cancel-order',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": order_id,
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return False
|
||
return True
|
||
|
||
# 获取可拼接工作流
|
||
def query_split_workflow(self) -> list:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/workflow/split-workflow-list',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return []
|
||
return str(response.get("data", {}))
|
||
|
||
# 合并工作流
|
||
def merge_workflow(self, json_str: str) -> dict:
|
||
try:
|
||
data = json.loads(json_str)
|
||
params = {
|
||
"name": data.get("name", ""),
|
||
"workflowIds": data.get("workflow_ids", [])
|
||
}
|
||
except:
|
||
return {}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/workflow/merge-workflow',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params,
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
# 合并工作流并设置参数 API
|
||
def merge_workflow_with_parameters(self, json_str: str) -> dict:
|
||
"""合并工作流并设置参数
|
||
|
||
调用 Bioyond API: /api/lims/workflow/merge-workflow-with-parameters
|
||
|
||
Args:
|
||
json_str (str): JSON 字符串,包含工作流合并配置数据
|
||
|
||
Returns:
|
||
dict: API 响应结果,包含 code、message 和 timestamp
|
||
"""
|
||
try:
|
||
# 解析输入的 JSON 数据
|
||
data = json.loads(json_str)
|
||
|
||
# 构造 API 请求参数
|
||
params = {
|
||
"name": data.get("name", ""),
|
||
"workflows": data.get("workflows", [])
|
||
}
|
||
|
||
# 验证必要参数
|
||
if not params["name"]:
|
||
return {
|
||
"code": 0,
|
||
"message": "工作流名称不能为空",
|
||
"timestamp": int(datetime.now().timestamp() * 1000)
|
||
}
|
||
|
||
if not params["workflows"]:
|
||
return {
|
||
"code": 0,
|
||
"message": "工作流列表不能为空",
|
||
"timestamp": int(datetime.now().timestamp() * 1000)
|
||
}
|
||
|
||
except json.JSONDecodeError as e:
|
||
return {
|
||
"code": 0,
|
||
"message": f"JSON 解析错误: {str(e)}",
|
||
"timestamp": int(datetime.now().timestamp() * 1000)
|
||
}
|
||
except Exception as e:
|
||
return {
|
||
"code": 0,
|
||
"message": f"参数处理错误: {str(e)}",
|
||
"timestamp": int(datetime.now().timestamp() * 1000)
|
||
}
|
||
|
||
# 发送 POST 请求到 Bioyond API
|
||
try:
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/workflow/merge-workflow-with-parameters',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params,
|
||
})
|
||
|
||
# 处理响应
|
||
if not response:
|
||
return {
|
||
"code": 0,
|
||
"message": "API 请求失败,未收到响应",
|
||
"timestamp": int(datetime.now().timestamp() * 1000)
|
||
}
|
||
|
||
# 返回完整的响应结果
|
||
return {
|
||
"code": response.get("code", 0),
|
||
"message": response.get("message", ""),
|
||
"timestamp": response.get("timestamp", int(datetime.now().timestamp() * 1000))
|
||
}
|
||
|
||
except Exception as e:
|
||
return {
|
||
"code": 0,
|
||
"message": f"API 请求异常: {str(e)}",
|
||
"timestamp": int(datetime.now().timestamp() * 1000)
|
||
}
|
||
|
||
# 合并当前工作流序列
|
||
def merge_sequence_workflow(self, json_str: str) -> dict:
|
||
try:
|
||
data = json.loads(json_str)
|
||
name = data.get("name", "合并工作流")
|
||
except:
|
||
return {}
|
||
|
||
if not self.workflow_sequence:
|
||
print("工作流序列为空,无法合并")
|
||
return {}
|
||
|
||
params = {
|
||
"name": name,
|
||
"workflowIds": self.workflow_sequence
|
||
}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/workflow/merge-workflow',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params,
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
# 发布任务
|
||
def process_and_execute_workflow(self, workflow_name: str, task_name: str) -> dict:
|
||
web_workflow_list = self.get_workflow_sequence()
|
||
workflow_name = workflow_name
|
||
|
||
pending_params_backup = self.pending_task_params.copy()
|
||
print(f"保存pending_task_params副本,共{len(pending_params_backup)}个参数")
|
||
|
||
# 1. 处理网页工作流列表
|
||
print(f"处理网页工作流列表: {web_workflow_list}")
|
||
web_workflow_json = json.dumps({"web_workflow_list": web_workflow_list})
|
||
workflows_result = self.process_web_workflows(web_workflow_json)
|
||
|
||
if not workflows_result:
|
||
error_msg = "处理网页工作流列表失败"
|
||
print(error_msg)
|
||
result = str({"success": False, "error": f"process_and_execute_workflow:{error_msg}", "method": "process_and_execute_workflow", "step": "process_web_workflows"})
|
||
return result
|
||
|
||
# 2. 合并工作流序列
|
||
print(f"合并工作流序列,名称: {workflow_name}")
|
||
merge_json = json.dumps({"name": workflow_name})
|
||
merged_workflow = self.merge_sequence_workflow(merge_json)
|
||
print(f"合并工作流序列结果: {merged_workflow}")
|
||
|
||
if not merged_workflow:
|
||
error_msg = "合并工作流序列失败"
|
||
print(error_msg)
|
||
result = str({"success": False, "error": f"process_and_execute_workflow:{error_msg}", "method": "process_and_execute_workflow", "step": "merge_sequence_workflow"})
|
||
return result
|
||
|
||
# 3. 合并所有参数并创建任务
|
||
workflow_name = merged_workflow.get("name", "")
|
||
workflow_id = merged_workflow.get("subWorkflows", [{}])[0].get("id", "")
|
||
print(f"使用工作流创建任务: {workflow_name} (ID: {workflow_id})")
|
||
|
||
workflow_query_json = json.dumps({"workflow_id": workflow_id})
|
||
workflow_params_structure = self.workflow_step_query(workflow_query_json)
|
||
|
||
self.pending_task_params = pending_params_backup
|
||
print(f"恢复pending_task_params,共{len(self.pending_task_params)}个参数")
|
||
|
||
param_values = self.generate_task_param_values(workflow_params_structure)
|
||
|
||
task_params = [{
|
||
"orderCode": f"BSO{self.get_current_time_iso8601().replace('-', '').replace('T', '').replace(':', '').replace('.', '')[:14]}",
|
||
"orderName": f"实验-{self.get_current_time_iso8601()[:10].replace('-', '')}",
|
||
"workFlowId": workflow_id,
|
||
"borderNumber": 1,
|
||
"paramValues": param_values,
|
||
"extendProperties": ""
|
||
}]
|
||
|
||
task_json = json.dumps(task_params)
|
||
print(f"创建任务参数: {type(task_json)}")
|
||
result = self.create_order(task_json)
|
||
|
||
if not result:
|
||
error_msg = "创建任务失败"
|
||
print(error_msg)
|
||
result = str({"success": False, "error": f"process_and_execute_workflow:{error_msg}", "method": "process_and_execute_workflow", "step": "create_order"})
|
||
return result
|
||
|
||
print(f"任务创建成功: {result}")
|
||
self.pending_task_params.clear()
|
||
print("已清空pending_task_params")
|
||
|
||
return {
|
||
"success": True,
|
||
"workflow": {"name": workflow_name, "id": workflow_id},
|
||
"task": result,
|
||
"method": "process_and_execute_workflow"
|
||
}
|
||
|
||
# 生成任务参数
|
||
def generate_task_param_values(self, workflow_params_structure):
|
||
if not workflow_params_structure:
|
||
print("workflow_params_structure为空")
|
||
return {}
|
||
|
||
data = workflow_params_structure
|
||
|
||
# 从pending_task_params中提取实际参数值,按DisplaySectionName和Key组织
|
||
pending_params_by_section = {}
|
||
print(f"开始处理pending_task_params,共{len(self.pending_task_params)}个任务参数组")
|
||
|
||
# 获取工作流执行顺序,用于按顺序匹配参数
|
||
workflow_sequence = self.get_workflow_sequence()
|
||
print(f"工作流执行顺序: {workflow_sequence}")
|
||
|
||
workflow_index = 0
|
||
|
||
for i, task_param in enumerate(self.pending_task_params):
|
||
if 'param_values' in task_param:
|
||
print(f"处理第{i+1}个任务参数组,包含{len(task_param['param_values'])}个步骤")
|
||
|
||
if workflow_index < len(workflow_sequence):
|
||
current_workflow = workflow_sequence[workflow_index]
|
||
section_name = WORKFLOW_TO_SECTION_MAP.get(current_workflow)
|
||
print(f" 匹配到工作流: {current_workflow} -> {section_name}")
|
||
workflow_index += 1
|
||
else:
|
||
print(f" 警告: 参数组{i+1}超出了工作流序列范围")
|
||
continue
|
||
|
||
if not section_name:
|
||
print(f" 警告: 工作流{current_workflow}没有对应的DisplaySectionName")
|
||
continue
|
||
|
||
if section_name not in pending_params_by_section:
|
||
pending_params_by_section[section_name] = {}
|
||
|
||
for step_id, param_list in task_param['param_values'].items():
|
||
print(f" 步骤ID: {step_id},参数数量: {len(param_list)}")
|
||
|
||
for param_item in param_list:
|
||
key = param_item.get('Key', '')
|
||
value = param_item.get('Value', '')
|
||
m = param_item.get('m', 0)
|
||
n = param_item.get('n', 0)
|
||
print(f" 参数: {key} = {value} (m={m}, n={n}) -> 分组到{section_name}")
|
||
|
||
param_key = f"{section_name}.{key}"
|
||
if param_key not in pending_params_by_section[section_name]:
|
||
pending_params_by_section[section_name][param_key] = []
|
||
|
||
pending_params_by_section[section_name][param_key].append({
|
||
'value': value,
|
||
'm': m,
|
||
'n': n
|
||
})
|
||
|
||
print(f"pending_params_by_section构建完成,包含{len(pending_params_by_section)}个分组")
|
||
|
||
# 收集所有参数,过滤TaskDisplayable为0的项
|
||
filtered_params = []
|
||
|
||
for step_id, step_info in data.items():
|
||
if isinstance(step_info, list):
|
||
for step_item in step_info:
|
||
param_list = step_item.get("parameterList", [])
|
||
for param in param_list:
|
||
if param.get("TaskDisplayable") == 0:
|
||
continue
|
||
|
||
param_with_step = param.copy()
|
||
param_with_step['step_id'] = step_id
|
||
param_with_step['step_name'] = step_item.get("name", "")
|
||
param_with_step['step_m'] = step_item.get("m", 0)
|
||
param_with_step['step_n'] = step_item.get("n", 0)
|
||
filtered_params.append(param_with_step)
|
||
|
||
# 按DisplaySectionIndex排序
|
||
filtered_params.sort(key=lambda x: x.get('DisplaySectionIndex', 0))
|
||
|
||
# 生成参数映射
|
||
param_mapping = {}
|
||
step_params = {}
|
||
for param in filtered_params:
|
||
step_id = param['step_id']
|
||
if step_id not in step_params:
|
||
step_params[step_id] = []
|
||
step_params[step_id].append(param)
|
||
|
||
# 为每个步骤生成参数
|
||
for step_id, params in step_params.items():
|
||
param_list = []
|
||
for param in params:
|
||
key = param.get('Key', '')
|
||
display_section_index = param.get('DisplaySectionIndex', 0)
|
||
step_m = param.get('step_m', 0)
|
||
step_n = param.get('step_n', 0)
|
||
|
||
section_name = param.get('DisplaySectionName', '')
|
||
param_key = f"{section_name}.{key}"
|
||
|
||
if section_name in pending_params_by_section and param_key in pending_params_by_section[section_name]:
|
||
pending_param_list = pending_params_by_section[section_name][param_key]
|
||
if pending_param_list:
|
||
pending_param = pending_param_list[0]
|
||
value = pending_param['value']
|
||
m = step_m
|
||
n = step_n
|
||
print(f" 匹配成功: {section_name}.{key} = {value} (m={m}, n={n})")
|
||
pending_param_list.pop(0)
|
||
else:
|
||
value = "1"
|
||
m = step_m
|
||
n = step_n
|
||
print(f" 匹配失败: {section_name}.{key},参数列表为空,使用默认值 = {value}")
|
||
else:
|
||
value = "1"
|
||
m = display_section_index
|
||
n = step_n
|
||
print(f" 匹配失败: {section_name}.{key},使用默认值 = {value} (m={m}, n={n})")
|
||
|
||
param_item = {
|
||
"m": m,
|
||
"n": n,
|
||
"key": key,
|
||
"value": str(value).strip()
|
||
}
|
||
param_list.append(param_item)
|
||
|
||
if param_list:
|
||
param_mapping[step_id] = param_list
|
||
|
||
print(f"生成任务参数值,包含 {len(param_mapping)} 个步骤")
|
||
return param_mapping
|
||
|
||
# 工作流方法
|
||
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):
|
||
"""反应器放入"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_in"}')
|
||
material_id = self._get_material_id_by_name(assign_material_name)
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
step_id = WORKFLOW_STEP_IDS["reactor_taken_in"]["config"]
|
||
reactor_taken_in_params = {
|
||
"param_values": {
|
||
step_id: [
|
||
{"m": 0, "n": 3, "Key": "cutoff", "Value": cutoff},
|
||
{"m": 0, "n": 3, "Key": "temperature", "Value": f"{temperature:.2f}"},
|
||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
|
||
]
|
||
}
|
||
}
|
||
|
||
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: str = "1",
|
||
assign_material_name: str = None, temperature: float = 25.00):
|
||
"""固体进料小瓶"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Solid_feeding_vials"}')
|
||
material_id_m = self._get_material_id_by_name(assign_material_name)
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
feeding_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["feeding"]
|
||
observe_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["observe"]
|
||
|
||
solid_feeding_vials_params = {
|
||
"param_values": {
|
||
feeding_id: [
|
||
{"m": 0, "n": 3, "Key": "materialId", "Value": material_id},
|
||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id_m}
|
||
],
|
||
observe_id: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": 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, temp={temperature:.2f}°C")
|
||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||
return json.dumps({"suc": True})
|
||
|
||
def liquid_feeding_vials_non_titration(self, volumeFormula: str, assign_material_name: str,
|
||
titration_type: str = "1", time: str = "0",
|
||
torque_variation: str = "1", temperature: float = 25.00):
|
||
"""液体进料小瓶(非滴定)"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_vials(non-titration)"}')
|
||
material_id = self._get_material_id_by_name(assign_material_name)
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["liquid"]
|
||
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_id: [
|
||
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volumeFormula},
|
||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id},
|
||
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type}
|
||
],
|
||
observe_id: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": torque_variation},
|
||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
||
]
|
||
}
|
||
}
|
||
|
||
self.pending_task_params.append(params)
|
||
print(f"成功添加液体进料小瓶(非滴定)参数: volume={volumeFormula}μ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, titration_type: str = "1",
|
||
time: str = "360", torque_variation: str = "2", temperature: float = 25.00):
|
||
"""液体进料溶剂"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_solvents"}')
|
||
material_id = self._get_material_id_by_name(assign_material_name)
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["liquid"]
|
||
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_id: [
|
||
{"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_id: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": 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):
|
||
"""液体进料(滴定)"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}')
|
||
material_id = self._get_material_id_by_name(assign_material_name)
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["liquid"]
|
||
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_id: [
|
||
{"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_id: [
|
||
{"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 = "35000", assign_material_name: str = "BAPP",
|
||
time: str = "0", torque_variation: str = "1", titrationType: str = "1",
|
||
temperature: float = 25.00):
|
||
"""液体进料烧杯"""
|
||
self.append_to_workflow_sequence('{"web_workflow_name": "liquid_feeding_beaker"}')
|
||
material_id = self._get_material_id_by_name(assign_material_name)
|
||
|
||
if isinstance(temperature, str):
|
||
temperature = float(temperature)
|
||
|
||
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["liquid"]
|
||
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["observe"]
|
||
|
||
params = {
|
||
"param_values": {
|
||
liquid_id: [
|
||
{"m": 0, "n": 2, "Key": "volume", "Value": volume},
|
||
{"m": 0, "n": 2, "Key": "assignMaterialName", "Value": material_id},
|
||
{"m": 0, "n": 2, "Key": "titrationType", "Value": titrationType}
|
||
],
|
||
observe_id: [
|
||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": 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 _load_material_cache(self):
|
||
"""预加载材料列表到缓存中"""
|
||
try:
|
||
print("正在加载材料列表缓存...")
|
||
stock_query = '{"typeMode": 2, "includeDetail": true}'
|
||
stock_result = self.stock_material(stock_query)
|
||
|
||
if isinstance(stock_result, str):
|
||
stock_data = json.loads(stock_result)
|
||
else:
|
||
stock_data = stock_result
|
||
|
||
materials = stock_data
|
||
for material in materials:
|
||
material_name = material.get("name")
|
||
material_id = material.get("id")
|
||
if material_name and material_id:
|
||
self.material_cache[material_name] = material_id
|
||
|
||
print(f"材料列表缓存加载完成,共加载 {len(self.material_cache)} 个材料")
|
||
|
||
except Exception as e:
|
||
print(f"加载材料列表缓存时出错: {e}")
|
||
self.material_cache = {}
|
||
|
||
def _get_material_id_by_name(self, material_name_or_id: str) -> str:
|
||
"""根据材料名称获取材料ID"""
|
||
if len(material_name_or_id) > 20 and '-' in material_name_or_id:
|
||
return material_name_or_id
|
||
|
||
if material_name_or_id in self.material_cache:
|
||
material_id = self.material_cache[material_name_or_id]
|
||
print(f"从缓存找到材料: {material_name_or_id} -> ID: {material_id}")
|
||
return material_id
|
||
|
||
print(f"警告: 未在缓存中找到材料名称 '{material_name_or_id}',将使用原值")
|
||
return material_name_or_id
|
||
|
||
def refresh_material_cache(self):
|
||
"""刷新材料列表缓存"""
|
||
print("正在刷新材料列表缓存...")
|
||
self._load_material_cache()
|
||
|
||
def get_available_materials(self):
|
||
"""获取所有可用的材料名称列表"""
|
||
return list(self.material_cache.keys())
|
||
|
||
# 物料管理接口
|
||
def add_material(self, json_str: str) -> dict:
|
||
"""添加新的物料"""
|
||
try:
|
||
params = json.loads(json_str)
|
||
except:
|
||
return {}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/material',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
def query_matial_type_id(self, data) -> list:
|
||
"""查找物料typeid"""
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/material-types',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": data
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return []
|
||
return str(response.get("data", {}))
|
||
|
||
def query_warehouse_by_material_type(self, type_id: str) -> dict:
|
||
"""查询物料类型可以入库的库位"""
|
||
params = {"typeId": type_id}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/warehouse-info-by-mat-type-id',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
def material_inbound(self, material_id: str, location_name: str) -> dict:
|
||
"""指定库位入库一个物料"""
|
||
location_id = LOCATION_MAPPING.get(location_name, location_name)
|
||
|
||
params = {
|
||
"materialId": material_id,
|
||
"locationId": location_id
|
||
}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/inbound',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
def delete_material(self, material_id: str) -> dict:
|
||
"""删除尚未入库的物料"""
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/delete-material',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": material_id
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response.get("data", {})
|
||
|
||
def material_outbound(self, material_id: str, location_name: str, quantity: int) -> dict:
|
||
"""指定库位出库物料"""
|
||
location_id = LOCATION_MAPPING.get(location_name, location_name)
|
||
|
||
params = {
|
||
"materialId": material_id,
|
||
"locationId": location_id,
|
||
"quantity": quantity
|
||
}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/outbound',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response or response['code'] != 1:
|
||
return {}
|
||
return response
|
||
|
||
def get_logger(self):
|
||
return self._logger
|
||
|
||
# ==================== 配液站特有方法 ====================
|
||
|
||
def sample_waste_removal(self, order_id: str) -> dict:
|
||
"""
|
||
样品/废料取出接口
|
||
|
||
参数:
|
||
- order_id: 订单ID
|
||
|
||
返回: 取出结果
|
||
"""
|
||
params = {"orderId": order_id}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/order/take-out',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response:
|
||
return {}
|
||
|
||
if response['code'] != 1:
|
||
self._logger.error(f"样品废料取出错误: {response.get('message', '')}")
|
||
return {}
|
||
|
||
return response.get("data", {})
|
||
|
||
def dispensing_material_inbound(self, material_id: str, location_id: str) -> dict:
|
||
"""
|
||
配液站物料入库接口
|
||
|
||
参数:
|
||
- material_id: 物料ID
|
||
- location_id: 库位ID
|
||
|
||
返回: 入库结果
|
||
"""
|
||
params = {
|
||
"materialId": material_id,
|
||
"locationId": location_id
|
||
}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/inbound',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response:
|
||
return {}
|
||
|
||
if response['code'] != 1:
|
||
self._logger.error(f"配液站物料入库错误: {response.get('message', '')}")
|
||
return {}
|
||
|
||
return response.get("data", {})
|
||
|
||
def dispensing_material_outbound(self, material_id: str, location_id: str, quantity: int) -> dict:
|
||
"""
|
||
配液站物料出库接口
|
||
|
||
参数:
|
||
- material_id: 物料ID
|
||
- location_id: 库位ID
|
||
- quantity: 出库数量
|
||
|
||
返回: 出库结果
|
||
"""
|
||
params = {
|
||
"materialId": material_id,
|
||
"locationId": location_id,
|
||
"quantity": quantity
|
||
}
|
||
|
||
response = self.post(
|
||
url=f'{self.host}/api/lims/storage/outbound',
|
||
params={
|
||
"apiKey": self.api_key,
|
||
"requestTime": self.get_current_time_iso8601(),
|
||
"data": params
|
||
})
|
||
|
||
if not response:
|
||
return {}
|
||
|
||
if response['code'] != 1:
|
||
self._logger.error(f"配液站物料出库错误: {response.get('message', '')}")
|
||
return {}
|
||
|
||
return response.get("data", {})
|
||
|
||
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
|
||
|
||
返回: 任务创建结果
|
||
"""
|
||
# 设置默认值
|
||
if order_name is None:
|
||
order_name = f"90%10%小瓶投料任务_{self.get_current_time_iso8601()}"
|
||
if speed is None:
|
||
speed = "400"
|
||
if temperature is None:
|
||
temperature = "20"
|
||
if delay_time is None:
|
||
delay_time = "600"
|
||
|
||
# 获取工作流ID
|
||
workflow_id = "3a19310d-16b9-9d81-b109-0748e953694b" # 90%10%小瓶投料工作流ID
|
||
|
||
# 查询holdMId
|
||
holdMId = None
|
||
if hold_m_name:
|
||
holdMId_response = self.material_id_query(hold_m_name)
|
||
if holdMId_response:
|
||
holdMId = holdMId_response
|
||
|
||
# 构建订单数据
|
||
order_data = [{
|
||
"code": order_name,
|
||
"Name": "90%10%小瓶投料任务",
|
||
"workflowName": "90%10%小瓶投料",
|
||
"borderNumber": 1,
|
||
"paramValues": {
|
||
workflow_id: [
|
||
# 搅拌速度
|
||
{"m": 3, "n": 2, "key": "speed", "value": speed},
|
||
# 温度
|
||
{"m": 3, "n": 2, "key": "temperature", "value": temperature},
|
||
# 延迟时间
|
||
{"m": 3, "n": 2, "key": "delayTime", "value": delay_time},
|
||
# 90%_1固体物料
|
||
{"m": 3, "n": 2, "key": "90%_1_assignMaterialName", "value": percent_90_1_assign_material_name},
|
||
{"m": 3, "n": 2, "key": "90%_1_targetWeigh", "value": percent_90_1_target_weigh},
|
||
# 90%_2固体物料
|
||
{"m": 3, "n": 2, "key": "90%_2_assignMaterialName", "value": percent_90_2_assign_material_name},
|
||
{"m": 3, "n": 2, "key": "90%_2_targetWeigh", "value": percent_90_2_target_weigh},
|
||
# 90%_3固体物料
|
||
{"m": 3, "n": 2, "key": "90%_3_assignMaterialName", "value": percent_90_3_assign_material_name},
|
||
{"m": 3, "n": 2, "key": "90%_3_targetWeigh", "value": percent_90_3_target_weigh},
|
||
# 10%_1液体物料
|
||
{"m": 3, "n": 2, "key": "10%_1_assignMaterialName", "value": percent_10_1_assign_material_name},
|
||
{"m": 3, "n": 2, "key": "10%_1_targetWeigh", "value": percent_10_1_target_weigh},
|
||
{"m": 3, "n": 2, "key": "10%_1_volume", "value": percent_10_1_volume},
|
||
{"m": 3, "n": 2, "key": "10%_1_liquidMaterialName", "value": percent_10_1_liquid_material_name},
|
||
# 10%_2液体物料
|
||
{"m": 3, "n": 2, "key": "10%_2_assignMaterialName", "value": percent_10_2_assign_material_name},
|
||
{"m": 3, "n": 2, "key": "10%_2_targetWeigh", "value": percent_10_2_target_weigh},
|
||
{"m": 3, "n": 2, "key": "10%_2_volume", "value": percent_10_2_volume},
|
||
{"m": 3, "n": 2, "key": "10%_2_liquidMaterialName", "value": percent_10_2_liquid_material_name},
|
||
# 10%_3液体物料
|
||
{"m": 3, "n": 2, "key": "10%_3_assignMaterialName", "value": percent_10_3_assign_material_name},
|
||
{"m": 3, "n": 2, "key": "10%_3_targetWeigh", "value": percent_10_3_target_weigh},
|
||
{"m": 3, "n": 2, "key": "10%_3_volume", "value": percent_10_3_volume},
|
||
{"m": 3, "n": 2, "key": "10%_3_liquidMaterialName", "value": percent_10_3_liquid_material_name}
|
||
]
|
||
},
|
||
"ExtendProperties": f"{{{holdMId}:null}}" if holdMId else "{}"
|
||
}]
|
||
|
||
try:
|
||
# 调用create_order方法创建任务
|
||
result = self.create_order(json.dumps(order_data, ensure_ascii=False))
|
||
self._logger.info(f"90%10%小瓶投料任务创建成功: {result}")
|
||
return result
|
||
|
||
except Exception as e:
|
||
error_msg = f"90%10%小瓶投料任务创建异常: {str(e)}"
|
||
self._logger.error(error_msg)
|
||
return {"error": 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则使用默认值25
|
||
- delay_time: 延迟时间,如果为None则使用默认值600
|
||
- hold_m_name: 库位名称,如"ODA-1",用于查找对应的holdMId
|
||
|
||
返回: 任务创建结果
|
||
"""
|
||
# 验证必填参数
|
||
if not material_name or not target_weigh or not volume:
|
||
return {
|
||
"status": "error",
|
||
"message": "material_name、target_weigh和volume为必填参数"
|
||
}
|
||
|
||
# 设置默认值
|
||
if order_name is None:
|
||
order_name = f"二胺溶液配置任务_{self.get_current_time_iso8601()}"
|
||
if speed is None:
|
||
speed = "400"
|
||
if temperature is None:
|
||
temperature = "25"
|
||
if delay_time is None:
|
||
delay_time = "600"
|
||
|
||
# 获取工作流ID
|
||
workflow_id = "1"
|
||
|
||
# 查询holdMId
|
||
holdMId = None
|
||
if hold_m_name:
|
||
try:
|
||
material_query_params = json.dumps({"materialName": hold_m_name})
|
||
material_response = self.material_id_query(material_query_params)
|
||
if material_response and material_response.get("code") == 1:
|
||
data = material_response.get("data", [])
|
||
if data:
|
||
holdMId = data[0].get("id")
|
||
self._logger.info(f"查询到holdMId: {holdMId} for {hold_m_name}")
|
||
else:
|
||
self._logger.warning(f"未找到物料: {hold_m_name}")
|
||
else:
|
||
self._logger.error(f"查询物料ID失败: {material_response}")
|
||
except Exception as e:
|
||
self._logger.error(f"查询holdMId时发生错误: {e}")
|
||
|
||
# 构建order_data
|
||
order_data = {
|
||
"workflowId": workflow_id,
|
||
"orderName": order_name,
|
||
"params": {
|
||
"1": speed, # 搅拌速度
|
||
"2": temperature, # 温度
|
||
"3": delay_time, # 延迟时间
|
||
"4": material_name, # 固体物料名称
|
||
"5": target_weigh, # 固体目标重量
|
||
"6": volume, # 液体体积
|
||
"7": liquid_material_name # 液体物料名称
|
||
}
|
||
}
|
||
|
||
if holdMId:
|
||
order_data["holdMId"] = holdMId
|
||
|
||
try:
|
||
# 使用create_order方法创建任务
|
||
order_params = json.dumps(order_data)
|
||
response = self.create_order(order_params)
|
||
return response
|
||
except Exception as e:
|
||
self._logger.error(f"创建二胺溶液配置任务时发生错误: {e}")
|
||
return {"status": "error", "message": f"创建任务失败: {str(e)}"}
|
||
|
||
def create_batch_90_10_vial_feeding_task(self, json_str: str) -> dict:
|
||
"""
|
||
创建批量90%10%小瓶投料任务
|
||
|
||
接受JSON输入,支持多个90%10%小瓶投料任务的批量创建
|
||
|
||
JSON格式示例:
|
||
{
|
||
"batch_name": "批量90%10%小瓶投料任务_20240101",
|
||
"tasks": [
|
||
{
|
||
"order_name": "小瓶投料任务1",
|
||
"hold_m_name": "C01",
|
||
"percent_90_1_assign_material_name": "物料A",
|
||
"percent_90_1_target_weigh": "10.5",
|
||
"percent_10_1_assign_material_name": "物料B",
|
||
"percent_10_1_target_weigh": "5.2",
|
||
"percent_10_1_volume": "50.0",
|
||
"percent_10_1_liquid_material_name": "NMP",
|
||
"speed": "400",
|
||
"temperature": "40",
|
||
"delay_time": "600"
|
||
}
|
||
],
|
||
"global_settings": {
|
||
"speed": "400",
|
||
"temperature": "40",
|
||
"delay_time": "600"
|
||
}
|
||
}
|
||
|
||
参数说明:
|
||
- batch_name: 批量任务名称,可选
|
||
- tasks: 任务列表,每个任务包含90%10%小瓶投料参数
|
||
- global_settings: 全局默认设置,当单个任务未指定参数时使用
|
||
|
||
返回: 批量任务创建结果
|
||
"""
|
||
try:
|
||
# 解析JSON输入
|
||
data = json.loads(json_str)
|
||
|
||
# 获取批量任务参数
|
||
batch_name = data.get("batch_name", f"批量90%10%小瓶投料任务_{self.get_current_time_iso8601()}")
|
||
tasks = data.get("tasks", [])
|
||
global_settings = data.get("global_settings", {})
|
||
|
||
if not tasks:
|
||
return {
|
||
"status": "error",
|
||
"message": "任务列表不能为空"
|
||
}
|
||
|
||
# 批量创建结果
|
||
batch_results = {
|
||
"batch_name": batch_name,
|
||
"total_tasks": len(tasks),
|
||
"successful_tasks": 0,
|
||
"failed_tasks": 0,
|
||
"task_results": []
|
||
}
|
||
|
||
self._logger.info(f"开始创建批量90%10%小瓶投料任务: {batch_name}, 包含 {len(tasks)} 个子任务")
|
||
|
||
# 逐个创建任务
|
||
for i, task in enumerate(tasks):
|
||
try:
|
||
# 合并全局设置和任务特定设置
|
||
task_params = {**global_settings, **task}
|
||
|
||
# 验证必填参数 - hold_m_name是必须的
|
||
if not task_params.get("hold_m_name"):
|
||
error_msg = f"任务 {i+1} 缺少必填参数: hold_m_name"
|
||
self._logger.error(error_msg)
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"status": "error",
|
||
"message": error_msg
|
||
})
|
||
batch_results["failed_tasks"] += 1
|
||
continue
|
||
|
||
# 设置任务名称
|
||
if not task_params.get("order_name"):
|
||
task_params["order_name"] = f"{batch_name}_任务{i+1}"
|
||
|
||
# 调用单个90%10%小瓶投料任务创建方法
|
||
task_result = self.create_90_10_vial_feeding_task(
|
||
order_name=task_params.get("order_name"),
|
||
speed=task_params.get("speed"),
|
||
temperature=task_params.get("temperature"),
|
||
delay_time=task_params.get("delay_time"),
|
||
percent_90_1_assign_material_name=task_params.get("percent_90_1_assign_material_name"),
|
||
percent_90_1_target_weigh=task_params.get("percent_90_1_target_weigh"),
|
||
percent_90_2_assign_material_name=task_params.get("percent_90_2_assign_material_name"),
|
||
percent_90_2_target_weigh=task_params.get("percent_90_2_target_weigh"),
|
||
percent_90_3_assign_material_name=task_params.get("percent_90_3_assign_material_name"),
|
||
percent_90_3_target_weigh=task_params.get("percent_90_3_target_weigh"),
|
||
percent_10_1_assign_material_name=task_params.get("percent_10_1_assign_material_name"),
|
||
percent_10_1_target_weigh=task_params.get("percent_10_1_target_weigh"),
|
||
percent_10_1_volume=task_params.get("percent_10_1_volume"),
|
||
percent_10_1_liquid_material_name=task_params.get("percent_10_1_liquid_material_name"),
|
||
percent_10_2_assign_material_name=task_params.get("percent_10_2_assign_material_name"),
|
||
percent_10_2_target_weigh=task_params.get("percent_10_2_target_weigh"),
|
||
percent_10_2_volume=task_params.get("percent_10_2_volume"),
|
||
percent_10_2_liquid_material_name=task_params.get("percent_10_2_liquid_material_name"),
|
||
percent_10_3_assign_material_name=task_params.get("percent_10_3_assign_material_name"),
|
||
percent_10_3_target_weigh=task_params.get("percent_10_3_target_weigh"),
|
||
percent_10_3_volume=task_params.get("percent_10_3_volume"),
|
||
percent_10_3_liquid_material_name=task_params.get("percent_10_3_liquid_material_name"),
|
||
hold_m_name=task_params.get("hold_m_name")
|
||
)
|
||
|
||
# 记录任务结果
|
||
if isinstance(task_result, dict) and task_result.get("status") != "error":
|
||
batch_results["successful_tasks"] += 1
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"task_name": task_params.get("order_name"),
|
||
"status": "success",
|
||
"result": task_result
|
||
})
|
||
self._logger.info(f"任务 {i+1} 创建成功: {task_params.get('order_name')}")
|
||
else:
|
||
batch_results["failed_tasks"] += 1
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"task_name": task_params.get("order_name"),
|
||
"status": "error",
|
||
"message": str(task_result)
|
||
})
|
||
self._logger.error(f"任务 {i+1} 创建失败: {task_result}")
|
||
|
||
except Exception as e:
|
||
error_msg = f"任务 {i+1} 处理时发生异常: {str(e)}"
|
||
self._logger.error(error_msg)
|
||
batch_results["failed_tasks"] += 1
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"status": "error",
|
||
"message": error_msg
|
||
})
|
||
|
||
# 设置批量任务整体状态
|
||
if batch_results["failed_tasks"] == 0:
|
||
batch_results["status"] = "success"
|
||
batch_results["message"] = f"批量90%10%小瓶投料任务全部创建成功,共 {batch_results['successful_tasks']} 个任务"
|
||
elif batch_results["successful_tasks"] == 0:
|
||
batch_results["status"] = "error"
|
||
batch_results["message"] = f"批量90%10%小瓶投料任务全部创建失败,共 {batch_results['failed_tasks']} 个任务"
|
||
else:
|
||
batch_results["status"] = "partial_success"
|
||
batch_results["message"] = f"批量90%10%小瓶投料任务部分成功,成功 {batch_results['successful_tasks']} 个,失败 {batch_results['failed_tasks']} 个"
|
||
|
||
self._logger.info(f"批量90%10%小瓶投料任务完成: {batch_results['message']}")
|
||
return batch_results
|
||
|
||
except json.JSONDecodeError as e:
|
||
error_msg = f"JSON解析失败: {str(e)}"
|
||
self._logger.error(error_msg)
|
||
return {"status": "error", "message": error_msg}
|
||
except Exception as e:
|
||
error_msg = f"创建批量90%10%小瓶投料任务时发生错误: {str(e)}"
|
||
self._logger.error(error_msg)
|
||
return {"status": "error", "message": error_msg}
|
||
|
||
def create_batch_diamine_solution_task(self, json_str: str) -> dict:
|
||
"""
|
||
创建批量二胺溶液配制任务
|
||
|
||
接受JSON输入,支持多个二胺溶液配制任务的批量创建
|
||
|
||
JSON格式示例:
|
||
{
|
||
"batch_name": "批量二胺溶液配制任务_20240101",
|
||
"tasks": [
|
||
{
|
||
"order_name": "二胺溶液配制任务1",
|
||
"material_name": "物料A",
|
||
"target_weigh": "10.5",
|
||
"volume": "50.0",
|
||
"liquid_material_name": "NMP",
|
||
"speed": "400",
|
||
"temperature": "25",
|
||
"delay_time": "600",
|
||
"hold_m_name": "A01"
|
||
},
|
||
{
|
||
"order_name": "二胺溶液配制任务2",
|
||
"material_name": "物料B",
|
||
"target_weigh": "15.2",
|
||
"volume": "75.0",
|
||
"liquid_material_name": "DMF",
|
||
"speed": "350",
|
||
"temperature": "30",
|
||
"delay_time": "800",
|
||
"hold_m_name": "B02"
|
||
}
|
||
],
|
||
"global_settings": {
|
||
"speed": "400",
|
||
"temperature": "25",
|
||
"delay_time": "600",
|
||
"liquid_material_name": "NMP"
|
||
}
|
||
}
|
||
|
||
参数说明:
|
||
- batch_name: 批量任务名称,可选
|
||
- tasks: 任务列表,每个任务包含二胺溶液配制参数
|
||
- global_settings: 全局默认设置,当单个任务未指定参数时使用
|
||
|
||
每个任务参数:
|
||
- order_name: 任务名称
|
||
- material_name: 物料名称,必填
|
||
- target_weigh: 目标重量,必填
|
||
- volume: 体积,必填
|
||
- liquid_material_name: 液体物料名称,可选
|
||
- speed: 搅拌速度,可选
|
||
- temperature: 温度,可选
|
||
- delay_time: 延迟时间,可选
|
||
- hold_m_name: 库位名称,可选
|
||
|
||
返回: 批量任务创建结果
|
||
"""
|
||
try:
|
||
# 解析JSON输入
|
||
data = json.loads(json_str)
|
||
|
||
# 获取批量任务参数
|
||
batch_name = data.get("batch_name", f"批量二胺溶液配制任务_{self.get_current_time_iso8601()}")
|
||
tasks = data.get("tasks", [])
|
||
global_settings = data.get("global_settings", {})
|
||
|
||
if not tasks:
|
||
return {
|
||
"status": "error",
|
||
"message": "任务列表不能为空"
|
||
}
|
||
|
||
# 批量创建结果
|
||
batch_results = {
|
||
"batch_name": batch_name,
|
||
"total_tasks": len(tasks),
|
||
"successful_tasks": 0,
|
||
"failed_tasks": 0,
|
||
"task_results": []
|
||
}
|
||
|
||
self._logger.info(f"开始创建批量二胺溶液配制任务: {batch_name}, 包含 {len(tasks)} 个子任务")
|
||
|
||
# 逐个创建任务
|
||
for i, task in enumerate(tasks):
|
||
try:
|
||
# 合并全局设置和任务特定设置
|
||
task_params = {**global_settings, **task}
|
||
|
||
# 验证必填参数
|
||
required_params = ["material_name", "target_weigh", "volume"]
|
||
missing_params = [param for param in required_params if not task_params.get(param)]
|
||
|
||
if missing_params:
|
||
error_msg = f"任务 {i+1} 缺少必填参数: {', '.join(missing_params)}"
|
||
self._logger.error(error_msg)
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"status": "error",
|
||
"message": error_msg
|
||
})
|
||
batch_results["failed_tasks"] += 1
|
||
continue
|
||
|
||
# 设置任务名称
|
||
if not task_params.get("order_name"):
|
||
task_params["order_name"] = f"{batch_name}_任务{i+1}"
|
||
|
||
# 调用单个二胺溶液配制任务创建方法
|
||
task_result = self.create_diamine_solution_task(
|
||
order_name=task_params.get("order_name"),
|
||
material_name=task_params.get("material_name"),
|
||
target_weigh=task_params.get("target_weigh"),
|
||
volume=task_params.get("volume"),
|
||
liquid_material_name=task_params.get("liquid_material_name", "NMP"),
|
||
speed=task_params.get("speed"),
|
||
temperature=task_params.get("temperature"),
|
||
delay_time=task_params.get("delay_time"),
|
||
hold_m_name=task_params.get("hold_m_name")
|
||
)
|
||
|
||
# 记录任务结果
|
||
if isinstance(task_result, dict) and task_result.get("status") != "error":
|
||
batch_results["successful_tasks"] += 1
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"task_name": task_params.get("order_name"),
|
||
"status": "success",
|
||
"result": task_result
|
||
})
|
||
self._logger.info(f"任务 {i+1} 创建成功: {task_params.get('order_name')}")
|
||
else:
|
||
batch_results["failed_tasks"] += 1
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"task_name": task_params.get("order_name"),
|
||
"status": "error",
|
||
"message": str(task_result)
|
||
})
|
||
self._logger.error(f"任务 {i+1} 创建失败: {task_result}")
|
||
|
||
except Exception as e:
|
||
error_msg = f"滴定液任务 {i+1} 处理时发生异常: {str(e)}"
|
||
self._logger.error(error_msg)
|
||
batch_results["failed_tasks"] += 1
|
||
batch_results["task_results"].append({
|
||
"task_index": i + 1,
|
||
"status": "error",
|
||
"message": error_msg
|
||
})
|
||
|
||
# 设置批量任务整体状态
|
||
if batch_results["failed_tasks"] == 0:
|
||
batch_results["status"] = "success"
|
||
batch_results["message"] = f"批量滴定液任务全部创建成功,共 {batch_results['successful_tasks']} 个任务"
|
||
elif batch_results["successful_tasks"] == 0:
|
||
batch_results["status"] = "error"
|
||
batch_results["message"] = f"批量滴定液任务全部创建失败,共 {batch_results['failed_tasks']} 个任务"
|
||
else:
|
||
batch_results["status"] = "partial_success"
|
||
batch_results["message"] = f"批量滴定液任务部分成功,成功 {batch_results['successful_tasks']} 个,失败 {batch_results['failed_tasks']} 个"
|
||
|
||
self._logger.info(f"批量滴定液任务完成: {batch_results['message']}")
|
||
return batch_results
|
||
|
||
except json.JSONDecodeError as e:
|
||
error_msg = f"JSON解析失败: {str(e)}"
|
||
self._logger.error(error_msg)
|
||
return {"status": "error", "message": error_msg}
|
||
except Exception as e:
|
||
error_msg = f"创建批量滴定液任务时发生错误: {str(e)}"
|
||
self._logger.error(error_msg)
|
||
return {"status": "error", "message": error_msg}
|